The API door: scoped keys, a sandbox, signed webhooks
Most ticketing APIs are announced long before they exist. This one is small and finished rather than large and promised: two read endpoints, keys that carry only the permissions you give them, a sandbox that cannot touch real data, and webhooks you can prove came from us.
What it does today, exactly
Two endpoints under /api/v1. GET /events returns your own events with tier prices and how many have sold. GET /tickets returns tickets for them, filterable by event. Both are GETs. There is no write API — you cannot create an event, place an order or scan a ticket through it — and there are no SDKs. When those exist they will be on the developers page and not before.
The sandbox is a different key, not a setting
A tr_test_ key reads fixtures and touches nothing real. A tr_live_ key reads your account. They are separate credentials rather than a mode on one, because a request must not be able to reach live data by leaving something out — which is exactly how a first integration attempt ends up scanning tickets at a real door.
The fixtures are deliberately awkward: a sold-out tier, a ticket already redeemed, a ticket refunded. A fresh live account has none of those, so an integration built against one breaks on the first real evening. Sandbox addresses sit on the reserved .invalid domain, so nothing sent to them can leave your own machine.
Keys are scoped, and attendee data has its own scope
A key carries only what you grant it. Names and email addresses sit behind attendees:read, separate from tickets:read — so a key pasted into a reporting dashboard can count ticket sales without ever being able to export a mailing list. That distinction is what makes handing a key to somebody else a small decision rather than a large one.
We store only a hash of your key, never the key. It is shown once, when you create it, and after that we genuinely cannot show it again — a lost key is replaced rather than recovered. That is not inconvenience for its own sake: a database of plaintext API keys is a database of everybody’s ticket data, and the owners could not tell it had leaked, because the keys keep working.
Every webhook is signed
Each delivery carries a TicketRoyality-Signature header: an HMAC-SHA256 over the timestamp and the raw body, using your endpoint’s own secret. Verify it before parsing, compare in constant time, and reject anything where the timestamp is more than five minutes old.
Two details cause most integration bugs here. Sign the raw bytes — re-serialising the JSON first changes them and every signature stops matching. And check the timestamp, not only the signature: a genuine delivery somebody captured stays valid forever unless the time it was sent is part of what was signed.
Failure is a retry, not a loss
A delivery your server does not accept is retried with an increasing delay across five attempts. After the last one it is marked failed and stays in your delivery log with the status code and the error. Nothing disappears quietly — silence is the one outcome that would make a webhook system untrustworthy.
Deliveries are queued rather than sent inline, so a ticket is issued whether or not your server is up. Nothing in the path that takes somebody’s money waits on somebody else’s infrastructure.
What we do not send
There is no ticket.issued event, deliberately. Issuance runs in a separate deployable from the app, so an event fired from here would be a guess about something that had not happened yet — and a webhook arriving before the tickets exist is worse than no webhook. order.completed is the honest one: the money arrived, and the tickets follow. We also send ticket.redeemed, ticket.refunded and donation.received.
Your endpoint must be public
Endpoints must be https, and private addresses are refused — localhost, 10.x, 192.168.x, and the cloud metadata address. A URL we POST to on a schedule from inside our own network is otherwise a request-forgery tool aimed at us, and you would be the one paying for it.
Common questions
- Does TicketRoyality have an API?
- Yes, a read API: GET /api/v1/events and GET /api/v1/tickets, with scoped keys, a sandbox and HMAC-signed webhooks. There is no write API yet — you cannot create events, place orders or scan tickets through it.
- How do I verify a TicketRoyality webhook?
- Compute an HMAC-SHA256 over the timestamp and the raw request body — not a re-serialised copy — with your endpoint secret, and compare it to the v1 value in the signature header in constant time. Reject anything where the timestamp is more than five minutes old.
- Can I test without touching real data?
- Yes. A tr_test_ key reads fixture data and cannot reach your live account at all, because live and test are different keys rather than a mode on one key.
Keep reading
- The messages that must arrive, and the ones you can turn offOver a hundred defined events across email, in-app, push and WhatsApp — with a hard line between a message you can unsubscribe from and one you cannot.The platform underneath
- Where your tickets live, and how to send one to a friendEvery ticket in one place, transferable to someone else properly — which is the difference between giving a friend a ticket and forwarding them a screenshot.Buying and going
- Refunds, chargebacks and what happens when it goes wrongA stated policy before purchase, cancellation handled automatically, and a dispute process that does not depend on who complains loudest.Trust and safety
- Only humans get in: what actually stops a scriptProof-of-work that costs a bot more than it costs you, six signals scored on the server, and a deliberate decision not to penalise anyone for using a VPN.Trust and safety
Written and edited by people. Nothing on this blog is generated and published automatically — see our editorial approach.