Receive webhooks
Register an endpoint, verify the signature on every delivery and test the whole path from a laptop through a tunnel.
You have a URL that should hear about orders, customers, events and tickets.
This is how to register it, prove each delivery came from Ticketlayer, and
try it before an order exists. What arrives, and in what shape, is in
Webhooks. A receiver that keeps a copy of your
events current from event.updated, event.deleted and the listing webhooks
is in Syncing events.
Register an endpoint#
With an organisation API key or a staff token:
curl -X POST https://api.ticketlayer.com/v1/webhook-endpoints \
-H "Authorization: Bearer tlak_..." \
-H "X-Ticketlayer-Org: your-org-slug" \
-H "Content-Type: application/json" \
-d '{
"accountId": "acc_01J...",
"url": "https://hooks.example.com/ticketlayer",
"description": "Orders to the CRM",
"eventTypes": ["order.confirmed", "ticket.issued"]
}'The response carries the endpoint and, once only, its secret. Omit
eventTypes to receive every type. Endpoints are per account (the
organisation's sub-unit that owns the events); the same URL can be registered
for several accounts.
Operations: create,
list,
get,
update (URL,
description, event types, enabled/disabled),
delete and
test, which sends a
signed sample delivery.
Verify the signature#
The signing secret is returned once when the endpoint is created. The
signature is an HMAC-SHA256 of "<t>.<raw body>" with that secret, hex
encoded; reject deliveries whose t is more than five minutes from now.
import crypto from 'node:crypto';
export function verifyTicketlayerSignature(rawBody: string, header: string, secret: string, toleranceSeconds = 300): boolean {
const parts = Object.fromEntries(header.split(',').map((kv) => kv.split('=').map((s) => s.trim())));
const t = Number(parts.t);
const v1 = parts.v1;
if (!t || !v1) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - t) > toleranceSeconds) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const a = Buffer.from(v1, 'hex');
const b = Buffer.from(expected, 'hex');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Compute the HMAC over the raw request body, before any JSON parsing or re-serialisation.
app.post('/webhooks/ticketlayer', express.raw({ type: 'application/json' }), (req, res) => {
const raw = req.body.toString('utf8');
if (!verifyTicketlayerSignature(raw, req.get('Ticketlayer-Signature') ?? '', process.env.TL_WEBHOOK_SECRET!)) {
return res.status(400).send('bad signature');
}
const envelope = JSON.parse(raw);
if (envelope.type === 'order.confirmed') {
// envelope.data is the Order payload above
}
res.sendStatus(204);
});Local development#
Run a tunnel (for example ngrok http 3000) and register the tunnel URL as
the endpoint. The test operation above is the quickest way to see a
delivery without buying a ticket.