Logo
Search
API Docs

Mid-Call Error Fallback: call.ending Hooks

Live Call Features

Mid-Call Error Fallback: call.ending Hooks

Overview

When a call is already underway with a live assistant and something goes wrong mid-pipeline — a transcriber outage, a custom LLM returning an error, a voice provider failure — you don't want the call to simply drop. A call.ending hook with a pipeline-error filter lets you catch that moment and automatically redirect the caller to a fallback destination before the call actually ends.

This is a different situation from three related pages you may already be using:

  • Hooks & Call Control covers the general hooks mechanism (trigger events, action types, the endCall tool) that this page builds on.
  • Phone Number Fallback Destination & Inbound Settings covers what happens when a phone number has no assistant, squad, or workflow to hand the call to at all — a number-level routing gap, not a mid-call failure on an assistant that was already active.
  • Call Forwarding & Transfer Modes covers the full transferCall destination schema and warm-transfer behavior that the fallback transfers on this page rely on.

This page is specifically about recovering from a mid-call pipeline error on an assistant that is already active on a live call — a distinct case from all three of the above.


The call.ending Event and the pipeline-error Filter

The call.ending event fires while a call is in the process of ending — not after it has fully ended. That timing is what makes the hook useful: it gives you a window to speak a message and issue a transfer before the call actually terminates.

Pair the event with a filters array using a oneOf filter on call.endedReason. Setting the filter's oneOf array to ["pipeline-error"] acts as a catch-all, matching any pipeline-related error reason regardless of which specific failure occurred:

{
  "on": "call.ending",
  "filters": [
    { "type": "oneOf", "key": "call.endedReason", "oneOf": ["pipeline-error"] }
  ],
  "do": [ ]
}

Omitting the filters array entirely means the hook fires on every call.ending event, regardless of why the call is ending — so always include a filter when you only want to react to pipeline errors specifically.


Basic Example: Transfer Fallback

The minimum viable version of this pattern: the moment a call starts ending due to a pipeline error, transfer it to a fallback number instead of letting it drop.

{
  "hooks": [
    {
      "on": "call.ending",
      "filters": [
        { "type": "oneOf", "key": "call.endedReason", "oneOf": ["pipeline-error"] }
      ],
      "do": [
        {
          "type": "tool",
          "tool": {
            "type": "transferCall",
            "destinations": [
              { "type": "number", "number": "+1234567890", "callerId": "+1987654321" }
            ]
          }
        }
      ]
    }
  ]
}

Combined Example: Apology, Logging, and Transfer

Actions in a hook's do array execute in order, so a spoken apology needs to come before the transfer, not after. You can also chain in an asynchronous logging tool call so the error gets recorded without holding up the transfer itself:

{
  "hooks": [
    {
      "on": "call.ending",
      "filters": [
        { "type": "oneOf", "key": "call.endedReason", "oneOf": ["pipeline-error"] }
      ],
      "do": [
        {
          "type": "say",
          "exact": "I apologize for the technical difficulty. Let me transfer you to our support team."
        },
        {
          "type": "tool",
          "tool": {
            "type": "function",
            "function": {
              "name": "log_error",
              "parameters": {
                "type": "object",
                "properties": {
                  "error_type": { "type": "string", "value": "pipeline_error" }
                }
              },
              "description": "Logs the error details for monitoring"
            },
            "async": true,
            "server": { "url": "https://your-server.com/api" }
          }
        },
        {
          "type": "tool",
          "tool": {
            "type": "transferCall",
            "destinations": [
              { "type": "number", "number": "+1234567890", "callerId": "+1987654321" }
            ]
          }
        }
      ]
    }
  ]
}

Setting "async": true on the logging tool call lets it fire in the background without delaying the transfer that follows it.


Transferring to a SIP Destination

The fallback transfer isn't limited to phone numbers — it can point to a SIP URI instead:

{
  "hooks": [
    {
      "on": "call.ending",
      "filters": [
        { "type": "oneOf", "key": "call.endedReason", "oneOf": ["pipeline-error"] }
      ],
      "do": [
        {
          "type": "tool",
          "tool": {
            "type": "transferCall",
            "destinations": [
              { "type": "sip", "sipUri": "sip:[email protected]" }
            ]
          }
        }
      ]
    }
  ]
}

See Call Forwarding & Transfer Modes for the full transfer destination schema, including which call-type-to-destination combinations SIP transfers actually support.


Targeting Specific Pipeline Error Codes

Instead of catching every pipeline error with the generic "pipeline-error" value, you can target specific error codes so different failures route to different fallbacks (or so only certain failures trigger a fallback at all). For example, to catch only a custom LLM's 500-error and its generic failure code:

{
  "filters": [
    {
      "type": "oneOf",
      "key": "call.endedReason",
      "oneOf": [
        "pipeline-error-custom-llm-500-server-error",
        "pipeline-error-custom-llm-llm-failed"
      ]
    }
  ]
}

This lets you build more granular fallback logic — for instance, sending custom-LLM failures to one destination and voice-provider failures to another — rather than treating every pipeline error identically.


Key Points

  • The hook fires while the call is ending, not yet fully ended — this is what gives you a window to redirect the caller instead of the call simply dropping.
  • Omitting the filters array means the hook fires on every call.ending event, not just pipeline errors — always scope it with a filter for this use case.
  • Actions in the do array execute in order, so a say action must come before a transferCall action if you want the caller to hear it.
  • "pipeline-error" is a catch-all filter value; targeting specific error codes gives you finer-grained control over which failures trigger which fallback.