Skip to main content

Troubleshooting: queue booking failed

A caller — or an agent-driven widget — submits the public queue self-booking form and the POST /api/v1/public/queues/{tenantId}/{queueId}/appointments call comes back 500 with BOOKING_FAILED. The code is the route’s generic wrap for a booking attempt that failed below the HTTP layer, so it is a transient server-side failure until proven otherwise — but it occasionally hides an already-taken slot that raced availability. This page separates the two, gets the booking through when a retry is safe, and builds the support bundle when it is not. Only the BOOKING_FAILED 500 lives here: a taken slot usually rejects cleanly with 409 (SLOT_ALREADY_BOOKED / SLOT_NOT_AVAILABLE) and a queue closed to public booking rejects with 422 (INVALID_TENANT or NO_BOOKABLE_SCHEDULE) — those codes are working as designed, not failures.

Recognize the signal

The booking POST rejects with a 500 and this envelope shape:
  • error.codeBOOKING_FAILED, one refusal for every server-side failure on the booking attempt. It does not name the underlying cause.
  • meta.request_id — the one handle that ties this response to the server log line; keep it for every retry and every escalation.
Reclassify before retrying: if the refusal came back 409 or 422 instead of a 500, the slot or the queue gate decided — the booking never reached the write path, and this page is the wrong runbook.

Cause table

Fixes

1. Retry once with the same payload

BOOKING_FAILED is deliberately retriable — the booking either persisted or it did not, and a failed attempt never creates a partial slot hold. Send the identical body once:
One retry only. Every booking attempt on the public surface counts against the unauthenticated rate limit the hosted booking page shares with all its callers — burn the allowance and legitimate callers get refused behind you.

2. Poll the availability GET and re-pick if the slot went

When the retry also 500s, re-read the published slots before picking again:
If your slot_start is no longer listed as available, a concurrent caller took it and the conflict surfaced as the generic 500 — book a slot the fresh read still shows. If the slot still reads as available and the POST still 500s, the failure is server-side and deterministic; move to escalation.

3. Open the ticket with the narrowing bundle

On the second consecutive BOOKING_FAILED, stop and page support with:
  • the meta.request_id from the last failing response envelope,
  • the queue id and the slot interval you attempted (slot_start + slot_minutes), and
  • the availability snapshot — whether the slot still read as available after the failure.
The request id is what narrows it: the route logs the underlying failure server-side per attempt, and support reads that line straight off your request id instead of re-deriving the cause from the queue config.

What not to do

  • Do not hammer the endpoint in a retry-loop. The public booking surface sits behind anti-bot token checks and a brutal per-IP rate limit — a tight retry loop burns the allowance the hosted booking page shares with every other caller, and legitimate bookings start refusing behind you.
  • Do not treat BOOKING_FAILED as “the slot is taken.” A taken slot gets its own 409 codes; the 500 means the write itself failed. Re-read availability before re-picking so you do not abandon a slot that was never taken.
  • Do not retry a queue where every slot fails the same way. Uniform failure across every slot is deterministic, not transient — escalate with the bundle instead of sampling more slots and tripping the rate limit.
  • Do not book on behalf of a caller through a script. The booking is the caller’s consent to be called back at that slot; scripted bulk booking stores consent records nobody gave and hits the same anti-bot gate.

Escalate

If the retry fails and the availability read still lists the slot:
  • capture the meta.request_id from the failing response envelope,
  • include the queue id, the slot interval (slot_start and slot_minutes), and the caller’s phone in E.164,
  • attach the availability response showing the slot still listed.
Send those to support with the tenant’s organization name — the operator reads the logged booking failure straight off the request id and compares it against the queue’s dispatcher counters.

See also

  • Public queue self-booking — the guide that opts a queue into public booking, publishes its bookable window, and composes the hosted booking page URL.
  • Queue capacity gates — the sibling queue refusals (QUEUE_DEPTH_CAP_REACHED) on inbound call entry, a different surface from self-booking.
  • Queue SLA forecast callback gate — the 503 + slaCallbackBlocked entry refusal and the callback-dispatcher wiring that also dials your booked appointments.
  • Voice queues guide — queue membership, schedules, and the callback dispatcher the booked appointment rides on.