apps/api/src/services/webhooks.ts — this reflects what actually fires, not what’s planned.
Envelope
Every event is delivered as a POST with this JSON envelope:X-Sly-Signature: t=<unix_ts>,v1=<hmac_sha256_hex>— verify thisX-Sly-Event-Id: evt_<uuid>— stable ID; dedupe in your handlerX-Sly-Delivery-Id: del_<uuid>— unique per delivery attemptX-Sly-Webhook-Id: wh_<uuid>— which subscription triggered this
Transfer events
| Event | Fires when |
|---|---|
transfer.created | New transfer record created |
transfer.completed | Transfer settled |
transfer.failed | Transfer settlement failed |
transfer.refunded | Transfer was refunded |
data:
Settlement events
Fired when payouts transition through Circle (stablecoin → fiat) or other rails.| Event | Fires when |
|---|---|
settlement.initiated | Payout submitted to rail (status: pending) |
settlement.processing | Payout accepted by rail (status: confirmed) |
settlement.completed | Payout finalized |
settlement.failed | Payout rejected by rail |
settlement.returned | Payout returned (ACH return, chargeback) |
data:
return and errorCode are only present for returned/failed states.
Batch events
For batch transfers and mass operations.| Event | Fires when |
|---|---|
batch.created | Batch queued |
batch.processing | Batch processing started |
batch.completed | All items processed (success or failure) |
batch.failed | Batch processing errored at the batch level |
data:
Reconciliation events
| Event | Fires when |
|---|---|
reconciliation.completed | Reconciliation run finished |
reconciliation.discrepancy | Discrepancy detected during recon |
data for reconciliation.completed:
x402 events
| Event | Fires when |
|---|---|
x402.payment.completed | x402 payment settled to endpoint owner |
x402.endpoint.created | New x402 endpoint registered |
data for x402.payment.completed:
AP2 events
| Event | Fires when |
|---|---|
ap2.mandate.created | New mandate signed and issued |
ap2.mandate.executed | Mandate execution recorded |
ap2.mandate.revoked | Mandate revoked |
data for ap2.mandate.executed:
ACP events
| Event | Fires when |
|---|---|
acp.checkout.created | New ACP checkout session |
acp.checkout.completed | Checkout completed with payment |
acp.checkout.expired | Session expired without completion |
data:
Account events
| Event | Fires when |
|---|---|
account.created | New account created |
account.updated | Account record modified |
account.balance.low | Account balance below configured threshold |
data for account.balance.low:
System events
| Event | Fires when |
|---|---|
webhook.test | You triggered POST /v1/webhooks/:id/test |
webhook.dlq | A delivery exhausted retries and entered dead-letter queue |
data for webhook.test:
Subscribing
- Specific events:
"transfer.completed" - Wildcards:
"transfer.*"(all transfer events) - Everything:
"*"(not recommended in production — high volume)
Enumerate supported events programmatically
Events in planning but NOT emitted yet
The following have been mentioned in platform roadmap docs but are not wired up in the current API. Don’t subscribe expecting to receive them:- Most
stream.*events (runway alerts, pause, resume, cancel) approval.*events — use polling + the approvals endpoints insteaddispute.*/refund.*as distinct events — currently surfaced throughtransfer.refundedand audit log; dedicated events plannedagent.*lifecycle events (tier upgrade, key rotation, etc.)quote.*,mpp.*,a2a.task.*funding.transaction.*,treasury.alert.*card.dispute.*
Delivery behavior recap
- At-least-once — dedupe by
X-Sly-Event-Id - Timeout — 10 seconds; respond fast, process async
- Retries — 1m → 5m → 15m → 1h → 24h, then DLQ
- Envelope always
{ id, type, timestamp, data }