Guides
Webhooks
Receive signed events when orders complete, attendees arrive and more.
Add an endpoint under Settings → Webhooks, choose the events, and copy the signing secret. Deliveries are JSON POST requests with these headers:
| Header | Meaning |
|---|---|
| EventOS-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256 of "t.body" with your secret> |
| EventOS-Event | The event type, e.g. order.completed |
| EventOS-Delivery | Unique id of this delivery; the same event replayed keeps its payload id |
Events
| Type | When |
|---|---|
| order.completed | A paid order is confirmed by the provider, or a free registration completes. Payload: order, attendees |
| order.cancelled | An organiser cancelled a free order |
| order.refunded | A paid order was refunded in full |
| attendee.created | A named ticket was issued (checkout, organiser, API) |
| attendee.checked_in | First entry scan |
| exhibitor.approved | An exhibitor profile was approved |
| event.published | An event went live |
| ping | Test from the Webhooks page |
Verify the signature
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret, rawBody, header) {
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (!parts.t || age > 300) return false;
const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
return parts.v1?.length === expected.length && timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}import hmac, hashlib, time
def verify(secret: str, raw_body: bytes, header: str) -> bool:
parts = dict(kv.split("=", 1) for kv in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(parts.get("v1", ""), expected)Delivery and retries
Respond with any 2xx within 10 seconds. Anything else is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours, then marked as given up. After twenty consecutive failures the endpoint is paused; re-enable it from the Webhooks page once fixed. Every delivery, its response and its attempts are shown there, and any delivery can be replayed.
Handle events idempotently: use the payload id to ignore duplicates. Replays and retries deliver the same id.