Complete checkout and take payment with the Booking API

Last verified
Last verified Sep 23, 2026

Use this guide to turn a filled cart into a paid order. It covers the checks before checkout, legal consent, the hosted payment handoff, payment status and ticket delivery. The Booking API quickstart shows the shortest path; this guide explains each decision along the way.

Before you start

  • A cart with at least one item and its cart token.
  • The customer's name, email address and billing address.

Check the cart before checkout

Read the cart with request(id: <cart token>) and act on these fields:

FieldMeaning and action
checkoutPolicy.targetStateBOOKED completes with requestPaymentInitiate. RESERVED and REQUESTED cannot be completed through the Booking API yet; link to the offer in the hosted shop instead.
checkoutPolicy.compatible and conflictsWhen compatible is false, remove the lines in conflicts or check them out in a separate cart.
requiresShippingWhen true, the customer needs a shipping address in one of shop.allowedShippingCountries.
customFieldsCompletion.allRequiredCompleteWhen false, do not start checkout. The Booking API cannot collect these answers; send the customer to the hosted shop to complete this purchase.
requestItems.requiresWithdrawalEarlyStartConsentWhen true for any line, ask for the early-start consent described below.
checkoutHold.secondsRemainingTime left before an unpaid order is canceled. Keep it visible until the payment succeeds.

The hosted shop's address for an offer is its domain, the two-letter language code and the offer's shopSlug.slugTranslated, for example https://tickets.example.com/en/guided-tour.

Add the customer

Send the customer with requestUpdate, as shown in the quickstart. Mark the billing address with billing: true and, when the cart requires shipping, a shipping address with shipping: true. The same address can have both.

Load the documents customers must accept with legalDocuments(required: [true]). For each document, show a checkbox with checkboxLabelTranslated, or nameTranslated when no label is set. Show the document text from contentTranslated; when isUrl is true, the content is a link to the document.

Send one acceptance per required document with checkout. legalDocumentVersion must be the publishedAt value the customer saw. When a document changes between display and checkout, checkout fails with an error for legalDocumentAcceptances; reload the documents and ask the customer again.

Some services can only be sold when the customer agrees that the service starts before the legal withdrawal period ends. When a cart line has requiresWithdrawalEarlyStartConsent, show a consent checkbox with your consent text and send the result with checkout:

json
{
  "withdrawalEarlyStartConsent": {
    "accepted": true,
    "requestItemIds": ["<cart line id>"],
    "text": "I agree that the service starts before the withdrawal period ends."
  }
}

text must be the exact text the customer saw.

Complete checkout

Call requestPaymentInitiate with the cart token, the legal acceptances and, when needed, the early-start consent. The quickstart shows the full mutation.

ResultWhat to do
invoice with paymentLinkRedirect the customer to paymentLink.
invoice.paymentState is PAIDNothing is left to pay, for example when a voucher covers the full amount. The order is complete; do not redirect.
errorsShow messageTranslated. The cart is unchanged; fix the input and try again.

Hand payment to the hosted page

paymentLink opens the shop's payment page for this invoice, in the language of your Accept-Language header. The customer chooses a payment method and pays there. The page handles payment retries. It does not collect required booking custom fields: if customFieldsCompletion.allRequiredComplete is false, use the hosted shop to complete the purchase before starting checkout.

Keep the invoice token from invoice.accessToken. You need it to follow the payment.

Follow the payment

Read the invoice with the invoice token, for example with the PaymentStatus query from the quickstart. Poll every few seconds while the customer pays and stop when the payment is final.

FieldMeaning
paymentState: PAIDThe payment succeeded.
paymentState: REQUIRES_PAYMENTNo successful payment yet. payments lists the attempts and their state.
paymentIntentCreateErrorWhy a new payment cannot start: already paid, already in progress, or the order was canceled.
payments.providerInitializationFailedThe payment provider could not start this attempt. The customer can open paymentLink again.
request.state: CANCELEDThe order was canceled, for example because the checkout hold ended before payment. Start a new cart.

Rely on the invoice, not on the customer returning to a page: payments can succeed after the customer closes the browser.

Deliver tickets

After payment, request.shareablePublicTicketsUrl(format: PDF) returns a customer-facing link to the tickets page. Ticket activation can still be pending when this URL is present; the page makes the tickets available when activation finishes. Use format: PKPASSES for Apple Wallet passes when they are available.

Handle an expired checkout hold

When checkoutHold.secondsRemaining reaches zero before payment, KORONA Event cancels the order and releases its places. Call requestCancelExpiredCheckoutHold with the cart token as requestId, or the invoice token as invoiceToken, to release them immediately, then offer the customer a new cart.

Troubleshooting

ProblemWhat to check
Checkout fails with legalDocumentAcceptancesEvery required document is accepted with its current publishedAt as legalDocumentVersion.
A cart change fails with checkoutHoldThe hold ended and the order was canceled. Start a new cart.
A response is missing after checkoutRead the cart with its token before trying again. When state is BOOKED, checkout already succeeded; read the invoice token from invoices { accessToken }.

Ready to Get Started?

Book a free demo or reach out — we’d love to hear from you.