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.refundedescrow.created·escrow.claimed·escrow.refundedsubscription.plan_created·subscription.started·subscription.charged·subscription.cancelled·subscription.haltedcampaign.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.