{"slug": "best-practices-for-conversation-relay-provider-failover", "title": "Best Practices for Conversation Relay Provider Failover", "summary": "Twilio published best-practice guidance for building application-level provider failover into Voice AI agents running on Twilio Conversation Relay, which uses a third-party transcription (ASR/STT) provider and a text-to-speech (TTS) provider on every call. The guide instructs developers to always set an action URL on <Connect> and to check ErrorCode rather than only SessionStatus, because some mid-call ASR failures report SessionStatus=ended while still including an ErrorCode. It also notes that mid-call TTS failures keep the call connected and surface only as WebSocket error messages, so the developer's server must detect them and send an end message with handoffData before switching providers in the following TwiML.", "body_md": "# Best Practices for Conversation Relay Provider Failover\n\nTime to read:\n\n**Written by**\n\n**Reviewed by**\n\nWhen you're scaling Voice AI agents in production [using Twilio Conversation Relay](https://www.twilio.com/docs/voice/conversationrelay), keeping your architecture uninterrupted is critical. That’s *especially* true during high-volume events like Black Friday or Cyber Monday, or sudden traffic spikes. High load or unexpected provider degradation can introduce latencies, connection drops, or request failures. To harden your Conversation Relay infrastructure against these risks, you need an application-level failover strategy.\n\n[Conversation Relay](https://www.twilio.com/docs/voice/twiml/connect/conversationrelay) uses two speech providers on every call: a **transcription (ASR/STT)** provider that turns the caller's speech into text, and a **text-to-speech (TTS)** provider that speaks your agent's replies. Each one is a third-party service, such as Deepgram, Google, ElevenLabs, or Amazon. Sometimes, a service might be slow, unavailable, or reject a request.\n\nThis guide covers:\n\n- How a provider failure reaches your application, at startup and mid-call.\n- Which error codes to look for.\n- How to return new `<ConversationRelay>` TwiML from your`action` URL that**fails over** to a different provider, so the caller stays on the line.\n- How to deal with failures in the middle of a call by sending an `end` message with`handoffData` , then switching providers in the TwiML that follows.\n\n##  [**How failures reach you**](#toc-heading-09102531-97d0-4d50-aa04-e1ef82aababd)\n\n**How failures reach you**\n\nWhere a failure shows up depends on **which provider** failed and **when** it failed:\n\n| When | Provider | What Conversation Relay does | Where you see it | \n|---|---|---|---|\n| Startup | Transcription (ASR) | Ends the session. | **`action` URL** :`SessionStatus=failed` and an`ErrorCode` | \n| Startup (welcome greeting) | Text-to-speech (TTS) | Usually ends the session. If the failure arrives just after the call connects, it's reported like a mid-call TTS failure instead (next row). | **`action` URL** :`SessionStatus=failed` and an`ErrorCode` ,**or** a WebSocket`error` message | \n| Mid-call | Transcription (ASR) | Ends the session. | **`action` URL** : an`ErrorCode` (`SessionStatus` may be`failed`**or**` ended` ) | \n| Mid-call | Text-to-speech (TTS) | **Keeps the call up.** The failed reply is not spoken, and later replies are tried again. | **WebSocket `error` message only.** Your server decides whether to end the session. | \n\nTwo rules follow from this table:\n\n-  **In your `action` URL handler, check `ErrorCode`, not only `SessionStatus`.** Some mid-call failures report` SessionStatus=ended` and still include an`ErrorCode` .\n-  **Handle TTS failures in your WebSocket server.** When the agent's voice stops working mid-call, the call stays connected and the caller hears silence. Conversation Relay won't end the call for you. Your server has to notice the`error` messages and send`end` (see the**Mid-call failures section** , below).\n\n##  [**Prerequisites for failover**](#toc-heading-642ff913-77f1-455d-87ff-b65efac9a814)\n\n**Prerequisites for failover**\n\nBefore implementing custom failover logic, your Conversation Relay application should implement a few basic best practices. These fundamentals will help you build a robust production service.\n\n###  [**Always set an `action` URL on `<Connect>`**](#toc-heading-656dd77f-746f-43c1-ba75-8ffa7ab76045)\n\n**Always set an**\n\n`action` URL on `<Connect>`\nFailover only works when an `action` URL is set on `<Connect>`. When the Conversation Relay session ends, for any reason, Twilio sends a request to that URL, and the call carries on with the TwiML you return. Without an `action` URL, the call moves on to whatever verb follows `<Connect>` in the original document, and you can't vary that by error.\n\n###  [**Name your providers explicitly**](#toc-heading-38e90b07-9c54-44cc-bc3c-82ece9d5098f)\n\n**Name your providers explicitly**\n\nAlways set `ttsProvider` and `transcriptionProvider`, even when you're using the defaults. That way every TwiML document says exactly which provider it uses, and your failover TwiML can switch to a different one.\n\nIf you leave out the TTS or transcription settings for a language, Conversation Relay fills them from a default (ElevenLabs with a default voice for TTS; Google or Deepgram for transcription). That default is not something you can fail over from or to on purpose. Set `ttsProvider` next to `voice` as well, rather than letting the provider be inferred from the voice name.\n\n###  [**Track the attempt number**](#toc-heading-112857bd-7b9d-4034-ba14-2f534088d0b6)\n\n**Track the attempt number**\n\nPut the attempt number, and which providers you've already tried, in the `action` URL query string (for example `?attempt=2&tts=google`). Your handler then knows where it is in the failover chain and can't end up in a loop (see the  **Avoiding failover loops** section for more).\n\n##  [**Watch your `action` URL parameters**](#toc-heading-76f0cf8b-bf22-4a9d-95c2-47a6a0f3be5a)\n\n**Watch your**\n\n`action` URL parameters\nWhen the session ends, [Twilio sends the standard Voice call parameters](https://www.twilio.com/docs/voice/api/call-resource#statuscallback) (`CallSid`, `AccountSid`, `From`, `To`, `CallStatus`, and so on) plus these Conversation Relay parameters:\n\n| Parameter | Present | Description | \n|---|---|---|\n| `SessionId` | Always | ID of the Conversation Relay session that ended. | \n| `SessionStatus` | Always | `completed` : the caller hung up.`ended` : the session was ended, normally by your`end` message.`failed` : the session ended because of an error. | \n| `SessionDuration` | Always | How long the session lasted, in seconds. | \n| `ErrorCode` | On error | Twilio error code. **Branch on this.** | \n| `ErrorMessage` | On error | Human-readable description. It can include the provider's own error text. Log it, but don't branch on it, because the wording can change. | \n| `HandoffData` | When you sent it | The `handoffData` string from your`end` message, passed through unchanged. | \n\n###  [**Error codes to watch**](#toc-heading-ec4a3317-bbf1-486b-b4ea-da1b5d325c1a)\n\n**Error codes to watch**\n\nSome error codes you’ll get back from the webhook suggest it’s time for you to fail over to another provider. This section has an overview of the types of codes you may see, plus the individual error codes.\n\n####  [**Provider failures: candidates for failover**](#toc-heading-c53ba6c6-542d-470b-aa2d-5b8d42cbb3d0)\n\n**Provider failures: candidates for failover**\n\n| Code | Meaning | Typical cause | Recommended action | \n|---|---|---|---|\n| **64114** | Unable to connect to ASR provider | The transcription provider is unreachable, timed out during setup, or failed during the call. | **Fail over transcription** to a different`transcriptionProvider` . | \n| **64111** | TTS provider service error | The TTS provider returned a server error (5xx), timed out, or dropped the connection. This usually means a provider outage or slowdown. | **Fail over TTS** to a different`ttsProvider` . | \n| **64112** | TTS provider conversion error | The TTS provider rejected the request, for example because the voice ID doesn't exist, the credentials or quota are invalid, or the text or settings aren't supported. `ErrorMessage` (or the WebSocket`description` ) includes the provider's reason. | **Fail over TTS** to keep the caller on the line,**and** check your voice and provider configuration. This error usually happens again with the same settings.**Exception:** if the message contains`input_timeout_exceeded` , see the**64112** section below. | \n| **64103** | Internal server error | Something failed inside Twilio, not at your speech provider. Provider failures are reported with 64111, 64112, and 64114 instead. | Retry **once** with the same providers. If the error keeps happening, contact Twilio Support with the`CallSid` . | \n\n| Code | Meaning | Suggested handling | \n|---|---|---|\n| 64102 | Unable to connect to WebSocket URL | Fail over to a backup WebSocket URL ( `url=` ), not to a different speech provider. | \n| 64105 | WebSocket session ended | Your server closed the WebSocket without sending `end` . Send`end` instead (see the**Mid-call failures** section). | \n| 64107 | Invalid message received | Your server sent a malformed message. Fix the server. | \n| 64104 | Max call duration reached | Expected behavior. No failover. | \n\n##  [**Startup failures**](#toc-heading-34ec8bfe-a964-47bf-9659-87e2b042d820)\n\n**Startup failures**\n\nA  *startup failure* means the session ended before the conversation got going. Usually the caller has heard nothing yet, or only part of the welcome greeting. Conversation Relay calls your `action` URL with `SessionStatus=failed` and an `ErrorCode`, and the caller  **stays on the line** while you return new TwiML.\n\n###  [**Original TwiML (primary providers)**](#toc-heading-534a1c95-0c4f-4360-9a1f-c04e83358c8d)\n\n**Original TwiML (primary providers)**\n\n###  [**Transcription (ASR) failed at startup: `ErrorCode=64114`**](#toc-heading-a64078f5-122e-4381-857b-5f8264060b2c)\n\n**Transcription (ASR) failed at startup:**\n\n`ErrorCode=64114`\nKeep the TTS provider and switch the transcription provider:\n\nWhen you switch transcription providers, also change `speechModel` to a model the new provider supports. A Deepgram model name sent to Google, or the other way round, fails with  **64106**.\n\n###  [**TTS failed at startup: `ErrorCode=64111` or `64112`**](#toc-heading-99cd4a3a-d66e-4604-9920-80c30d15b4eb)\n\n**TTS failed at startup:**\n\n`ErrorCode=64111` or `64112`\nKeep the transcription provider and switch the TTS provider and voice:\n\nVoices belong to a provider. When you change `ttsProvider`, choose a voice from the new provider's catalog. Pick your backup voice in advance, so the agent sounds as close as possible to the primary.\n\nThe backup provider must also support your `language`. Providers don't all serve the same languages, and a backup that doesn't (for example Amazon with `zh-TW` or `he-IL`) is rejected with  **64101**, which means your failover TwiML fails too. Test every backup voice for each language you support.\n\nThe `<Parameter>` elements are passed to your WebSocket server in the `setup` message under `customParameters`. That way your agent knows it's running on a failover session, and can, for example, keep the response style shorter or log the event.\n\n###  [**Example `action` URL handler**](#toc-heading-179b1f52-3c35-4f2a-817e-0b3ff0efd2a4)\n\n**Example**\n\n`action` URL handler\nHere's a minimal Node.js (Express) handler. It picks the next provider, limits the number of attempts, and falls back to a human agent when nothing else works:\n\n##  [**Mid-call failures**](#toc-heading-800bb1eb-adae-4608-8873-e280e4323010)\n\n**Mid-call failures**\n\nSometimes, you’ll need to react to a failure in the middle of a call. The following sections detail failure modes you could run into during a live call and detail whether you should take action.\n\n###  [**Mid-call transcription failure: no additional action needed in your WebSocket server**](#toc-heading-06b99ceb-dfc6-4b96-9088-abdb85152809)\n\nIf the transcription provider fails during the call, Conversation Relay ends the session and calls your `action` URL with `ErrorCode=64114` (or `64103` if the failure was inside Twilio). Depending on where in the call the failure happened, `SessionStatus` may be `failed` or `ended`. Your WebSocket connection is closed.\n\nThe `action` URL handler from the  **Example action URL handler** section above already deals with this: it sees the ASR error code and returns TwiML with the other `transcriptionProvider`. To carry on the conversation instead of starting over, store your conversation state server-side (keyed by `CallSid`) and look it up again when the new session's `setup` message arrives. `setup` always includes `callSid`.\n\n###  [**Mid-call TTS failure: your WebSocket server must act**](#toc-heading-71b4ec57-b0ee-42bc-bfc0-44887b5ee834)\n\nA mid-call TTS failure  **does not end the session**. Conversation Relay sends an `error` message to your WebSocket server, doesn't speak that reply, and keeps the call connected. If the provider is down, every later reply fails the same way, and the caller hears silence until they hang up.\n\nThe `error` message comes in one of two forms, depending on the provider:\n\nThe first form is used by most providers, including ElevenLabs and Deepgram. The second is used by Google and Amazon voices, and it doesn't include the unspoken text. Both include the  **error code** (`code: ...`). When the first form also includes the  **text that couldn't be spoken** (`tokens: ...`), you can pass it to the new session so the caller hears the reply they missed.\n\n **Recommended handling:**\n\n- Read the code from `description` . Treat`64111` and`64112` as TTS failures, whichever form the message takes. Skip`64112` with`input_timeout_exceeded` (see the**Error codes to watch** section). Other codes, such as`64106` for a media URL that couldn't be played, aren't provider failures.\n- Decide when to fail over. A single 64111 can be a one-off. Two failures in a row, or any failure during the first turn (the welcome greeting), means the provider can't be relied on, so fail over.\n- Send an  **`end` message** with`handoffData` that describes why you're ending and what the next session needs.`handoffData` must be a**string** , so call`JSON.stringify` on your object.\n- Conversation Relay ends the session and calls your `action` URL with`SessionStatus=ended` and`HandoffData` set to your string. Your handler returns TwiML that uses a different`ttsProvider` .\n\nThe `end` message:\n\n###  [**Handling the handoff in your `action` URL**](#toc-heading-95ba5020-cd2e-44e1-8935-a0f25a285383)\n\n**Handling the handoff in your**\n\n`action` URL\nThe handler above already reads `HandoffData`. When `reasonCode` is `tts-failure`, it moves on to the next TTS provider. It also passes `conversationId` through a `<Parameter>`, so the new session's `setup` message resumes the same conversation. Here's the TwiML for a mid-call TTS handoff that also replays the unspoken text:\n\nPutting `unspokenText` in `welcomeGreeting` means the caller hears the reply that failed. If you'd rather not, use a short \"where were we?\" greeting, or leave `welcomeGreeting` out and have your agent speak first once `setup` arrives.\n\n### [**Ending the call for other mid-call reasons**](#toc-heading-7a1f26e9-d3b8-42f4-ab80-b49119fbb881)\n\n**Ending the call for other mid-call reasons**\n\nYou can use the same `end` plus `handoffData` pattern for any mid-call problem your server detects, for example when your LLM provider is down, a tool call keeps failing, or the caller asks for a person. Set a different `reasonCode` (`llm-failure`, `live-agent`, ...) and branch on it in your `action` URL handler. Always prefer `end` to closing the WebSocket. If you close the WebSocket, the session fails with **64105** and you lose the `HandoffData`.\n\n## [**Avoiding failover loops**](#toc-heading-8c9b0476-3a4c-4ad5-96be-d6bf2fd856da)\n\n**Avoiding failover loops**\n\nWhen you add failovers to your Conversation Relay app, you need to be careful that you don’t accidentally create a loop which cycles through fallback providers and never exits. This list contains our best practices for avoiding a failover loop.\n\n- **Limit the number of attempts.** Carry`attempt` in the`action` URL and stop after 2 or 3.\n- **Change something on every attempt.** Only retry identical TwiML for 64103, and only once.\n- **Don't fail over on configuration errors** (64101, 64106). They fail the same way on every attempt.\n- **Always have a final fallback that doesn't use Conversation Relay** , such as`<Say>` with a`<Dial>` to a human agent,`<Enqueue>` ,`<Record>` for voicemail, or a polite`<Hangup>` .`<Say>` uses Twilio's own TTS, so it doesn't depend on the provider that just failed.\n- **Return `<Response/>` when `SessionStatus=completed`** , because the caller has already hung up.\n\n## [**Checklist**](#toc-heading-a9a9b9d3-bef2-4afc-ab85-5c60ff7cdaf9)\n\n**Checklist**\n\nLooking for a cheatsheet on how to handle failovers in your production Conversation Relay app? This list contains a succinct version of our advice from this post, so you can check that your app is handling possible failure cases.\n\n- ☐ Every `<Connect>` has an`action` URL that carries`attempt` and the current providers.\n- ☐ `ttsProvider` ,`voice` ,`transcriptionProvider` , and`speechModel` are set explicitly.\n- ☐ A backup voice and a backup speech model are chosen and tested for each language you support. A backup that doesn't serve the language fails with 64101.\n- ☐ The `action` URL handler branches on`ErrorCode` (not only`SessionStatus` ) and parses`HandoffData` .\n- ☐ 64114 fails over transcription. 64111 and 64112 fail over TTS. 64103 retries once. 64101 and 64106 go to the fallback and get alerted on.\n- ☐ Your WebSocket server watches for `error` messages with 64111 or 64112 (in both message forms) and sends`end` with`handoffData` .\n- ☐ Conversation state is stored server-side and resumed through `<Parameter name=\"conversationId\">` .\n- ☐ The final fallback doesn't depend on Conversation Relay.\n- ☐ `ErrorCode` ,`ErrorMessage` , and`CallSid` are logged for every failover, so you can follow up with Twilio Support.\n\n## [**Conclusion**](#toc-heading-afc42966-100c-4053-92f8-cf71e3b18996)\n\n**Conclusion**\n\nBy implementing these failover strategies, your Conversation Relay Voice AI agents will be more resilient against speech provider outages, traffic surges, and transient degradations. Building a multi-provider fallback strategy helps ensure continuous, high-availability conversation flow – whether you’re dealing with high-volume events like Black Friday, or traffic on a random weeknight.\n\nWith these best practices in place, your voice infrastructure can be hardened for enterprise scale. *We can’t wait to see – and hear – what you build!*\n\n### [**Suggested resources**](#toc-heading-acb5c7b4-602b-4b81-ad5a-f16644dce764)\n\n**Suggested resources**\n\n- [Twilio Conversation Relay Overview & Documentation](https://www.twilio.com/docs/voice/conversationrelay)\n- [ConversationRelay TwiML Noun Reference](https://www.twilio.com/docs/voice/twiml/connect/conversationrelay)\n- [Twilio Voice Call Resource & Status Callback Parameters](https://www.twilio.com/docs/voice/api/call-resource#statuscallback)\n\n*Daniel Jansson is a Tech Lead at Twilio, where they have worked since 2017 and helped create Conversation Relay. They spend a lot of their time making voice AI calls keep working when the speech-to-text or text-to-speech provider on the other end doesn't. They can be reached at djansson [at] [twilio.com](https://twilio.com).*\n\n## Related Posts\n\n## Related Resources\n\nTwilio Docs\n\nFrom APIs to SDKs to sample apps\n\nAPI reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.\n\nResource Center\n\nThe latest ebooks, industry reports, and webinars\n\nLearn from customer engagement experts to improve your own communication.\n\nAhoy\n\nTwilio's developer community hub\n\nBest practices, code samples, and inspiration to build communications and digital engagement experiences.", "url": "https://wpnews.pro/news/best-practices-for-conversation-relay-provider-failover", "canonical_source": "https://www.twilio.com/en-us/blog/developers/best-practices/conversation-relay-failover", "published_at": "2026-11-04 00:00:00+00:00", "updated_at": "2026-11-05 01:20:51.925503+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-infrastructure"], "entities": ["Twilio", "Conversation Relay", "Deepgram", "Google", "ElevenLabs", "Amazon"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/best-practices-for-conversation-relay-provider-failover", "markdown": "https://wpnews.pro/news/best-practices-for-conversation-relay-provider-failover.md", "text": "https://wpnews.pro/news/best-practices-for-conversation-relay-provider-failover.txt", "jsonld": "https://wpnews.pro/news/best-practices-for-conversation-relay-provider-failover.jsonld"}}