1. Feedback Actions
  2. Update From Dial

Feedback Actions

Update From Dial Feedback Action

Explains the Dial feedback action that runs when a phone call comes in through a SIP connection. Useful for answering phone calls with a voice assistant, turning calls away, and following up on missed calls.

Overview

Stubber can take phone calls through your phone system (PBX). A SIP connection logs Stubber into the PBX as an extension, like a desk phone. Any call to that extension, whether someone dials it on the PBX or the PBX routes a phone number to it, rings Stubber, and the _update_from_dial feedback action is triggered on the stub.

The stub that gets the call is set on the SIP connection. SIP connections are set up by Stubber for now.

The message of the stubpost, i.e. stubpost.message, says who is calling, eg Incoming call from Joe Soap (0215550123).
All the details of the call are available in the stubpost.data._incoming_dial_data data path.

Only two call events are posted: incoming and missed. Once a call is answered, it reports back like any live voice call, with the _update_from_voice feedback action, including when it ends.

NOTE

This is about phone calls coming in to Stubber. To make a call from a user's own PBX extension, see the Dial notification.

Two flags on stubpost.data._incoming_dial_data tell the events apart:

Flag Incoming call Missed call
is_call_event true true
is_incoming_call true false

Answering a call

Run Start Voice Chat with the dial channel and the call's audio format, then Answer Dial Call with the call and the new voice session. Use is_incoming_call as the condition on both:

loading...
WARNING

Pass call_data.audio_format to start_voice_chat with ~~, as above. Phone calls use the G.711 audio format the phone system prefers, μ-law or A-law, and the voice call has to match it. Without it, the voice call uses μ-law, and an A-law call can't be answered.

answer_dial_call returns once the caller hears the assistant, or with an error when answering failed:

Result Meaning
success The call is live. payload.status is answered
call_not_ringing The call isn't ringing anymore, eg the caller hung up first or the call was already answered or rejected
unknown_call No call with this dialcalluuid in your organization
technical error Answering failed. error.details.step says where. The caller's call ends
NOTE

The call must be answered within 25 seconds of the incoming event. The caller hears ringing until then. After that the call is turned away and a missed event follows.

To end an answered call, end the voice call as usual, eg with a hangup voice notification.

Rejecting a call

To not take the call, run Reject Dial Call:

loading...

It only works while the call is ringing. The caller's phone system is told the call was declined, and no missed event follows.

Missed calls

A call that was never answered triggers _update_from_dial a second time, with call_data.event set to missed and is_incoming_call set to false. Use it to follow up, eg to schedule a call back:

        "conditions": ["stubpost.data._incoming_dial_data.call_data.event = 'missed'"]

      

call_data.reason says why the call was missed:

reason Meaning
caller_cancelled The caller hung up while it was ringing
answer_timeout The call wasn't answered within 25 seconds

Things to know

  • Phone audio. Phone calls are 8 kHz telephone audio (G.711), so the assistant hears and speaks at phone quality.
  • One call at a time per extension. A SIP connection takes one call at a time unless Stubber raises its limit. Other callers get a busy signal.
  • One voice call per stub. A stub can only have one live voice call. To take calls on more than one extension at once, have the stub that gets the calls create a stub per call.
  • Who is calling. from is the number or extension the phone system presents for the caller. It can be an internal extension, eg 101, or a full phone number.
  • Keypad presses (DTMF) are not passed on yet.

Data Structure

Feedback Action Data

Example data for an incoming call:

        "_incoming_dial_data": {
  "is_call_event": true,
  "is_incoming_call": true,
  "from": "0215550123",
  "from_name": "Joe Soap",
  "to": "801",
  "call_data": {
    "event": "incoming",
    "dialcalluuid": "238e63a5-e071-47d9-bac2-360b77fc5991",
    "sipconnectionuuid": "161bc664-3c90-4a1e-ba07-6d1f5438dfe4",
    "audio_format": { "type": "audio/pcma", "rate": 8000 },
    "answer_deadline": "2026-10-15T10:00:25.000Z"
  }
}

      

Example data for a missed call:

        "_incoming_dial_data": {
  "is_call_event": true,
  "is_incoming_call": false,
  "from": "0215550123",
  "from_name": "Joe Soap",
  "to": "801",
  "call_data": {
    "event": "missed",
    "dialcalluuid": "238e63a5-e071-47d9-bac2-360b77fc5991",
    "sipconnectionuuid": "161bc664-3c90-4a1e-ba07-6d1f5438dfe4",
    "audio_format": { "type": "audio/pcma", "rate": 8000 },
    "reason": "answer_timeout"
  }
}

      

Properties

is_call_event
boolean

true for every call event.


is_incoming_call
boolean

true for a ringing call to answer or reject, false for a missed call.


from
string

The caller's number or extension, as the phone system presents it.


from_name
string

The caller's name, when the phone system knows it. Otherwise null.


to
string

The extension that was called, i.e. the SIP connection's extension.


call_data.event
string

incoming or missed.


call_data.dialcalluuid
string

The id of the call. Pass it to answer_dial_call or reject_dial_call.


call_data.sipconnectionuuid
string

The SIP connection the call came in on.


call_data.audio_format
object

The call's audio format, audio/pcmu (μ-law) or audio/pcma (A-law) at 8000. Pass it to start_voice_chat as audio.format.


call_data.answer_deadline
string

incoming only. The ISO 8601 date-time by which the call must be answered.


call_data.reason
string

missed only. Why the call was missed: caller_cancelled or answer_timeout.

JSONSchema

The JSONSchema of the stubpost.data._incoming_dial_data is:

        {
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Incoming Dial Data",
  "type": "object",
  "properties": {
    "_incoming_dial_data": {
      "type": "object",
      "properties": {
        "is_call_event": { "type": "boolean", "description": "True for every call event." },
        "is_incoming_call": {
          "type": "boolean",
          "description": "True for a ringing call, false for a missed call."
        },
        "from": { "type": "string", "description": "The caller's number or extension." },
        "from_name": {
          "type": ["string", "null"],
          "description": "The caller's name, when known."
        },
        "to": { "type": "string", "description": "The extension that was called." },
        "call_data": {
          "type": "object",
          "properties": {
            "event": { "type": "string", "enum": ["incoming", "missed"] },
            "dialcalluuid": { "type": "string", "description": "UUID of the call." },
            "sipconnectionuuid": { "type": "string", "description": "UUID of the SIP connection." },
            "audio_format": {
              "type": "object",
              "properties": {
                "type": { "type": "string", "enum": ["audio/pcmu", "audio/pcma"] },
                "rate": { "type": "number", "enum": [8000] }
              },
              "required": ["type", "rate"]
            },
            "answer_deadline": {
              "type": "string",
              "description": "incoming only. When the call must be answered by."
            },
            "reason": {
              "type": "string",
              "enum": ["caller_cancelled", "answer_timeout"],
              "description": "missed only."
            }
          },
          "required": ["event", "dialcalluuid", "sipconnectionuuid", "audio_format"]
        }
      },
      "required": ["is_call_event", "is_incoming_call", "from", "to", "call_data"]
    }
  },
  "required": ["_incoming_dial_data"]
}