Skip to main content

Status Callbacks Overview

Status callbacks allow you to set up a form of event tracking that sends data to the webhook you specify on every status change. The type of data depends on the product: SMS status callbacks will show message status changes whereas recording status callbacks will update when a recording is completed.

In the sections below is comprehensive data on each type of status callback and examples of how to use them. You could easily build a system to monitor your throughput or delivery rate with SMS, push calls logs to your CRM, or simply send recordings via SMS when they are created. There are numerous possibilities for what to do with this data.

Testing Status Callbacks

If you are building a status callback application and would like a full printout of all of the possible parameters you can use in your application, a helpful tool is webhook site. You can copy the unique URL on webhook site to use as your status callback url and you will see all the headers, form data, and query strings if there are any. This is a good way to see examples of the data returned as well as get exact parameter names.

We recommend using ngrok as a tunnel for local testing if you don't want to host this script on your own server. You can read more about ngrok here.

To run these exact examples, you will need the Flask framework and Python SignalWire SDK downloaded. However, you could recreate them or write your own example using any of the SignalWire SDKs and a web framework.

One of the most important parts of voice and SMS status callbacks is the error code for when things go amiss. You can read more about the possible error codes and what they mean here.

SMS Status Callbacks

One of the most popularly used forms of status callbacks is the SMS status callback. As messaging can often be high volume, it's crucial to be able to keep track of how your messaging campaigns are performing and handle any errors that start to pop up.

SMS status callbacks will allow SignalWire to make an HTTP request to your specified callback URL with the message status and error code (if present). Your app can use these parameters to keep track of delivery status, calculate the delivery rate, monitor throughput, monitor for emerging errors, and much more.

SMS Status Callback Parameter

The following parameters will be posted via an HTTP request to your webhook - you can use one or all of them depending on what type of information you are looking for.

Parameter NameParameter DescriptionParameter Type
MessageStatusThe current status of the message at the time of the callback.string
ErrorCodeIf your message has failed or is undelivered, the error code may provide you with more information about what went wrong.string
MessageSidThe unique ID of this message.string
AccountSidThe unique ID of the project this message is associated with.string
FromThe 'From' number of the messagestring
ToThe 'To' number of the messagestring
BodyThe body of the messagestring
NumMediaThe number of media files that were included with the messageinteger
NumSegmentsThe number of segments that make up the entire message. If the body of the message is larger than 160 GSM-7 characters or 70 UCS-2 characters, it will automatically be broken up into smaller messages and annotated to attempt proper reconstruction on the recipient handset. Read more about this here.integer

How to Set SMS Status Callbacks

You can set up SMS status callbacks in your API request for an outgoing message by using the StatusCallback parameter.

There are 8 possible SMS statuses that are expanded upon below.

Message StatusDescription of Status
queuedThe API request to send this message was processed successfully, and the message is currently waiting to be sent out. Read more about our queuing system here.
sendingThe message is currently being transmitted by SignalWire to the nearest carrier upstream in the network.
sentThe nearest carrier upstream in the network has accepted the message.
deliveredConfirmation of receipt of the message by the nearest carrier upstream in the network.
undeliveredSignalWire has received notice from the nearest carrier upstream in the network that the message was not delivered.
failedSignalWire could not send the message. There is no charge for failed messages.
receivingSignalWire has received and is currently processing an inbound message.
receivedThe message has been received by one of the numbers in your account. Applies to inbound messages only.
"Sent" vs "Delivered

SignalWire can’t and won’t show a message as Delivered unless we can 100% confirm delivery to the end network by getting a Delivery Receipt (DLR) from the receiving carrier. A status of Sent means that the message left SignalWire successfully and reached our downstream peer. Once we receive the Delivery Receipt indicating that the message entered the end carrier's network, it switches to Delivered.

If you see a message with a status of Delivered that did not reach the end handset, feel free to open a support ticket with the message SID for us to escalate the example to our carrier partners to find out why the MNOs (Verizon, AT&T, T-Mobile, etc) marked it as Delivered.

Some carriers may send delayed DLRs, and others may not send DLRs at all. Providers do not currently allow for any DLRs for MMS, so you will only ever see the status Sent.

You can also fetch messages individually or delete messages using our retrieve message API endpoint and delete message API endpoint.

SMS Status Callbacks Application Example

Below is an example of a simple delivery status tracker that could begin running before a message campaign goes out and end when a message campaign ends. While the app is running, it will log the status change event of every single message to the console with the following information: MessageStatus, MessageSid, and ErrorCode. This will happen for every outbound message in the same project that includes this script as the StatusCallback URL.

When a message returns a status of failed or undelivered, it will be added to a table that keeps track of all unsuccessful messages along with their MessageSid, MessageStatus, ErrorCode, ErrorMessage, DateSent, To, From, and Body. After every failed/undelivered message, this table will be printed so the updated list can be seen.

When the messaging campaign is over and the Flask app is closed, the whole table will be exported to CSV so that failed/undelivered messages can be easily investigated.

In the output below, you can see successful messages logged to the console as well as the table being updated when messages fail to deliver for any reason.

A screenshot of the output. Successful messages or logged to the console. Whenever a message cannot be delivered, the table is updated with the relevant values.
The end result.
from flask import Flask, request
import logging
import pandas as pd
from signalwire.rest import Client as signalwire_client
import atexit

logging.basicConfig(level=logging.INFO)
app = Flask(__name__)

# create an empty array to keep track of all of our undelivered
# or failed messages during the time this app is run
undeliveredArray = []

# define actions to take when flask app is closed
# export dataframe of all failed or undelivered
# messages to CSV with added detail
def onExitApp(dataframe):
dataframe.to_csv('failedAndUndeliveredMessages.csv', index=False, encoding='utf-8')
print('SMS Callback App Exited')

# authenticate the SignalWire client
client = signalwire_client("ProjectID",
"AuthToken",
signalwire_space_url='YOUR_SPACE.signalwire.com')

# define route for SMS status callbacks to be posted to
@app.route("/smsErrorTracker", methods=['POST'])
def newest_message():
# get message sid, message status, and error code (if it exists) from callback parameters
# if they don't exist, set to None
message_sid = request.values.get('MessageSid', None)
message_status = request.values.get('MessageStatus', None)
error_code = request.values.get('ErrorCode', None)

# log every message that comes in to console
logging.info('SID: {}, Status: {}, ErrorCode: {}'.format(message_sid, message_status, error_code))

# if the message is undelivered or failed, use message SID to fetch additional data
# about the failed message
if (message_status == "undelivered" or message_status == "failed"):
message = client.messages(message_sid).fetch()

# add identifying data from newest message to undelivered array
undeliveredArray.append([message_sid, message_status, error_code, message.error_message, message.date_sent, message.to, message.from_, message.body])
# insert array into dataframe with columns for easier reading
df = pd.DataFrame(undeliveredArray, columns=('Message Sid', 'Message Status', 'Error Code', 'Error Message', 'Date Sent', 'To', 'From', 'Body'))
# print dataframe to string for nicer formatting and set dataframe to our parameter in function for handling app exit
print(df.to_string())
atexit.register(onExitApp, dataframe=df)

# return 200OK
return ('', 200)

if __name__ == "__main__":
app.run()

Voice Status Callbacks

Voice status callbacks allow you to get an advanced view of how your call center is performing in real-time as well as key analytics that can be used to track performance. Callback applications could be as simple as tracking failures or as complex as full-scale real-time monitoring of in-progress calls.

Voice status callbacks will allow SignalWire to make an HTTP request to your specified callback URL with the ForwardedFrom, CallerName, CallDuration, RecordingURL, RecordingSid, RecordingDuration, Timestamp, CallbackSource, 'AudioInMos, and SequenceNumber`. Your app can use these parameters to keep track of any associated recordings, caller info, average times of calls, or simply monitoring for failures.

Voice Status Callback Parameter

The following parameters will be posted via an HTTP request to your webhook - you can use one or all of them depending on what type of information you are looking for.

Parameter NameParameter DescriptionParameter Type
ForwardedFromThe number this call was forwarded from.string
CallerNameThe name of the caller. Only available if Caller ID lookup is enabled.string
CallDurationThe duration, in seconds, of the finished call. Only present on the completed event.integer
RecordingUrlThe URL of the recorded audio callstring
RecordingSidThe unique identifier for the audio recordingstring
RecordingDurationThe duration, in seconds, of the recordinginteger
CallbackSourceThe source of the status callbackstring
TimestampThe timestamp, in RFC 2822 format, of when the event occurred.string
SequenceNumberThe order in which events occur. Starts at 0. Although events are fired in order, they each take time and may not appear in the order you expect.integer
CallSidA unique identifier for the call.string
AccountSidThe unique ID of the project this call is associated with.string
FromThe phone number that sent this call, in E.164 format.string
ToThe phone number of the call recipient, in E.164 format.string
CallStatusThe status of the call. Can be one of the following values: ringing, in-progress, queued, failed, busy, no-answer, or completed.string
ApiVersionThe version of the SignalWire API. Incoming calls use the API version placed on the number called. Outgoing calls use the version of the REST API request.string
DirectionAn identifier to describe the direction of the call:
outbound-dial: calls launched through the verb
outbound-api: calls launched through the REST API
inbound: for inbound calls
string
ParentCallSidA unique identifier for the call that created this call.string
AudioInMosA mean opinion score on a scale of 1-5 that helps determine audio quality.string

How to Set Voice Status Callbacks

You can use the StatusCallback while creating a call via the API or using Dial with Number, SIP, or Conference to get notifications with these parameters when the call is completed.

Alternatively, you can get every call progress event posted to your StatusCallbackURL parameter along with information about the call state as well as several helpful request parameters by using StatusCallbackEvent.

Once a call is created, it progresses through multiple states until it is finally completed. There are 8 possible call statuses that are expanded upon below:

Call StatusStatus Description
ringingThe call is ringing
in-progressThe call was answered and is in progress.
queuedThe call is ready and in line to initiate.
failedThe call could not be completed. Usually occurs when phone number does not exist.
busyThe caller encountered a busy signal.
no-answerThe call ended without an answer.
completedThe call was answered and ended normally.
canceledThe REST API canceled the call while it was ringing or queued.

You can also fetch messages individually or delete calls using our retrieve call API endpoint and delete call API endpoint.

Voice Status Callbacks Application Example

Below is an example of a simple call status tracker that will log every status change event ('initiated' to 'ringing', 'ringing' to 'answered', etc) to the console along with the CallSID, CallStatus, Timestamp, and Direction. If a call has reached an end-stage state, i.e. completed, canceled, no-answer, or busy`, it will be added to our table of call records. This table will exclude failed calls and in-progress calls so as to not have duplicate records.

If a call fails, it will be added to a separate table containing failed calls along with relevant data to investigate later. If at any point the number of failed calls in the table reaches 100, the table will be downloaded to CSV and an SMS alert will be sent out to notify whoever would be in charge of dealing with the failures. After the CSV has been downloaded and the alert has been sent, we will clear the failed calls array so it can begin appending new failed calls again.

After every call is appended to either of the tables, the tables will reprint so that it's easy to see the updated list of completed and failed calls. Below you can see the status alerts printed out for each call in red and the table that contains all of the completed calls along with relevant data.

A screenshot of the log, showing the tables reprinting with the updated list of completed and failed calls.
Screenshot.

For the example's sake, the script was changed to alert of call failures at 5 calls instead of 100. However, you can see below the table of 5 failed calls and console log statement confirming that the CSV was downloaded and the SMS alert was sent.

A screenshot showing the table of 5 failed calls and console log statement confirming that the CSV was downloaded and the SMS alert was sent.
Screenshot.
from flask import Flask, request
import logging
import pandas as pd
from signalwire.rest import Client as signalwire_client

logging.basicConfig(level=logging.INFO)
app = Flask(__name__)

# create empty arrays to store our call records - one for failed only and one to handle all other end stage statuses (not queued, not ringing, not answered, etc)
failedCallArray = []
allCallArray = []

# authenticate the SignalWire client
client = signalwire_client("ProjectID",
"AuthToken",
signalwire_space_url='example-space.signalwire.com')


@app.route("/CallStatus", methods=['POST'])
def incoming_calls():
# grab incoming parameters posted to webhook and assign them variables
call_sid = request.values.get('CallSid', None)
call_status = request.values.get('CallStatus', None)
event_timestamp = request.values.get('Timestamp', None)
recording_url = request.values.get('RecordingUrl', None)
call_direction = request.values.get('Direction', None)
to_number = request.values.get('To', None)
from_number = request.values.get('From', None)

# log some basic information to print to console for EVERY status change
logging.info('SID: {}, Status: {}, Timestamp: {}, Direction: {}'.format(call_sid, call_status, event_timestamp, call_direction))

# create a separate array for all end stage statuse updates and add them to our call log table, display updated table
if (call_status != 'ringing' and call_status != 'initiated' and call_status != 'answered' and call_status != 'in-progress' and call_status != 'queued' and call_status != 'failed'):
allCallArray.append([call_sid, call_status, event_timestamp, call_direction, to_number, from_number, recording_url])
adf = pd.DataFrame(allCallArray, columns=('Call SID', 'Call Status', 'Event Timestamp', 'Call Direction', 'To Number', 'From Number',
'Recording URL (if present)'))
print("All Calls")
print(adf.to_string())

# if call status is failed, log call record to call failure table and display table
if (call_status == "failed"):
failedCallArray.append([call_sid, call_status, event_timestamp, call_direction, to_number, from_number, recording_url])
df = pd.DataFrame(failedCallArray, columns=('Call SID', 'Call Status', 'Event Timestamp', 'Call Direction', 'To Number', 'From Number', 'Recording URL (if present)'))
print("Failed Calls")
print(df.to_string())

# if the number of failed calls is over 100
if len(failedCallArray) > 100:
# download call logs with necessary data to CSV and send an sms alert of the failures
df.to_csv('failedCallReport.csv', index=False, encoding='utf-8')
m = client.messages.create(
body='Call Failure Alert! You have received 100 call failures. '
'A CSV of the failures has been downloaded and the failure database will now reset.',
from_='+1xxxxxxxxxx',
to='+1xxxxxxxxxx')
print("CSV of Failed Calls Downloaded & Message Alert Sent")

# clear array to start adding fresh logs again
while len(failedCallArray) > 0:
failedCallArray.pop()

# Return 200 OK
return ('', 200)

if __name__ == "__main__":
app.run()

Recording Status Callbacks

A vital feature for working with inbound/outbound calls is call recording. If you're actively recording a call, the recording may not be available immediately. When there is a high volume of calls, it can be difficult to individually retrieve/handle each recording within your portal.

Recording status callbacks will allow SignalWire to make an HTTP request to your specified callback URL with the recording file as well as some other additional parameters. Your app can use these parameters to handle the recording whether by uploading the recording somewhere, pushing to external storage, sending via email, or maybe even using SignalWire SMS to forward the recording URL.

Recording Status Callback Parameters

The following parameters will be posted via an HTTP request to your webhook - you can use one or all of them depending on what type of information you are looking for.

Parameter NameParameter DescriptionParameter Type
AccountSidThe unique ID of the project this call is associated with.String
CallSidA unique identifier for the call. May be used to later retrieve this message from the REST API.String
RecordingSidThe unique identifier for the recording.String
RecordingUrlThe URL for the audio recording.String
RecordingStatusThe status of the recording.String
RecordingDurationThe duration, in seconds, of the recording.Integer
RecordingChannelsThe number of channels in the recording.Integer
RecordingSourceThe type of call that initiated the recording.String

How to Set Recording Status Callbacks & Recording Status Callback Events

You can set up recording status callbacks in your API request for an outgoing call or by using Dial, Conference, or Record.

You can use recordingStatusCallbackEvent to specify multiple events that you want your callback URL to receive HTTP requests for, but if you do not specify it will default to completed.

Call Recordings have three possible statuses.

Call StatusStatus Description
in-progressThis status occurs as soon as the call recording has begun
completedThis status occurs when the file is available for access.
absentThe call recording was too short to be processed or the call was silent so no audio was detected.

You can also fetch recordings individually or delete recordings using our [retrieve recording API endpoint](pathname:///compatibility-API endpoint/rest/retrieve-a-recording) and delete recording API.

Recording Status Callback Application Example

Below is an example of an application that could be used to process incoming recordings and forward them to a specific person. We need to use request.form.get('ParameterName') in order to gather the CallSid and RecordingUrl parameters and store them in their own variables. If you want to include more parameters either to print to console or include in the message, you can gather them using the same format here.

We then create a SignalWire client object with our project details and authentication. All that's left there is to create a message object and send all of the necessary information within the Body with the To number being the end destination number and the From number being a SignalWire number.

from flask import Flask, request
from signalwire.rest import Client as signalwire_client

app = Flask(__name__)


@app.route("/sendRecording", methods=["POST"])
def message():
# accept incoming parameters and store them. Feel free to add any extra parameters that you would like to print to
# to console or add to your message. This example will show CallSID and recording URL.
call_sid = request.form.get('CallSid')
recording_url = request.form.get('RecordingUrl')

# create a client object connected to our account & project
client = signalwire_client("ProjectID", "AuthToken", signalwire_space_url = 'YOURSPACE.signalwire.com')

# create a text message and send ourselves the text
m = client.messages.create(
body='You have received a voicemail. Listen to the recording here: "' + recording_url +
'". The Call SID is ' + call_sid,
from_='+1xxxxxxxxxx',
to='+1xxxxxxxxxx'
)
return recording_url

Transcription Status Callbacks

If you are already using recordings, you may also want to transcribe them as well. Transcription callbacks will allow SignalWire to make an HTTP request to your specified callback URL with the transcription text as well as some other additional parameters. Your app can use these parameters to handle the transcription by uploading to your CRM, sending via email, or maybe even using SignalWire SMS to forward the body of the transcription.

Transcription Status Callback Parameters

The following parameters will be posted via an HTTP request to your webhook - you can use one or all of them depending on what type of information you are looking for.

Parameter TitleParameter DescriptionParameter Type
TranscriptionSidThe unique, 34 character ID of the transcription.String
TranscriptionTextThe text of the transcription.String
TranscriptionStatusThe status of the transcription (completed or failed).String
TranscriptionUrlThe URL for the transcription's REST API resource.String
RecordingSidThe unique, 34 character identifier for the recording from which the transcription was generated from.String
RecordingUrlThe URL for the audio recording from which the transcription was generated from.String

How to Set Transcription Status Callbacks

You can set up transcription status callbacks by using Record and setting transcribe to True and transcribeCallback to your webhook URL.

You can also fetch transcriptions individually or delete transcriptions using our retrieve transcription API endpoint and delete transcription API endpoint.

Transcription Status Callback Application Example

Below is an example of an application that could be used for transcription status callbacks to process incoming transcriptions and forward them to someone's phone number. We need to use request.form.get('ParameterName') in order to gather the CallSid, TranscriptionText, and From number parameters and store them in their own variables. If you want to include more parameters either to print to console or include in the message, you can gather them using the same format here.

We then create a SignalWire client object with our project details and authentication. All that's left there is to create a message object and send all of the necessary information within the Body with the To number being the end destination number and the From number being a SignalWire number.

@app.route("/message", methods=["POST"])
def message():
# gather necessary paramters and store them in an accessible variable
call_sid = request.form.get('CallSid')
transcription_text = request.form.get('TranscriptionText')
from_number = request.form.get('From')

# create a client object connected to our account & project
client = signalwire_client("ProjectID", "AuthToken", signalwire_space_url = 'YOURSPACE.signalwire.com')

# create a text message and the text with necessary parameters
m = client.messages.create(
body='You have received a voicemail from the number ' + from_number +
'. The voicemail transcription is as follows: "' + transcription_text +
'" and the Call SID is ' + call_sid,
from_='+1xxxxxxxxxx',
to='+1xxxxxxxxxx'
)
return transcription_text

Inbound Call Status Callbacks

Inbound Call status callbacks are enabled at the phone number level and focus specifically on tracking inbound calls to that number. By default, your webhook is only called when the call is either completed or if it fails. This would be best suited for tracking the inbound calls in a call center to keep track of success rate, call quality, total calls, etc.

Inbound Call Status Change Callback Parameters

The following parameters will be posted via an HTTP request to your webhook - you can use one or all of them depending on what type of information you are looking for.

Parameter NameParameter DescriptionParameter Type
CallSidA unique identifier for the call. May be used to later retrieve this message from the REST API.String
AccountSidThe unique ID of the project this call is associated with.String
FromThe From number in E.164 format.String
ToThe To number in E.164 format.String
TimestampThe timestamp of the call creation date/time.String
CallStatusThe status of the call. In this case it will either be failed or completed.String
CallDurationThe duration, in seconds, of the call.String
AudioInMosThe score that represents the quality of the call (1-5).String

How to Set Inbound Call Status Callbacks

To set an inbound call status callback, navigate to the Phone Numbers tab within your SignalWire Space on the lefthand sidebar. You can then click a specific phone number for which you want this feature enabled and click into Phone Number Settings. Set Status Change Webhook to your webhook and save. All future inbound calls will now result in an HTTP request to your webhook.

A screenshot of the Phone Numbers tab in the SignalWire Space, in the Edit Settings page of a given phone number. 'Status Change Webhook' has been set to the desired webhook URL.
Screenshot.

Inbound Call Status Callback Application Example

Below is an example of a simple inbound call tracker that will log every incoming HTTP request (when any inbound call is completed or failed). We will add it to a table of call records and print this out each time so anyone viewing can watch the current progress. We will also perform some calculations to keep an eye on the total number of calls, total call duration in minutes, and the average audio quality of all the calls. When the app is exited, we will export the full log to CSV for easier review and storage of daily records. Below you can see what is logged to the console each time a new call comes in.

A screenshot of the output of the inbound call tracker. The output displays the current total number of calls, call duration in minutes, and average audio quality. Beneath these stats, a table organizes call records by Call SID, Status, Event Timestamp, To and From Numbers, Call Duration, and Audio in MOS.
Screenshot.
from flask import Flask, request
import numpy as np
import pandas as pd
from signalwire.rest import Client as signalwire_client
import atexit

# instantiate flask app and signalwire client
app = Flask(__name__)

client = signalwire_client("ProjectID", "AuthToken",
signalwire_space_url='YOURSPACE.signalwire.com')
# set up an array to store call records
callArray = []

# define actions to take on exit - export dataframe to CSV and print goodbye message to console
def onExitApp(dataframe):
dataframe.to_csv('Calls.csv', index=False, encoding='utf-8')
print("Inbound callback app exited, goodbye!")

# create empty global dataframe and register onExitApp using atexit()
df = pd.DataFrame()
atexit.register(onExitApp, dataframe = df)

# set up our route for incoming call http requests
@app.route("/incomingCalls", methods=['POST'])
def incoming_calls():
# store incoming request parameters in variables
call_sid = request.values.get('CallSid')
call_status = request.values.get('CallStatus')
event_timestamp = request.values.get('Timestamp')
to_number = request.values.get('To', None)
from_number = request.values.get('From', None)
call_duration = request.values.get('CallDuration', None)
audio_in_mos = request.values.get('AudioInMos', None)

# append call data to array
callArray.append([call_sid, call_status, event_timestamp, to_number, from_number, call_duration, audio_in_mos])

# create dataframe with headers from call array
df = pd.DataFrame(callArray, columns=('Call SID', 'Call Status', 'Event Timestamp', 'To Number', 'From Number', 'Call Duration(s)', 'Audio in MOS'))

# convert all None values to NaN so it can be used in calculations
# convert duration and audio mos score from strings to int & float
df = df.fillna(value=np.nan)
df['Call Duration(s)'] = df['Call Duration(s)'].astype(float)
df['Audio in MOS'] = df['Audio in MOS'].astype(float)

# perform some calculations to print up to date live call stats
totalCalls = len(callArray)
totalCallDuration = df['Call Duration(s)'].sum(skipna=True)
totalCallDurationinMinutes = float(totalCallDuration / 60)
roundedMinutes = round(totalCallDurationinMinutes, 2)
avgAudioMOS = df['Audio in MOS'].mean(skipna=True)

print("Current Inbound Call Stats: ")
print('----------------------------')
print('Total # of Calls: ' + str(totalCalls))
print("Call Duration in Minutes: " + str(roundedMinutes))
print('Average Audio Quality (measured in MOS): ' + str(avgAudioMOS))
print(df.to_string())

return ('', 200)

if __name__ == "__main__":
app.run()