tired/api/docs/webhooks

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.

node.js · express · verify
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

typefires when
order.createda buyer completes checkout
order.refundedfull or partial refund issued
ticket.issueda ticket is minted (transfer included)
ticket.transferredticket moves between accounts
ticket.scannedvalid scan at the door
ticket.scan_rejectedduplicate, wrong event, expired
event.publishedevent flips to on_sale
event.cancelledorganizer cancels
event.rescheduleddate/time changes
payout.scheduleda payout is queued for a date
payout.paidmoney has settled in your bank
payout.failedach reject, closed account, etc.
attendee.updatedname / email change on a ticket
waitlist.joinedsomeone 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.