Webhooks
Webhooks.
Signed, retried notifications when checks and payments change.
Event types.
check.created
check.updated
check.closed
check.fully_paid
payment.succeeded
payment.refunded
claim.created
feedback.received
Every delivery is a POST with this envelope — data is the resource’s full current state, identical to what the matching GET returns:
JSON
{
"id": "…",
"type": "check.fully_paid",
"created_at": "2026-08-24T19:04:11Z",
"data": { }
}
Registering an endpoint.
Requires write scope. URLs must be HTTPS.
curl -X POST https://api.tabsettle.com/v1/webhook-endpoints \
-H "Authorization: Bearer ts_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
-H "Content-Type: application/json" \
-d '{ "url": "https://your-server.example.com/tabsettle-webhook",
"event_types": ["check.fully_paid", "payment.succeeded"] }'
await fetch('https://api.tabsettle.com/v1/webhook-endpoints', {
method: 'POST',
headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
url: 'https://your-server.example.com/tabsettle-webhook',
event_types: ['check.fully_paid', 'payment.succeeded'],
}),
});
The response is the only place your signing secret (
whsec_…) is ever returned — store it immediately.Use POST /v1/webhook-endpoints/{id}/test to send yourself a signed ping and verify your receiver end-to-end before going live.
Verifying signatures.
Every delivery carries:
HTTP HEADER
X-TabSettle-Signature: t=<unix_seconds>,v1=<hex hmac-sha256(secret, "<t>.<rawBody>")>
If you’ve verified a Stripe webhook, this is the same scheme:
NODE
const crypto = require('node:crypto');
function verifyTabSettleWebhook(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(
signatureHeader.split(',').map((kv) => kv.split('='))
);
const t = Number(parts.t);
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
if (expected !== parts.v1) throw new Error('signature mismatch');
if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) throw new Error('timestamp outside tolerance — possible replay');
return JSON.parse(rawBody);
}
Verify against the raw request body — re-serializing JSON can change byte order and break the signature.
Delivery semantics.
- At-least-once: dedupe on the envelope
id. - Order is not guaranteed — but every payload is full state, so processing duplicates or out-of-order deliveries is harmless.
- Respond 2xx within 10 seconds. Anything else is retried on this backoff: 1m, 5m, 30m, 2h, 8h, 24h (≈35 hours total) before the delivery is marked dead.
- An endpoint that fails many deliveries in a row is automatically disabled to protect both sides — fix your receiver and re-register.