Developers

Ticketz API

Connect your own website, till, kiosk or door system to Ticketz. List your events, sell or issue tickets, scan them at the door and hear about every order as it happens.

API access is switched on per account. Once it is on, create a key under Account, API and webhooks in your dashboard. The key is shown once, so keep it somewhere safe, and only ever use it from your own server, never in a web page.

Basics

  • Base address: https://ticketz.uk/api/v1. Always this address, never your own yourname.ticketz.uk event page address.
  • Send the key on every call: Authorization: Bearer tz_live_...
  • Send and receive JSON. Dates are YYYY-MM-DD, times are HH:MM, UK time. Instants are ISO timestamps. Money is in pence.
  • Up to 600 calls every 5 minutes per key. Errors come back as { "error": "plain English message", "code": "..." }.
  • An order is identified by its reference, for example TZ-AB12CD. A ticket by its reference TK-AB12CDE or its code (the long string in the QR).
  • An event can be referred to by its id, its slug or the externalRef you set on it.
curl https://ticketz.uk/api/v1/events \
  -H "Authorization: Bearer tz_live_..."

Your website

Show your events on your own site and send people to the event page to pay, or issue free tickets from your own form.

CallWhat it does
GET/venueYour organiser summary: name, event page address, plan, whether card payments are set up, the monthly allowance, and the next event.
GET/eventsLive, upcoming events with their ticket types and what is left. Add ?all=1 for drafts and past events too.
GET/events/:idOne event with availability. Each ticket type has price (pence), available (null means unlimited), onSale and a reason when it is not.
GET/events/:id/availabilityJust the live numbers, for a picker that polls.
POST/ordersIssue tickets from your own system. Needs event, items ({ "ticketTypeId": quantity }), name, email; optional phone, code, holders, answers, externalRef, by, note. Free baskets go through as they are. A basket with a price needs "paid": true and a note saying how you took the money; nothing is charged through us. The tickets are emailed to the buyer straight away. Counts towards your monthly allowance.
GET/orders/TZ-...One order with its tickets, each with its code and QR url.
GET/orders?contact=A buyer's orders, by the email or phone number they used.
POST/orders/TZ-.../resendEmail the tickets again.
POST /api/v1/orders
{ "event": "quiz-night", "items": { "12": 2 },
  "name": "Jo Bloggs", "email": "[email protected]", "phone": "07700 900123",
  "paid": true, "note": "Cash at the bar", "by": "Karen" }

201 { "order": { "ref": "TZ-AB12CD", "status": "paid", "quantity": 2, "total": 2000,
      "tickets": [ { "ref": "TK-XY12ABC", "type": "Standard", "status": "valid", "code": "...", "url": "https://.../t/..." }, ... ] },
      "manageUrl": "https://..." }

To sell paid tickets by card, send people to the event's url. Card payments always go through the event page and your own Stripe account.

Your till or door system

CallWhat it does
GET/ordersFilters: event, status (paid, free, pending, cancelled, refunded; comma separated), since (orders changed after an ISO timestamp), limit up to 500, tickets=0 to leave the tickets out.
POST/orders/TZ-.../cancel{ "refund": "full" | pence | 0, "reason": "...", "ticketRefs": ["TK-..."] }. Cancels some or all tickets. A refund goes back to the card through your Stripe account (only for orders paid by card through us). The buyer is emailed.
POST/tickets/:code/checkinScan a ticket in. :code is the QR code, the whole QR link, or the ticket reference. Send { "event": "quiz-night", "by": "Main door", "device": "Scanner 1" }. The answer has result: ok, reentry, used (with when and by whom), cancelled, wrong_event or not_found. Re-entry follows your door setting; "force": true lets someone back in regardless.
POST/tickets/:code/undoTake a check in back.
GET/tickets/:codeLook one ticket up without scanning it.
GET/events/:id/doorThe door count (inside, total), the last 30 scans, and every live ticket with its code, so a door screen can keep its own list for when the signal drops.
POST/heartbeatCall every minute while your screen is on, with { "label": "Door screen" }. The dashboard shows it as online. { "off": true } when it is switched off.

Add "by": "Karen" to any action and the order history shows who did it.

Moving over from another system

POST /import takes up to 1000 orders a call. Give every order your own id as externalRef, so running it again never makes a copy; only a cancellation is brought up to date. Imported orders send no emails or texts, are never charged, and get real ticket codes so the door app can scan or find them. The answer tells you the Ticketz reference for each of your ids.

{ "event": "quiz-night",
  "orders": [ { "externalRef": "old-381", "name": "Rita", "email": "[email protected]", "phone": "07700 900666",
                "tickets": [ { "type": "Standard", "quantity": 2, "price": 1000 } ],
                "paid": true, "total": 2000, "createdAt": "2026-08-01T10:00:00Z" },
              { "externalRef": "old-382", "name": "Sam", "email": "[email protected]",
                "items": { "12": 1 }, "status": "cancelled" } ] }

200 { "imported": 2, "updated": 0, "unchanged": 0, "skipped": 0, "refs": { "old-381": "TZ-...", "old-382": "TZ-..." }, "problems": [] }

tickets names the ticket type (or gives its id) and can set a price; items uses your ticket type ids. checkedIn: true (or a number) marks tickets as already scanned in.

Webhooks

Save an https address under Account, API and webhooks and tick the events you want: order.paid (tickets issued, paid or free), order.cancelled, order.refunded, ticket.checked_in, event.updated. We POST JSON within a second or two, and try twice more if your server does not answer. POST /webhooks/test sends a test event to your address and waits for the result.

{ "id": "evt_...", "type": "order.paid", "createdAt": "...",
  "organiser": { "id": 4, "name": "...", "slug": "..." },
  "data": { "ref": "TZ-AB12CD", "status": "paid", "event": { "id": 7, "slug": "quiz-night", "name": "Quiz night", "date": "2026-10-03", "start": "19:30" },
            "name": "Jo Bloggs", "email": "[email protected]", "quantity": 2, "total": 2000,
            "tickets": [ { "ref": "TK-XY12ABC", "type": "Standard", "status": "valid", "code": "..." } ], ... } }

For ticket.checked_in the data is the order plus ticket, the one just scanned. For event.updated the data is the event.

Every webhook carries a Ticketz-Signature: t=TIMESTAMP,v1=HMAC header. Check it before trusting the body: work out HMAC-SHA256 of TIMESTAMP + "." + raw body with your signing secret, compare it in constant time, and reject anything older than five minutes.

const [t, v1] = header.split(',').map((p) => p.split('=')[1]);
const want = crypto.createHmac('sha256', SECRET).update(`${t}.${rawBody}`).digest('hex');
const ok = want.length === v1.length
  && crypto.timingSafeEqual(Buffer.from(want), Buffer.from(v1))
  && Math.abs(Date.now() / 1000 - Number(t)) < 300;

Webhooks can arrive late or not at all if your server is down, so also poll GET /orders?since= every minute or so and you will never miss a change.

Stuck? Send us a help request and we will help.