Skip to main content

Troubleshooting: in-call AI handoff, deflection, and follow-up failures

A live AI voice agent ends a call one of three ways from inside the call: it hands the caller to a human queue, it deflects the caller to SMS or WhatsApp, or it promises a follow-up message after the call ends. Each lane fails independently of the others, and each surfaces its own 500 code — HANDOFF_FAILED, DEFLECTION_FAILED, or FOLLOWUP_SEND_FAILED. Read the code and you know which lane to work; the overall posture is the same for all three: the failure happened inside the platform’s orchestration step, so retry is limited and the fix is usually one configuration lookup, not a payload change. These codes come back from machine-to-machine endpoints the voice gateway calls when the AI agent invokes a tool (transfer_to_human, deflect_to_digital, or the post-call follow-up intent). The gateway is fire-and-forget, so a 500 here never retries itself — the record of the failure is the structured error body and the Sentry side the platform keeps. Your customer’s live call is never dropped by any of the three: HANDOFF_FAILED leaves the caller with the AI agent, and the two message-send failures only lose the digital follow-up, not the call.

Code matrix — symptom to fix

Two non-500 responses sit next to these codes and are resolved on your side, not by a retry:

Warm handoff returns 500 — HANDOFF_FAILED

POST /api/v1/internal/voice/ai-handoff is invoked by the gateway when the live AI agent session calls the transfer_to_human tool. The orchestration persists a context packet (call summary, sentiment, key topics, action items, collected slots) onto the call record, bridges the caller into the named ACD queue, and — when a conference id is supplied — drops the bot leg once the caller is safely queued. A 500 means that sequence broke somewhere between the packet write and the queue bridge; the caller is still talking to the AI agent, and no handoff packet is guaranteed. Work it in this order:
  1. Confirm the queue exists and accepts entries. The most common cause is a queue id the agent names that does not resolve — deleted since the agent prompt was authored, or a typo in the tool definition. Open the queue in the dashboard (Voice → Queues) and retry the tool call. A queue at capacity rejects the same way; see Queue capacity gates.
  2. Check required-skills plausibility. When the agent supplies required_skills, the queue has to hold an agent with those skills to answer; mismatched skills suspend the handoff. Either align the skills or drop them from the tool definition.
  3. Look at the agent-runtime logs. The log line is voice-ai-handoff: orchestration failed and it carries the underlying error message plus the call, session, and queue ids — the first thing a report needs. The dashboard status page does not expose these; pull them from your log drain or ask support for the request id.
  4. Retry once with the same call id. The packet write and the queue enqueue are idempotent on the call, so a single retry after the queue check is safe. Persistent 500s after that are platform-side — escalate.
  5. Distinguish from supervisor takeovers. SUPERVISOR_* codes on supervisor takeover failures cover a human supervisor coaching surface; HANDOFF_FAILED is the AI bot exiting itself. The two lanes share the queue but not the codes.
Agent-side behavior to expect: the voice agent keeps the caller engaged while a handoff is pending, so a failed handoff is invisible to the caller until your retry succeeds. The failure shows on the dashboard’s agent session detail for that call as a failed transfer_to_human tool result.

Deflect-to-digital returns 500 — DEFLECTION_FAILED

POST /api/v1/internal/voice/deflect-to-digital is invoked when the agent calls deflect_to_digital — “I’ll text you a link to finish this”. It opens (or attaches to) a linked conversation on the target channel, stamps the call context onto it, and sends the agent’s opening message through the same messaging envelope any other outbound message uses. A 500 means the send step rejected; the call continues. Work it in this order:
  1. Only sms and whatsapp are valid channels. A prompt that lets the agent promise email or voice as the deflection target breaks here — restrict the tool definition to the two enums. That is a request-side fix; retrying a 422-class reject never converges.
  2. Resolve the sender. The message sends from the same number the call landed on (agent_phone, when the gateway knows it) or the org’s default sender when it is null. A tenant with no default sender and a null agent phone fails the send — set a default sender in Settings → Channels, or ensure the DID the inbound call hits is sender- capable. See Sender resolution errors.
  3. Content gates also count. The same messaging envelope applies the file-scan and compliance gates any outbound message applies — a link to a quarantined attachment blocks the same way it would from the inbox composer. The rejection reaches this endpoint as a 500, so check the message body against file-scan quarantine before treating it as platform-side.
  4. Idempotency is on (call, channel). A retry of the same deflection for the same call and channel returns 200 already_deflected rather than double-sending — so retry is safe, and a duplicate 201 is the bug to report, not the retry.
On the dashboard, the call’s agent session shows the deflection tool result as failed; the conversation it would have opened does not appear in the inbox. If the send had succeeded, the opening message id and the conversation id come back on the 201 response — capture them when you report a partial flow.

Post-call follow-up returns 500 — FOLLOWUP_SEND_FAILED

POST /api/v1/internal/conversations/post-call-followup is invoked during voice-gateway teardown when the agent verbally promised a follow-up (“I’ll text you”, “we’ll email you”). It picks a channel from the agent’s promise first, the contact’s preferred channel second, and SMS as the always-available fallback, then renders the follow-up body from the call summary and sends it through the messaging envelope. A 500 means the send rejected after a channel was resolved; if no channel resolved at all the endpoint returns 204 by design, not this code. Work it in this order:
  1. Read the resolved channel. The endpoint logs the channel it picked (sms, email, or whatsapp) before it fails, and the 500’s lane is that channel. Work the channel-specific gate: for SMS/WhatsApp the sender-resolution and destination checks above; for email the email bounces and complaints surface.
  2. The contact record matters. An email follow-up needs the contact’s email; when it is absent the endpoint returns 204, not a 500. A 500 on email means the address existed and the dispatch failed — check email DNS posture on email DNS drift.
  3. Re-trigger by resending the follow-up summary. There is no customer-callable retry endpoint for this lane — the gateway already tore the session down. The safe re-trigger is to send the follow-up manually from the conversation the call opened (Inbox → pick the caller’s thread → send), or re-queue it via the messages API with the same summary text. The metadata.source = voice_post_call_followup tag on the original attempt marks it on the call’s interaction record.
  4. Idempotency is on (call). Retry of the same call id lands as 200 already_enqueued, so an accidental re-POST never double-sends — only the manual re-send steps above will.
On the dashboard, the session ended cleanly (this failure is post-call); the only artifact is the missing follow-up message on the caller’s thread and the failed tool result on the session detail.

What NOT to try

  • Do not loop the retry. All three lanes are orchestration calls with bounded retry-safety (a single re-issue after configuration checks). The gateway’s fire-and-forget posture means a webhook-driven immediate re-POST turns one failure into a retry storm.
  • Do not treat 204 as failure. An empty message body or a missing destination address (email promised, none on file) short-circuits with 204 No Content and a metric bump — that is by design. Retry only after the missing configuration is restored.
  • Do not confuse lanes. Supervisor-coaching failures are SUPERVISOR_* and live on their own page. Media-path complaints after a successful handoff live on voice call quality. Neither lane belongs here.
  • Do not fix the caller’s experience in-place. A failed handoff keeps the caller with the AI; a failed deflection or follow-up loses only the digital message. None of the three drop the call — do not escalate these as call-quality incidents.

What to capture before escalating

Work the matching lane first. If the same code persists after the checks it names, open a ticket with:
  1. The error code and HTTP status, verbatim (HANDOFF_FAILED, DEFLECTION_FAILED, or FOLLOWUP_SEND_FAILED, 500).
  2. The request ID from meta.request_id on the failing response — the platform Sentry record is keyed to it.
  3. The call ID and session ID off the request — either on the tool call the agent logged or from the call detail view.
  4. The lane-specific detail: the queue id the agent named for handoff; the channel and sender for deflection; the resolved channel and the contact’s address presence for follow-up.
  5. Your organization ID (Settings → Organization, or organizationId from GET /api/v1/me).

See also