Skip to content

Holds and confirmation

A reservation moves through one state machine, and only through it — nothing changes a reservation’s state any other way:

HELD → CONFIRMED → CHECKED_IN
HELD → CANCELLED
HELD → EXPIRED (hold timed out — 15 minutes)
CONFIRMED → CANCELLED
CONFIRMED → NO_SHOW

POST /v1/holds takes a signedQuoteToken from Quotes and turns it into a 15-minute hold.

Terminal window
curl -X POST https://api.<domain>/v1/holds \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: <a-uuid-you-generate>" \
-H "Content-Type: application/json" \
-d '{ "signedQuoteToken": "eyJhbGciOi..." }'

A pk_ key can create a hold (it’s the widget’s job); it can never read what’s inside one — reading a hold’s guest data needs GET /v1/bookings/{id} with an sk_ key.

POST /v1/holds/{id}/payment-intent creates a Stripe PaymentIntent for a held reservation. It needs an sk_ key and an Idempotency-Key header.

Terminal window
curl -X POST https://api.<domain>/v1/holds/<RESERVATION_ID>/payment-intent \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: <a-unique-uuid>" \
-H "Content-Type: application/json" \
-d '{}'

It returns a clientSecret, which the integrator hands to Stripe.js or Stripe Elements in the guest’s browser; the card is charged there, never on our servers. If the tenant hasn’t connected a Stripe account, this returns 409 stripe_not_configured.

When the card payment succeeds, Stripe sends payment_intent.succeeded to our webhook (POST /v1/webhooks/stripe), and the reservation moves HELD → CONFIRMED automatically. No separate confirm call is needed on this path.

POST /v1/holds/{id}/confirm exchanges a guest-verification token — see Verify a guest and confirm a hold — for a CONFIRMED reservation.

Terminal window
curl -X POST https://api.<domain>/v1/holds/<RESERVATION_ID>/confirm \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{ "guestVerificationToken": "eyJhbGciOi..." }'

GET/PATCH/DELETE /v1/bookings/{id} reads a reservation and drives further legal transitions (check the guest in, check them out, mark a no-show, cancel). These need an sk_ key — a booking carries guest data, which a pk_ key can never read. PATCH only accepts checked_in, checked_out, no_show, or cancelled as a target state — confirmed is reachable only through the guest-token-gated confirm route above, never a plain PATCH.

Anyone can call POST /v1/holds with a publishable key found in a page’s source — that key is public by design. The only reason that isn’t a problem: a hold is worthless inventory-squatting until a real guest verifies their email and pays. Both of those steps are gated on our server, never the browser: verification through the OTP token, payment through the Stripe webhook — a pk_ key can trigger neither on its own.

Cancelling a reservation (DELETE /v1/bookings/{id}, above) never moves money on its own — refunding is a separate call. See Refund a booking.