Skip the polling. TicketIt pushes a signed, JSON event to your endpoint the instant an order clears, a pass is scanned, or a payout settles — so your systems stay in lockstep with the gate.
~1.2s
median delivery
99.98%
delivery success
24h
retry window
signed delivery
X-TicketIt-Signature: t=1690104724,v1=5257a869…
Webhook-Id: whd_019f8ba3c711
{ "type": "order.completed","data": {"ref": "ORD-4821","total": 892500,"currency": "KES"}}
Event catalog
Pick only the events you need — checkout, the gate, payouts and the catalog. Each fires once, carries the full resource, and is retried until you acknowledge it.
order.completedA buyer’s payment cleared and their tickets were issued.
order.failedA payment attempt was declined or abandoned.
order.refundedAn order was fully or partially refunded.
ticket.issuedA signed pass was generated for a seat.
ticket.scannedA pass was admitted at the gate (first scan wins).
ticket.voidedA pass was invalidated before the event.
payout.paidA merchant payout settled to M-Pesa or bank.
payout.failedA payout could not be delivered and was reversed.
event.publishedAn organizer took an event live.
event.sold_outThe last ticket for an event was sold.
Add your HTTPS URL in the dashboard and choose the events to receive. We generate a signing secret on the spot.
When something happens we POST the JSON event with an X-TicketIt-Signature header, and retry until you say 200.
Check the HMAC against the raw body, ack in under 15s, then fan the work out to your own queue.
Payloads
Every event shares one envelope — id, type, created and a data object with the full resource. Money is always integer minor units.
{
"id": "evt_019f8ba3c711",
"type": "order.completed",
"created": "2026-07-23T10:12:04Z",
"livemode": true,
"data": {
"id": "019f8b91-c711-7bb5-a901-80e596e2b01f",
"ref": "ORD-4821",
"event_id": "019f85d0-3fa0-728a-9742-26d6871bcafd",
"event_title": "Nairobi Jazz Festival",
"buyer_name": "Amara Njoroge",
"status": "completed",
"subtotal": 850000,
"fees": 42500,
"total": 892500,
"currency": "KES",
"items": [
{ "tier_type": "VIP", "quantity": 1, "unit_price": 850000 }
]
}
}Verify every request
We sign timestamp.rawBody with your endpoint secret using HMAC-SHA256 and send it as the X-TicketIt-Signature header. Recompute it over the raw body, compare in constant time, and drop stale timestamps.
import crypto from 'crypto';
// Use the raw request body — parse only AFTER you verify.
export function verifyTicketItWebhook(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(',').map(p => p.split('='))
);
const signed = `${parts.t}.${rawBody}`;
const expected = crypto
.createHmac('sha256', secret)
.update(signed)
.digest('hex');
const ok = crypto.timingSafeEqual(
Buffer.from(parts.v1), Buffer.from(expected)
);
if (!ok) throw new Error('Invalid signature');
// Reject events older than 5 minutes (replay protection).
if (Date.now() / 1000 - Number(parts.t) > 300) {
throw new Error('Timestamp too old');
}
return JSON.parse(rawBody);
}Networks fail; your ticketing shouldn’t. Delivery is engineered so a flaky moment never costs you an order or double-issues a pass.
Non-2xx or a timeout triggers retries with exponential backoff for up to 24 hours, then the event lands in your dead-letter queue.
Every delivery carries a stable Webhook-Id. Store it and no-op on repeats — retries never double-book a ticket.
Ack fast: return 200 within 15s and do the heavy work off a queue. Slow endpoints are treated as failures.
Events for the same order or ticket are delivered in the order they occurred, so your state never goes backwards.
Security
Webhooks touch money and access. Ours are signed, encrypted in transit, timestamped against replays, and sent from IPs you can pin down.
Each request is signed with your endpoint secret over `timestamp.body`. Verify before you trust a byte.
We deliver exclusively to https:// endpoints with a valid certificate. No plaintext, ever.
The signed timestamp lets you reject anything older than five minutes and shut down replay attacks.
Deliveries come from a published, stable IP range you can allowlist at your edge.