Complete Booking Flow

The ONDC booking flow is strictly sequential. Each step depends on values returned by the previous step. This guide shows the complete path from browsing to ticket confirmation.

Critical: Never skip or reorder steps. The transaction_id obtained in SELECT must remain unchanged across SELECT → INIT → CREATE ORDER. It uniquely ties together your entire booking session.

Phase 1 — Setup (One-time)

These APIs are typically called once on app load.

  • 1
    Get Token POST /auth/token
    Authenticate with clientId + clientSecret → Save access_token as your bearer token. Valid for 30 min.
  • 2
    Get the user's city. Save city.code (e.g., std:011) as cityCode — used in all ONDC transaction APIs.
  • 3
    Check enabled payment gateways for the platform/payment-options UI. Load city banners for the home screen.

Phase 2 — Browse & Select Experience

  • 4
    Show paginated experience listings. Use filters (category, city, price, etc.) or autocomplete search.
  • 5
    Get Single Item GET /item/slug/:id
    Full item detail page. Save these critical values:
    data.key (MongoDB ObjectId) → needed for timeslots
    provider.bppId, provider.bppUri, provider.cityCode, provider.id → needed for all ONDC transactions
    ticketsByPackage[n].packageId → needed for tickets API
    ticketsByPackage[n].tickets[n].key → needed for timeslots
    bookableDates → restrict date picker to only these dates

Phase 3 — Booking Transaction

This is the core ONDC flow. All steps are mandatory and must be executed in order.

  • 6
    Get Timeslots GET /item/:itemKey/timeslots
    After user picks a date from bookableDates. Use item's MongoDB key (not UUID). Returns timeslot.id, timeRangeStart, timeRangeEnd — all needed for SELECT.
  • 7
    Get Tickets GET /item/:packageId/tickets
    Pass packageId and timeStamp (the timeslot start). Returns live ticket.id (composite format — use as-is), parentItemId, minPeople/maxPeople, fulfillmentId.
  • 8
    SELECT POST /select
    Send items + fulfillment to the seller (BPP). Returns confirmed price quote and optional visitor forms. SAVE transaction_id — must be used unchanged in all subsequent steps.
  • 8b
    Form Submit (CONDITIONAL)
    Only if SELECT returns a non-empty forms[] array. Submit visitor details (name, age, email). Submit once per ticket if booking multiple. Proceed to INIT only after all forms are submitted.
  • 9
    INIT POST /init
    Add billing details (name, email, mobile). Use the same items[] + fulfillments[] from SELECT unchanged. Returns payments[0].id (paymentId) needed for Create Order.
  • 10
    Create Order POST /orders
    Creates the payment order. Returns orderId and rpOrderId. Use rpOrderId with the Razorpay SDK to launch the checkout modal.
  • P
    Payment (Razorpay SDK)
    User completes payment via Razorpay checkout. On success, the Razorpay webhook automatically triggers CONFIRM. You do NOT call CONFIRM manually.
  • 11
    Poll Order Status GET /orders/:orderId
    Poll every 2–3 seconds until orderStatus = SUCCESS AND bookingStatus = COMPLETED. Maximum 30 attempts. Show "Processing…" after 60s if still pending.

Phase 4 — Post Booking

  • 12
    Show the user's ticket with QR code. Render qrCodeDetails.token as a QR image. Check qrCodeDetails.status (UNCLAIMED / CLAIMED) for scan state.
  • 13
    Create Review (optional)
    Allow review only when bookingStatus = COMPLETED and no existing review. Requires bookingId from the tickets response.
  • 14
    Register Issue (if problem)
    If the user has a problem (QR not working, experience not delivered), raise an ONDC IGM issue. Use transactionId from the booking.

Field Traceability

Every critical value and where it comes from / where it's used:

ValueComes FromUsed In
bearer tokenPOST /auth/token → access_tokenAuthorization header on all APIs
cityCodeGET /city → code or item → provider.cityCodeSELECT, INIT → cityCode
bppIdGET /item/slug → provider.bppIdSELECT, INIT → bppId
bppUriGET /item/slug → provider.bppUriSELECT, INIT → bppUri
providerIdGET /item/slug → provider.idSELECT, INIT → providerId
itemKey (ObjectId)GET /item/slug → data.keyGET /timeslots → path param
ticketKey (ObjectId)GET /item/slug → ticketsByPackage[n].tickets[n].keyGET /timeslots → ticketIds query
timeslot.idGET /timeslots → data[n].idSELECT → fulfillments[n].id + items[n].fulfillment_ids
ticket.id (composite)GET /tickets → data[n].idSELECT → items[n].id (use whole string)
ticket.parentItemIdGET /tickets → data[n].parentItemIdSELECT → items[n].parent_item_id
transaction_idSELECT → onSelect.context.transaction_idINIT, CREATE ORDER → transactionId
paymentIdINIT → message.order.payments[0].idCREATE ORDER → paymentId
orderIdCREATE ORDER → data.orderIdGET /orders/:orderId (polling)
rpOrderIdCREATE ORDER → data.rpOrderIdRazorpay SDK → order_id
bookingIdGET /user/tickets → formattedTickets[n].bookingIdCREATE REVIEW → bookingId
cityId (ObjectId)GET /city → data.cities[n].idGET /hero-sections/city/:id