> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.getsinbad.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.getsinbad.com/_mcp/server.

# Webhooks

> Register an endpoint, verify its signatures and operate it for trade and message events.

A webhook endpoint is an HTTPS URL on your server. Sinbad sends it signed events about your customers' trades and messages, so you do not have to poll. Delivery is at least once, so your endpoint must verify each signature and ignore repeats.

> **Note**
>
> Confirm with Sinbad that webhook delivery is available in your environment. Registering an endpoint and queuing a test do not prove that delivery works.

## 1. Register an endpoint

Call [`POST /v1/webhooks`](https://docs.getsinbad.com/api-reference/webhooks/create-webhook). The URL must:

* use `https` on port 443,
* be a public hostname with no credentials, query string or fragment,
* not be an IP address or a reserved local hostname, and
* resolve in DNS to public addresses only.

Sinbad does not follow redirects. Register the endpoint for one permitted integration, and store its signing secret immediately: Sinbad returns it once, and it is not returned again if you repeat the request with the same `Idempotency-Key`. List your endpoints with [`GET /v1/webhooks`](https://docs.getsinbad.com/api-reference/webhooks/list-webhooks).

## 2. Verify ownership

Call [`POST /v1/webhooks/{webhookId}/test`](https://docs.getsinbad.com/api-reference/webhooks/test-webhook). Sinbad queues a [`webhook.test`](/webhook-events/webhook-test) event. Verify its signature against the raw request body, and respond with the exact challenge in a JSON response. A `202` from the test call means the test is queued, not that it succeeded. Check the result with [`GET /v1/webhooks/{webhookId}`](https://docs.getsinbad.com/api-reference/webhooks/get-webhook).

## 3. Receive trade and message events

Verify every signature, store the event durably, and deduplicate on the event ID. Each event type has a page with the complete payloads:

* [`trade.created`](/webhook-events/trade-created)
* [`trade.updated`](/webhook-events/trade-updated)
* [`trade.message.created`](/webhook-events/trade-message-created)

## Verify signatures before parsing

Use the exact raw request bytes and the timestamp header. Decode the `whsec_` suffix of your secret as base64url, then compute HMAC-SHA256 over `timestamp + "." + rawBody`. Match an accepted key ID, and reject timestamps more than 5 minutes from your clock.

```javascript
import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(rawBody, timestamp, signatureHeader, secrets, now = Date.now()) {
  if (!/^\d{1,12}$/.test(timestamp) || Math.abs(now / 1000 - Number(timestamp)) > 300)
    return false;
  return signatureHeader.split(',').some(entry => {
    const match = /^v1=([\w-]{1,64}):([a-f0-9]{64})$/.exec(entry.trim());
    if (!match || !secrets[match[1]]) return false;
    const key = Buffer.from(secrets[match[1]].slice(6), 'base64url');
    const expected = createHmac('sha256', key)
      .update(timestamp).update('.').update(rawBody).digest();
    return timingSafeEqual(expected, Buffer.from(match[2], 'hex'));
  });
}
```

After verification:

1. Require the signed event ID to match the `Sinbad-Webhook-Id` header.
2. Check the expected environment and integration.
3. Save a durable inbox record before you acknowledge the event.

A duplicate of an event you already accepted can be acknowledged again.

## 4. Inspect and replay failures

Read deliveries and their attempts with [`GET /v1/webhooks/{webhookId}/deliveries`](https://docs.getsinbad.com/api-reference/webhooks/list-webhook-deliveries) and [`GET /v1/webhooks/{webhookId}/deliveries/{deliveryId}`](https://docs.getsinbad.com/api-reference/webhooks/get-webhook-delivery). Fix your endpoint, then replay an eligible failed event with [`POST /v1/webhooks/{webhookId}/deliveries/{deliveryId}/replay`](https://docs.getsinbad.com/api-reference/webhooks/replay-webhook-delivery) within 30 days of when Sinbad captured it. A replay keeps the original event ID and body.

Sinbad retries a delivery up to 12 attempts or 24 hours, whichever comes first, when it hits a timeout, a network failure, or a `408`, `429` or `5xx` response. Any other failure is final. A `410` response disables the endpoint.

## 5. Manage the endpoint

Change an endpoint with [`PATCH /v1/webhooks/{webhookId}`](https://docs.getsinbad.com/api-reference/webhooks/update-webhook), using the current revision. Rotate its secret with [`POST /v1/webhooks/{webhookId}/rotate-secret`](https://docs.getsinbad.com/api-reference/webhooks/rotate-webhook), either with a 24-hour overlap or as an immediate replacement. Pause an endpoint, or remove it with [`DELETE /v1/webhooks/{webhookId}`](https://docs.getsinbad.com/api-reference/webhooks/delete-webhook), when you no longer need it.

A successful test moves a disabled endpoint to `paused`. Activate it explicitly after you have verified it. Limits for these calls are in [Limits and limitations](/limits-and-limitations#rate-limits).