Confirm checkout

Supports guest users.

Confirms a checkout by providing customer details and a payment return URL.
Returns a PSP payment URL to redirect the customer to. After payment the PSP
redirects back to return_url. Supports idempotency via the
Idempotency-Key header.

Events fired

EventTriggerDescription
checkouts.CheckoutFulfillmentRegisteredsyncFired when the checkout is confirmed and a fulfillment workflow has been registered.
Recent Requests
Log in to see full request history
TimeStatusUser Agent
Retrieving recent requests…
LoadingLoading…
📘

Eventscheckouts.CheckoutFulfillmentRegistered

Why this endpoint exists

This is the commitment point. Three things happen, in order, and all three have to succeed:

  1. The customer's details are attached to the checkout — this is where the ticket recipient is finally known.
  2. The fulfillment workflow starts, so tickets will be issued once payment clears.
  3. A payment is created at the provider, and its hosted payment page URL comes back.

After this call the customer is going to be charged. It is the one endpoint where getting idempotency right actually protects someone's money.

Request

curl -X POST "https://api.acc.funtrips.io/v2/checkouts/$CHECKOUT_ID/confirm" \
  -H "x-campaign-id: $CAMPAIGN_ID" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "first_name": "Sanne",
    "last_name": "de Vries",
    "return_url": "https://tickets.your-brand.example/order/complete",
    "newsletters": ["monthly-deals"]
  }'
FieldTypeRequiredNotes
emailemailYesWhere the tickets are sent. See below
first_namestringYes
last_namestringYes
return_urluriYesWhere the provider sends the customer after payment
newslettersstring[]NoNewsletter ids the customer opted into
streetstringConditionalRequired when requires_shipping_address is true
house_numberstringConditionalRequired when requires_shipping_address is true
house_number_suffixstringNoe.g. A, bis
postal_codestringConditionalRequired when requires_shipping_address is true
citystringConditionalRequired when requires_shipping_address is true
country_codestringConditionalISO 3166-1 alpha-2, e.g. NL. Required when requires_shipping_address is true

email is the delivery address, not the account

Tickets and the order confirmation go to this address — the one the customer typed on your form. For a signed-in customer that may not be the address on their account, and when it differs, this one wins.

That is intentional: people buy tickets for other people, and buy from a work account for a personal trip. Pre-fill the field from the session when you have one, but let them change it, and show them what they typed before they commit. A typo here means tickets delivered to nobody.

The shipping address is conditional and strict

Read requires_shipping_address from the Create response. When it is true the basket contains a physical product, and the address is validated as a set: street, house_number, postal_code, city and country_code must all be present. A partial address is rejected with checkout-shipping-address-required.

When it is false, address fields are ignored — you can send them or omit them.

return_url is a return, not a result

The provider redirects the customer here after they finish, whatever the outcome — paid, cancelled, or abandoned mid-flow. It carries no trustworthy status.

Treat landing on return_url as "the customer came back", then ask the platform what actually happened via GET /checkouts/{checkout_id}. Never grant tickets or show a success page based on the redirect alone.

Response

200 OK:

{
  "data": {
    "order_id": "5e2a7c91-3b6d-4f82-a17c-9d4e6b1f3a25",
    "order_display_id": "FT-2026-004471",
    "payment_url": "https://checkout.psp.example/pay/abc123"
  },
  "meta": { "correlation_id": "…" }
}
FieldUse
payment_urlRedirect the customer here immediately. Do not fetch, proxy or iframe it
order_idThe platform order id. Persist it — it is how you look up tickets later, including for guests
order_display_idHuman-readable reference for order confirmations and support

Store order_id and order_display_id against your own record before redirecting. If the customer closes the tab on the payment page, those two values are the only handle you have on the order.

Errors

StatustypeWhat to do
400checkout-shipping-address-requiredAddress incomplete for a physical basket
400checkout-timeslot-requiredA product needs a visit time
400Missing email / first_name / last_name / return_url, or no Idempotency-Key
401A token was sent and rejected as structurally invalid
404checkout-not-foundUnknown checkout_id, or it belongs to another customer
404checkout-reservation-expiredThe 30-minute window elapsed. Start a new checkout
404campaign-not-foundCampaign not configured — report it to us
409Idempotency collision. Retry-After present → in-flight, retry same key. Absent → body mismatch, use a new key
422checkout-reservation-invalidReservation no longer valid. Start a new checkout
422checkout-billing-detail-rejectedBilling/shipping details rejected — unknown country, invalid e-mail
502checkout-order-failedPayment system could not confirm. Retry with the same key
502checkout-order-empty-redirectPayment system returned no URL. Retry with the same key
502checkout-fulfillment-reservation-failedFulfillment could not start. Retry with the same key
500Server-side fault. Retry with the same key, then quote the correlation id
208Idempotent replay. Same payment_url and order_id. Treat as 200

Expiry is the error you will see most

checkout-reservation-expired and checkout-reservation-invalid both mean the reservation is gone, and both are unrecoverable at this endpoint — there is no extend call. Re-run Create with the same basket, which re-checks availability, then confirm again.

Handle it as a flow, not an error: "your reservation expired, we're re-checking availability" beats a stack trace. Expect it whenever a customer leaves a tab open.

On 502, retry — do not re-issue

A 502 means we reached the payment provider and something went wrong. The order may or may not exist.

Retry the identical request with the identical Idempotency-Key. You will get either a 208 with the original payment_url (the first attempt worked, the reply was lost) or a clean re-attempt.

Never retry a confirm with a fresh Idempotency-Key.

A new key is a new operation. If the first attempt did succeed, you have now created a second order and a second payment for the same basket, and the customer can be charged twice.

Notes

  • Idempotency guards double-submits too. A customer double-clicking "Pay" produces two identical requests; the same key makes the second a 208 and they are redirected to the same payment page. Reuse one key for the whole confirm intent.
  • A fast payment can beat your poll. The platform re-reads the checkout before writing PENDING_PAYMENT, so a provider that captures before Confirm returns cannot be overwritten back to a non-terminal state. A GET right after Confirm may legitimately show SUCCESS already.
  • If Confirm fails after fulfillment started, the platform cancels the fulfillment workflow itself. You do not compensate.
  • checkouts.CheckoutFulfillmentRegistered is published on success.
Path Params
uuid
required
Body Params
string
required
string
required
string
required
string

Shipping street name. Required when the checkout contains a physical product; ignored otherwise.

string

Shipping house number. Required when the checkout contains a physical product; ignored otherwise.

string

Optional house-number addition (e.g. "A", "bis").

string

Shipping postal code. Required when the checkout contains a physical product; ignored otherwise.

string

Shipping city. Required when the checkout contains a physical product; ignored otherwise.

string

ISO 3166-1 alpha-2 country code of the shipping address (e.g. "NL"). Required when the checkout contains a physical product; ignored otherwise. Resolved to the order system's country on submit.

uri
required

URL the PSP redirects to after payment.

newsletters
array of strings

List of newsletter IDs the customer opted into.

newsletters
Headers
uuid
required

Campaign scope for this request. Filters content to the specified campaign.

uuid
required

Client-generated UUID. Identical keys within the TTL window return the cached response.

string
enum
Defaults to application/json

Generated from available response content types

Allowed:
Responses

Language
Credentials
Header
URL
LoadingLoading…
Response
Click Try It! to start a request and see the response here! Or choose an example:
application/json
application/problem+json