Order status
/v1/events/{slug}/tickets/orders/{checkoutToken}Everything a confirmation page needs after the purchase it just made: where the order is, its lines and totals read back from the server, any personal promo codes the buyer earned on the organiser's other events, and the buyer's own Ambassador code and reward ladder where the organiser runs one.
After a card payment the order stays reserved until the payment is confirmed server-side, usually within a minute. Poll every few seconds until orderStatus reads completed, then tell the buyer their tickets are on their way; they arrive by email from Grofomo. Give up after a couple of minutes with a softer message: the confirmation email still arrives, and the buyer's card has not been charged twice.
Print the receipt from order rather than from what your page held in memory: it survives a payment redirect, and shortId is the reference printed on the tickets.
codes is empty until the order is completed, and stays empty when the cart earned nothing. When a code is present, offer a link to the target event's ticket page with ?promo=<code> so it is pre-filled; the same code is in the confirmation email. ambassador is null until the order completes, and stays null when the organiser has no programme or has automatic enrolment off.
404 not_found for no visible reason.Request
curl -sS \
-H 'X-Grofomo-Key: pk_live_YOUR_PUBLISHABLE_KEY' \
'https://api.grofomo.com/v1/events/summer-fest/tickets/orders/YzNhMWU1ZDctOWIyZi00ZTZhLThjMGQtMmY0YjZhOGMwZTEyOjE3ODk2NDM2MDAwMDA6dGt0LWNoZWNrb3V0.ZXhhbXBsZS1zaWduYXR1cmU?surface=web'Path parameters
slugstringrequired- The event's URL slug. Globally unique. It can change if the organiser renames the event, but an old slug keeps reaching the same event, so a stored one never breaks. Example: `summer-fest`.
checkoutTokenstringrequired- The order's `checkoutToken` from the reserve response. It authorises this checkout only and expires a day after the reserve; keep it out of logs and analytics. Example: `YzNhMWU1ZDctOWIyZi00ZTZhLThjMGQtMmY0YjZhOGMwZTEyOjE3ODk2NDM2MDAwMDA6dGt0LWNoZWNrb3V0.ZXhhbXBsZS1zaWduYXR1cmU`.
Query parameters
surface'web'required- Must be `web` for a website. Omit it and you get the mobile-app surface, which is gated on a different visibility setting — so a perfectly live event can return 404. Example: `web`.
Response
{
"apiVersion": 1,
"data": {
"orderStatus": "completed",
"codes": [
{
"offerId": "e7c5a3b1-2d4f-4a6c-8e0b-3f5d7a9c1e24",
"name": "Winter warmer",
"targetEventName": "Winter Fest",
"targetEventSlug": "winter-fest",
"discountType": "percentage",
"discountValue": 1500,
"applicableReleaseIds": [],
"code": "SF-WINTER-7K3M",
"expiresAt": "2026-12-01T00:00:00.000Z"
}
],
"order": {
"shortId": "T-3F9A2C",
"email": "alex@example.com",
"currency": "GBP",
"lines": [
{
"label": "Weekend camping ticket",
"quantity": 2,
"unitPricePennies": 9900,
"lineTotalPennies": 19800
}
],
"subtotalPennies": 22000,
"discountPennies": 2200,
"quantityDiscountName": null,
"bookingFeePennies": 1535,
"totalPennies": 21335,
"paidTodayPennies": 21335,
"paymentPlan": null
},
"ambassador": {
"code": "SF-ALEX-9K2Q",
"shareUrl": "https://grfm.to/e/summer-fest/tickets?aff=SF-ALEX-9K2Q",
"qualifyingTickets": 0,
"tiers": [
{
"thresholdTickets": 2,
"tierName": null,
"title": "5 free drinks",
"description": null,
"fulfilmentInstructions": "At the festival bar"
},
{
"thresholdTickets": 4,
"tierName": "VIP",
"title": "VIP bar access",
"description": "All day, for you and a plus one",
"fulfilmentInstructions": null
}
]
}
}
}orderStatusstring- Where the order is. `reserved` while the hold is live (including after a card payment, until the payment is confirmed server-side, usually within a minute); `completed` once tickets are issued; `expired` when the hold ran out unpaid; `cancelled`, `refunded` or `partially_refunded` after the fact. Poll this after payment until it reads `completed`.
codesobject[]- Personal promo codes this order earned on other events. Empty until the order is `completed`, and empty forever when the cart earned none.
codes[].offerIduuid- The offer the code came from.
codes[].namestring- The organiser's label for the offer.
codes[].targetEventNamestring- The event the code is spent on.
codes[].targetEventSlugstring- Slug of the target event. Link to its ticket page with `?promo=<code>` to pre-fill the code.
codes[].discountType"percentage" | "fixed"- How the code discounts: a percentage of the tier price, or a fixed amount off.
codes[].discountValuenumber- Basis points for percentage (1000 = 10%), minor units for fixed.
codes[].applicableReleaseIdsuuid[]- Release ids on the target event the code discounts. Empty means every tier.
codes[].codestring- The personal promo code. It is also emailed to the buyer.
codes[].expiresAtstring | null- ISO instant the code stops working, or null when it never expires.
orderobjectmay be absent- The order's lines and totals, read back from the server: what a confirmation page should print rather than what it held in memory across a payment redirect.
order.shortIdstring- The human order reference printed on the tickets, e.g. "T-3F9A2C".
order.emailstring | nullorder.currencystringorder.linesobject[]order.lines[].labelstring- The ticket name as sold, e.g. "Adult".
order.lines[].quantitynumberorder.lines[].unitPricePenniesnumber- Price per unit after any automatic discount, before the booking fee.
order.lines[].lineTotalPenniesnumberorder.subtotalPenniesnumberorder.discountPenniesnumberorder.quantityDiscountNamestring | nullorder.bookingFeePenniesnumberorder.totalPenniesnumberorder.paidTodayPenniesnumberorder.paymentPlanobject | nullorder.paymentPlan.iduuid- The payment plan offer the order is on.
order.paymentPlan.namestring- The organiser's name for the plan, to show the buyer.
order.paymentPlan.depositPenniesnumber- What the payment step charges now: the deposit share of the face value plus the whole booking fee, in minor units.
order.paymentPlan.balancePenniesnumber- The remaining face value, collected over the instalments, in minor units.
order.paymentPlan.payByAtstring- ISO instant the plan must be fully paid by. The last instalment lands on or before it.
order.paymentPlan.installmentsobject[]- The exact schedule the saved card is charged on once the deposit is paid. Show it before the buyer pays.
order.paymentPlan.installments[].seqnumber- 1-based position in the schedule.
order.paymentPlan.installments[].amountPenniesnumber- Charged on `dueAt`, in minor units.
order.paymentPlan.installments[].dueAtstring- ISO instant the instalment is taken.
ambassadorobject | nullmay be absent- The buyer's own Ambassador share code and reward ladder, once the order completed and the organiser runs a programme with automatic enrolment. Null otherwise.
ambassador.codestringambassador.shareUrlstringambassador.qualifyingTicketsnumberambassador.tiersobject[]ambassador.tiers[].thresholdTicketsnumber- Tickets that must sell through the code before this reward is earned.
ambassador.tiers[].tierNamestring | nullambassador.tiers[].titlestring- What they get, e.g. "5 free drinks".
ambassador.tiers[].descriptionstring | nullambassador.tiers[].fulfilmentInstructionsstring | null
Errors
unauthorizedHTTP 401- No key was sent and the organiser requires one — or the key is invalid, revoked, belongs to another organisation, or is scoped to a different event.
origin_not_allowedHTTP 403- Your site's domain is not on the organiser's allowed list. Browser calls only: a server sends no Origin header and is unaffected.
not_foundHTTP 404- No order for that `checkoutToken` on this event (or the token has expired), or no event with that slug on this surface.
Notes
- Rate limited to roughly 60 requests per minute per caller. Guidance, not a contract — see caching and rate limits.
Cache-Control: no-store