docs
webhooks
we post event payloads to your url. hmac-signed. auto-retried for 72 hours. 14 event types. same structure every time.
adding an endpoint
studio → webhooks → add endpoint. choose the events you care about, the version you're targeting, and save. we give you an endpoint secret (starts with whsec_) — copy it somewhere safe; we only show it once.
the event envelope
{
"id": "evt_01HYQ…",
"type": "order.created",
"created_at": "2026-05-17T14:02:11Z",
"api_version": "2026-04-01",
"livemode": true,
"data": { ... payload varies by type ... }
}
verifying the signature
header format: t=<unix_ts>,v1=<hex_signature>. the signed string is t + "." + request_body. reject if the timestamp is >5 minutes old — otherwise a replay is free.
import crypto from 'crypto'; app.post('/tired-hook', express.raw({ type: 'application/json' }), (req, res) => { const [t, v1] = req.headers['x-tired-signature'].split(',').map(x => x.split('=')[1]); const expected = crypto.createHmac('sha256', process.env.WHSEC) .update(t + '.' + req.body) .digest('hex'); if (!crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))) return res.sendStatus(400); if (Date.now() / 1000 - Number(t) > 300) return res.sendStatus(400); // safe to parse and act on req.body res.sendStatus(200); });
event types
| type | fires when |
|---|---|
order.created | a buyer completes checkout |
order.refunded | full or partial refund issued |
ticket.issued | a ticket is minted (transfer included) |
ticket.transferred | ticket moves between accounts |
ticket.scanned | valid scan at the door |
ticket.scan_rejected | duplicate, wrong event, expired |
event.published | event flips to on_sale |
event.cancelled | organizer cancels |
event.rescheduled | date/time changes |
payout.scheduled | a payout is queued for a date |
payout.paid | money has settled in your bank |
payout.failed | ach reject, closed account, etc. |
attendee.updated | name / email change on a ticket |
waitlist.joined | someone adds themselves to a waitlist |
retries & backoff
non-2xx responses are retried on an exponential backoff: 30s, 5m, 30m, 2h, 8h, 24h, 72h. after 72 hours we stop; the event stays in your dashboard for 30 days and you can replay it manually.
ordering
webhooks are not ordered. a ticket.scanned can arrive before the order.created in rare cases. always use created_at and idempotency on your side.