Guides

Webhooks

Subscribe to transaction lifecycle events, verify HMAC signatures, and rely on retries with circuit breaker protection.

Last updated 2026-07-16View as Markdown

Webhooks push every transaction state transition to your endpoint as it happens. They are the recommended integration pattern: one subscription replaces a polling loop, and delivery comes with signatures, retries, and a 30-day delivery log.

Register a subscription

curl -X POST https://api-staging.coinbax.com/api/v1/webhooks \
  -H "X-API-Key: $COINBAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example.com/coinbax-webhook",
    "eventTypes": ["transaction.created", "transaction.escrowed", "transaction.completed", "transaction.refunded", "transaction.failed"]
  }'

The response includes the subscription’s signing secret. Like API keys, it is shown once; store it securely. Requires the write:webhooks scope.

You can register multiple subscriptions, each with its own URL, event selection, and secret. An optional ipWhitelist restricts which addresses may receive deliveries for the subscription. Manage subscriptions with GET/PATCH/DELETE /webhooks/{id}, and send a test delivery with POST /webhooks/{id}/test.

Event types

Event Fires when
transaction.created A transaction is created; always the first event in a lifecycle
transaction.escrowed Funds lock on-chain in the escrow contract
transaction.completed Funds released to the receiver (terminal)
transaction.failed A pre-escrow control failed or risk rejected the transaction (terminal)
transaction.refunded Funds returned to the sender (terminal)
transaction.rescinded The sender cancelled during the hold period (terminal)
dispute.created A dispute is opened
dispute.resolved A dispute is resolved
dispute.escalated A dispute escalates to the next resolution tier
evidence.submitted Evidence is submitted on a dispute

The canonical, always-current list is available at GET /webhooks/events/types.

Payload shape

Deliveries are HTTPS POSTs with a JSON body. Transaction events carry the transaction plus event-specific fields:

// transaction.escrowed
{
  "transaction": { "id": "9f8b3c2a-...", "status": "escrowed" },
  "escrowAddress": "0x...",
  "txHash": "0x..."
}
// transaction.refunded
{
  "transaction": { "id": "9f8b3c2a-...", "status": "refunded" },
  "reason": "Customer request"
}

Each delivery includes these headers:

Header Contents
X-Webhook-Event The event type, for example transaction.completed
X-Webhook-Signature HMAC-SHA256 hex digest of the request body, keyed by your secret
X-Webhook-Delivery Unique delivery ID, for idempotency

Verify the signature

Every delivery is signed. Verify before trusting anything in the payload:

import crypto from "node:crypto";

function verifyWebhook(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signatureHeader),
    Buffer.from(expected),
  );
}

Two details that bite people:

  • Verify against the raw request body, not re-serialized parsed JSON. Re-serialization can reorder keys and change bytes, which breaks the HMAC.
  • Use a timing-safe comparison (crypto.timingSafeEqual or your language’s equivalent), never === on the hex strings.

Reject any request whose signature does not match.

Delivery, retries, and the circuit breaker

  • At-least-once. Deliveries can repeat. Dedupe on X-Webhook-Delivery within a reasonable window (24 hours is a good default).
  • Respond fast. Coinbax waits up to 30 seconds for a 2xx response. Acknowledge immediately and process asynchronously; a slow handler is indistinguishable from a failing one.
  • Retries. Non-2xx responses, timeouts, and network errors are retried with exponential backoff.
  • Circuit breaker. Each subscription has a circuit breaker. Repeated failures trip it OPEN, pausing deliveries so a broken endpoint does not accumulate an unbounded retry queue. It transitions to HALF_OPEN to test recovery and closes again once deliveries succeed. You can force a reset with POST /webhooks/{id}/reset-circuit-breaker.
  • Delivery logs. GET /webhooks/{id}/delivery-logs returns a 30-day audit trail with request and response details per delivery, which is the fastest way to debug a misbehaving endpoint.

Rotate the secret

Secrets rotate with zero downtime using dual signing:

  1. POST /webhooks/{id}/rotate-secret returns a new secret. During the grace period Coinbax signs deliveries with both the old and the new secret, so your endpoint can verify with either.
  2. Deploy the new secret to your handler.
  3. POST /webhooks/{id}/complete-rotation retires the old secret.

Rotate immediately if a secret may have been exposed, and on your normal credential rotation schedule otherwise.

Handler checklist

  • Verify the HMAC signature against the raw body, timing-safe
  • Dedupe on X-Webhook-Delivery
  • Return 2xx quickly; process async
  • Treat event ordering as advisory; fetch the transaction with GET /transactions/{id} when you need authoritative current state
  • Serve HTTPS with a valid certificate

Next steps