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.

  1. Start with a reservationId from a POST /v1/holds response — see Quickstart.
  2. Request a code: POST /v1/holds/{reservationId}/otp with { "email": "<guest email>" }. Returns 202 { "accepted": true, "expiresAt": ... }. The guest receives a 6-digit code by email.
  3. Verify it: POST /v1/holds/{reservationId}/otp/verify with { "email": "<same email>", "code": "<6 digits>" }. Returns 200 { "guestVerificationToken": "...", "expiresAt": ... }.
  4. Confirm the hold: POST /v1/holds/{reservationId}/confirm with { "guestVerificationToken": "..." }. This token only authorizes confirming this one reservation — it won’t work for any other reservation or email. Returns 200 with the reservation, now state: "confirmed" — but only once a card payment has succeeded on this hold (see step 5). See Holds and confirmation for what confirm checks today.
  5. 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

Verify with the exact same email you requested with. Matching is case-insensitive on our side, but a typo between the two calls always fails as bad_code, even if the digits are right.