API

Webhooks

Webhooks are how Tab tells your backend that something happened. They fire in real time, are signed, and are retried with exponential backoff if your endpoint doesn't return a 2xx.

Event types

  • order.created · order.completed · order.cancelled · order.expired · order.refunded
  • escrow.created · escrow.claimed · escrow.refunded
  • subscription.plan_created · subscription.started · subscription.charged · subscription.cancelled · subscription.halted
  • campaign.grant_paid

Payload shape (Stripe-compatible envelope)

Tab's envelope mirrors Stripe's exactly — sameid, object, type,data.object, created, livemode keys. Existing Stripe webhook handlers in any language need at most a URL + secret swap. Only the signature header name differs (Tab-Signature instead of Stripe-Signature) — wire format is identical.

POST https://your.app/webhooks/tab
Tab-Signature: t=1715953412,v1=8a4e1b…
Content-Type: application/json

{
  "id": "evt_3pX…",
  "object": "event",
  "api_version": "2025-01-01",
  "type": "order.completed",
  "created": 1715953412,
  "created_at": "2026-05-17T12:43:32Z",
  "livemode": true,
  "pending_webhooks": 1,
  "data": {
    "object": {
      "order": {
        "id": "ord_8H3kZ…",
        "amount": "12.50",
        "currency": "USDC",
        "chain": "base",
        "txHash": "0x…"
      }
    }
  }
}

Verifying the signature

The Tab-Signature header carries a timestamp and an HMAC-SHA256 over <timestamp>.<raw body> keyed on your webhook signing secret. Reject any request where the signature doesn't match, or where the timestamp is more than five minutes old.

const expected = hmacSha256(
  `${t}.${rawBody}`,
  WEBHOOK_SIGNING_SECRET
);
if (!timingSafeEqual(expected, providedV1)) throw new Error("bad sig");

Retries

Non-2xx responses are retried at 1m, 5m, 15m, 1h, 6h, and 24h. If the endpoint still hasn't ack'd after a day, the event is marked failed and shows up in the dashboard for manual replay.

Replay

Any past event can be replayed from the dashboard or via POST /v1/events/<id>/replay. Useful for backfilling after an outage.