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
x-project-silo-signature: v1=<hex-hmac of "{timestamp}.{rawBody}">
x-project-silo-timestamp: 1777435200
x-project-silo-event-id: evt_...
Content-Type: application/jsonTo 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
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_degradedDeposit 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.
{
"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_..."
}
}