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

  1. You register a webhook endpoint per environment from My Applications → Webhooks.
  2. USBC POSTs a JSON payload to that endpoint whenever a subscribed event fires.
  3. Your endpoint replies 2xx within 5 seconds to acknowledge.
  4. 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 timestamp is more than 5 minutes off from server time — protects against replay attacks.

Event types (planned)

EventTriggered when
transaction.confirmedA blockchain transaction reaches finality
transaction.failedA transaction is rejected on-chain
kyc.approvedA KYC session completes successfully
kyc.rejectedA KYC session is rejected
wallet.createdA new wallet is provisioned for a user
account.frozenAn 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.