> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orriven.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Headless checkout

> Sell tickets on your own website: place an order from your backend, hand the buyer a Stripe payment page, and build their account area from the API.

The checkout endpoints let an event site you build yourself sell exactly what the Orriven event page sells — tickets, add-ons, bundles, redemption codes, free and paid — with the platform doing what it already does underneath: capacity holds, settlement by webhook, fulfilment into registrations and perks.

<Warning>
  The shape is always **buyer → your frontend → your backend (holding the API key) → Orriven**. The key can read and write the whole Business Unit, so it never ships to a browser — and your backend is responsible for only showing a signed-in buyer *their own* orders, registrations and check-in codes. Buyer accounts (email OTP, sessions) are yours to build; Orriven identifies buyers by email.
</Warning>

## The flow

<Steps>
  <Step title="Show what's on sale">
    Render your event page from `GET …/ticket-types` (names, prices, sale windows, statuses), `…/availability` (live remaining numbers), `…/addons`, `…/bundles`, `…/venues`, `…/sessions` (the agenda) and `…/speakers`. When the buyer enters a redemption code, preview it with `GET …/redemption-codes/lookup?code=` — the unlocked ticket, its perks and the discounted price, before anything is placed. Your cart is your own frontend state — Orriven first hears about it at settlement.
  </Step>

  <Step title="Place the order">
    ```bash theme={null}
    curl -s -X POST "$BASE/v1/events/$EVENT/orders" \
      -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
      -d '{
        "email": "ada@example.com",
        "name": "Ada",
        "items": [
          { "itemType": "registration_type", "itemId": "…", "quantity": 1 },
          { "itemType": "addon",             "itemId": "…", "quantity": 2 }
        ],
        "redemptionCode": "VIP2026"
      }'
    ```

    `201` answers the pending order — it **holds its places for 15 minutes**. A buyer already registered in the event answers `200` with `alreadyRegistered: true` instead: acknowledged, never double-booked. A full ticket answers `412`.
  </Step>

  <Step title="Confirm — free settles, paid gets a Stripe page">
    ```bash theme={null}
    curl -s -X POST "$BASE/v1/events/$EVENT/orders/$ORDER_NUMBER/confirm" \
      -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
      -d '{
        "successUrl": "https://tickets.your-site.com/thanks?order='"$ORDER_NUMBER"'",
        "cancelUrl":  "https://tickets.your-site.com/checkout?order='"$ORDER_NUMBER"'"
      }'
    ```

    * A **free order** (total 0) settles on the spot: `{"status":"paid", "registration":{…}, "created":true}` — done, no Stripe involved.
    * A **paid order** answers `{"status":"awaiting_payment", "paymentUrl":"https://checkout.stripe.com/…"}`. Redirect the buyer there; `successUrl`/`cancelUrl` are where Stripe sends them back on **your** site (required for paid orders, https only — plain http is allowed for localhost development).
  </Step>

  <Step title="Wait for settlement">
    Payment settles through the platform's Stripe webhook, not through your call. When the buyer lands on your `successUrl`, poll `GET …/orders/{orderId}` (or find it in `GET …/orders`) until `status` is `paid` and `registrationId` is set — that is the moment the seat, the perks and the check-in code exist.
  </Step>

  <Step title="Build the buyer's account area">
    Everything a "my tickets" page needs is readable by your backend, filtered to the signed-in buyer's email:

    * **Registrations** (`GET …/registrations`) — status, ticket type, and the **`checkinCode`** to render as their entry QR;
    * **Perks** (`GET …/registrations/{id}/perks`) — the effective union of ticket ∪ code ∪ individual grants;
    * **Check-ins** (`GET …/checkins?registrationId=`) — their usage history;
    * **Orders** — their purchase history.
  </Step>
</Steps>

## Rules that bite

* **One order, one seat.** The registration model is one person per event, so an order carries at most one ticket seat (`412` otherwise) — a buyer purchasing for friends places one order per person, each with that person's email. Add-ons may repeat freely.
* **The order number is the order's credential** — keep it server-side with the same care as a session token for that buyer.
* **A pending order holds capacity for 15 minutes**; confirming a paid order stretches the hold to 35. Cancel (`POST …/orders/{orderNumber}/cancel`) to release it early; cancelling twice acknowledges.
* **Never call confirm for a paid order without `successUrl`/`cancelUrl`** — it answers `400` before anything happens.
* **If payments are not configured** for the Business Unit (no Stripe onboarding), placing a paid order answers `412`; the free path works regardless.
* **Automations fire the same as the platform's own checkout** — order placed/expired/cancelled and registration triggers all behave identically.

## Related

<CardGroup cols={2}>
  <Card title="Endpoint reference" icon="book" href="/en/developers/api-reference">
    Exact schemas for place, confirm and cancel.
  </Card>

  <Card title="Orders (console view)" icon="receipt" href="/en/ticketing/orders">
    How the same orders look to the organizer.
  </Card>
</CardGroup>
