v0.1 — Phase 1

Event types

EventWhen it fires
transfer.initiatedTransfer passes policy check and is submitted to Solana. Not yet final.
transfer.settledTransfer achieves on-chain finality. tx_signature is included.
transfer.rejectedTransfer fails policy validation. Never submitted to Solana.
balance.low_thresholdAccount USDC balance drops below the configured threshold.
POST /v1/webhooks

Register a webhook endpoint to receive event notifications.

Request body

json
{
  "url": "https://your-service.example.com/clawdgo/events",
  "events": [
    "transfer.settled",
    "transfer.rejected",
    "balance.low_threshold"
  ],
  "account_ids": ["acct_01j8k4x9p2qrst7yz"],
  "low_balance_threshold_usdc": 50
}

Leave account_ids empty to receive events for all accounts belonging to the operator.

Event payload

All webhook payloads share a common envelope:

json
{
  "id": "evt_04p7r2s9u1vwxy3cd",
  "type": "transfer.settled",
  "created_at": "2024-01-15T10:41:03Z",
  "data": {
    "transfer_id": "txn_02m9n5y3q7wpuv8ab",
    "account_id": "acct_01j8k4x9p2qrst7yz",
    "amount_usdc": "25.000000",
    "tx_signature": "5j4Kz9qXm2nWpRt8vYbFdCgHs3Ae7UoLkNi6ZwQxPmV...",
    "settled_at": "2024-01-15T10:41:02Z"
  }
}

Signature verification

All webhook requests include a X-ClawdGo-Signature header — an HMAC-SHA256 of the raw request body signed with your webhook secret.

typescript
import crypto from "node:crypto";

function verifyWebhook(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}
Always verify. Reject any webhook payload that fails signature verification. Do not process unverified events.

Retry behavior

If your endpoint returns a non-2xx response, ClawdGo retries with exponential backoff:

  • Retry 1: 30 seconds
  • Retry 2: 5 minutes
  • Retry 3: 30 minutes
  • Retry 4: 2 hours

After 4 failed retries the event is marked as undelivered. You can replay undelivered events from the dashboard.

Respond with 200 immediately on receipt. Process the event asynchronously to avoid timeouts.