Skip to content

Verify a guest and confirm a hold

Guest identity verification and hold confirmation are both live today. This guide covers the full escalation: a hold you already created, through to a confirmed reservation.

A reservationId from a POST /v1/holds response — see Quickstart.

Terminal window
curl -X POST https://api.<domain>/v1/holds/<RESERVATION_ID>/otp \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{ "email": "guest@example.com" }'

Returns 202 { "accepted": true, "expiresAt": "..." }. The guest receives a 6-digit code by email.

Terminal window
curl -X POST https://api.<domain>/v1/holds/<RESERVATION_ID>/otp/verify \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{ "email": "guest@example.com", "code": "482913" }'

Returns 200 { "guestVerificationToken": "...", "expiresAt": "..." }.

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..." }'

Card payment gates confirmation. POST /v1/holds/{reservationId}/payment-intent (sk_-only, Idempotency-Key required) returns a clientSecret for Stripe.js. Once the guest pays, our Stripe webhook confirms the hold automatically — you don’t need to call confirm yourself on that path. Call confirm early, before the payment succeeds, and it returns 409 payment_required instead.

You see Why What to do
404 hold_not_found Wrong reservationId, or it belongs to another tenant’s key Double-check the ID and which key you’re using
409 hold_not_held The hold isn’t active — already expired or already confirmed There’s nothing to verify against an expired hold; a new one needs a new OTP cycle
400 bad_code Wrong code, or no active code for this hold+email Ask the guest to re-check their email, or request a new code
400 expired The code’s own TTL passed Request a new code
423 exhausted_attempts Six wrong guesses in a row locked this code Request a new code — the old one won’t unlock, even with the right digits
429 rate_limited Too many issue/verify calls for this hold Back off using the Retry-After header
400 invalid_guest_token (confirm) The token is malformed, expired, or wasn’t issued by us Re-run the OTP verify step to get a fresh token
400 wrong_reservation (confirm) The token is valid but was issued for a different reservation Use the token from the same reservationId you’re confirming
409 not_confirmable (confirm) The hold is no longer in a confirmable state (e.g. already cancelled or expired) Start a new hold — there’s no way to revive this one
409 payment_required (confirm) No succeeded Stripe payment exists for this hold yet Create a payment intent and let the guest pay — the webhook confirms automatically once it succeeds