Skip to content
TriggersonCallEndstable

On call ended

Fires once when the call hangs up, regardless of where the main flow was. The downstream chain runs in isolation, so this is the safe place for final webh…

What it does

Fires once when the parent (caller) leg hangs up, no matter where the main flow happened to be. The chain runs in an isolated state branch after the worker has finished its own teardown housekeeping (closing the queue entry, committing any pending voicemail), which means the call is already gone by the time your downstream nodes run — there is no audio to play, no agent to talk to, no caller leg to interact with. What you do have is the full vars snapshot accumulated during the call: vars.lastRecordingUrl, vars.gatheredDigits, vars.transferTo, anything you stamped along the way.

This is the right place for the work that has to happen every call, regardless of which IVR path the caller took or where they dropped off: final webhooks, CRM "call ended" updates, summary Slack pings, custom analytics. Because the chain runs in isolation, even if the main flow had already terminated normally, your onCallEnd chain still runs — it's the "finally" block of the call.

When to use it

  • POST a final summary to your CRM with duration, recording URL, and vars.gatheredDigits.
  • Send a Slack message to #sales whenever a sales-line call ends, with the caller number and a link to the recording.
  • Mark a row in your own database as "completed" with the same metadata.
  • Kick off an after-call survey by writing a job to a queue (the call is gone — anything synchronous-voice belongs in the main flow before hangup).

If you need a teardown step that runs before hangup (e.g. play a goodbye message), put it in the main flow before hangup. Once onCallEnd fires, the carrier connection is already torn down.

Configuration

_label and _note are author-only metadata; this trigger has no runtime configuration of its own.

Fires once when the call hangs up, regardless of where the main flow was. The downstream chain runs in isolation, so this is the safe place for final webhooks, CRM updates, Slack notifications, or analytics — it always runs even if the caller dropped mid-menu.

FieldLabelTypeRequiredDefaultNotes
_labelTrigger labeltextOptionalFriendly name shown in the canvas and run logs. Optional.
_noteInternal notetextareaOptionalNotes for your team.

Outgoing events: triggered

Examples

Final summary webhook

Posts a JSON payload to your own service every time a call ends, no matter how it ended.

{
  "id": "end-trigger",
  "type": "onCallEnd",
  "config": { "_label": "Post call summary to webhook" },
  "on": { "triggered": "post-summary" }
}
[onCallEnd] ──► [httpCall POST /webhooks/call-ended
                  body: {
                    from: "{{event.from}}",
                    to: "{{event.to}}",
                    recording: "{{vars.lastRecordingUrl}}",
                    digits: "{{vars.gatheredDigits}}"
                  }] ──► [end]

Gotchas

  • No audio side-effects work here. The caller leg is already gone by the time onCallEnd fires. say, playAudio, record, holdCall, and anything else that talks to the carrier will fail or no-op silently. Do outbound HTTP, setVar, customScript, or runFlow work only.
  • Trigger errors are swallowed. If a node in the chain throws (the third-party API is down, an httpCall times out), the worker logs the error and moves on — it doesn't retry, and there's no onError path on the trigger itself. Build retries into your destination service if you need at-least-once delivery semantics.
  • Side-chain runs in an isolated state branch. The chain shares the call's final vars snapshot but cannot mutate flow position — by this point there is no flow position to mutate. Mutations to vars from inside the chain are not persisted anywhere durable; the run row is about to be marked completed.
  • Pending voicemail is committed before the trigger runs. If the caller hung up mid-voicemail, the worker calls commitVoicemail() first, so by the time your chain runs vars.lastRecordingUrl is populated. Don't try to commit the voicemail yourself.