Webhook Events

Webhooks are signed with HMAC-SHA256 using the endpoint secret returned during endpoint creation or rotation. The signed payload is "{timestamp}.{rawBody}" — the unix-seconds timestamp from the x-project-silo-timestamp header, a literal dot, then the exact request body bytes. Signing the timestamp prevents captured deliveries from being replayed later.

Headers

headers.txttxt
x-project-silo-signature: v1=<hex-hmac of "{timestamp}.{rawBody}">
x-project-silo-timestamp: 1777435200
x-project-silo-event-id: evt_...
Content-Type: application/json

To verify: recompute HMAC-SHA256 over "{timestamp}.{rawBody}" with your endpoint secret, compare against the v1= value in constant time, and reject deliveries whose timestamp is more than 300 seconds from your clock. The SDK helpers (verifyWebhookSignature / verify_webhook_signature) implement all three steps.

Event types

events.txttxt
deposit.account.created
deposit.account.destination_updated
deposit.source_seen
deposit.source_confirmed
deposit.credit_submitted
deposit.credited
deposit.failed
deposit.manual_review_required
webhook.test
tenant.api_key_revoked
inventory.low_balance
route.provider_degraded

Deposit lifecycle events fire automatically as the settlement pipeline advances — no polling required. deposit.source_seen, deposit.source_confirmed, deposit.credited, and deposit.failed are durable: each is emitted at most once per deposit with a deterministic event id (evt_dep_<depositId>_<status>), and late transitions (a manual credit days later) are still picked up and delivered. deposit.credit_submitted is a best-effort in-flight signal while a deposit is in routed settlement — pre-credited deposits and fast routes may skip it, so drive state from credited/failed, not from its presence. Payloads carry the same deposit object served by GET /v1/deposits, so a credited event doubles as a settlement receipt.

deposit.account.destination_updated

Emitted when PATCH /v1/deposit-accounts/:externalUserId/destination changes an account's settlement destination. The payload carries the updated account, the prior destination (or null when none was recorded), and the API key that made the change — enough to audit destination moves without polling.

payload.jsonjson
{
  "id": "evt_...",
  "type": "deposit.account.destination_updated",
  "createdAt": "2026-06-01T12:00:00.000Z",
  "tenantId": "11111111-2222-3333-4444-555555555555",
  "data": {
    "depositAccount": { "...": "updated deposit account" },
    "priorDestination": {
      "chainId": 137,
      "address": "0x1111111111111111111111111111111111111111",
      "tokenAddress": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174"
    },
    "apiKeyId": "key_..."
  }
}