Webhooks
USBC delivers webhooks for asynchronous events — transaction confirmations, KYC decisions, account state changes — so your service doesn't have to poll.
Placeholder content. The exact event catalogue is still being finalized by the platform team.
How delivery works
- You register a webhook endpoint per environment from My Applications → Webhooks.
- USBC
POSTs a JSON payload to that endpoint whenever a subscribed event fires. - Your endpoint replies
2xxwithin 5 seconds to acknowledge. - Non-2xx responses are retried with exponential backoff for up to 24 hours.
Request shape
POST /your/webhook HTTP/1.1
Content-Type: application/json
X-USBC-Event-Id: 01HQ123ABC456DEF
X-USBC-Event-Type: transaction.confirmed
X-USBC-Event-Timestamp: 2026-05-13T10:42:00Z
X-USBC-Signature: t=1715594520,v1=8f4c3a2e7d1...
{
"id": "01HQ123ABC456DEF",
"type": "transaction.confirmed",
"created_at": "2026-05-13T10:42:00Z",
"data": {
"transaction_id": "tx_01HQ...",
"amount": 12500,
"currency": "USDC",
"from": "wallet_aaa...",
"to": "wallet_bbb..."
}
}
Verifying the signature
Every delivery carries an HMAC-SHA256 signature over timestamp.body using your webhook secret. Reject the request if the signature doesn't match — that's how you know it really came from USBC.
import crypto from "crypto";
function verify(payload, header, secret) {
const [tPart, vPart] = header.split(",");
const t = tPart.split("=")[1];
const v = vPart.split("=")[1];
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${payload}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(v), Buffer.from(expected));
}
Tip: also reject requests where
timestampis more than 5 minutes off from server time — protects against replay attacks.
Event types (planned)
| Event | Triggered when |
|---|---|
transaction.confirmed | A blockchain transaction reaches finality |
transaction.failed | A transaction is rejected on-chain |
kyc.approved | A KYC session completes successfully |
kyc.rejected | A KYC session is rejected |
wallet.created | A new wallet is provisioned for a user |
account.frozen | An account is frozen due to a compliance review |
Full event catalogue and payload schemas: coming soon. Watch the release notes (also coming soon) for changes.
Local development
For local testing, use a tunnel (e.g. ngrok, cloudflared) to expose your localhost:
ngrok http 3000
# → register https://abcd-1234.ngrok-free.app/your/webhook in the portal
You can replay any past event from the portal's webhook log — useful for testing your handler's idempotency without re-triggering the source event.