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 Name | Parameter Description | Parameter Type |
---|---|---|
MessageStatus | The current status of the message at the time of the callback. | string |
ErrorCode | If your message has failed or is undelivered, the error code may provide you with more information about what went wrong. | string |
MessageSid | The unique ID of this message. | string |
AccountSid | The unique ID of the project this message is associated with. | string |
From | The 'From' number of the message | string |
To | The 'To' number of the message | string |
Body | The body of the message | string |
NumMedia | The number of media files that were included with the message | integer |
NumSegments | The 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 Status | Description of Status |
---|---|
queued | The 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. |
sending | The message is currently being transmitted by SignalWire to the nearest carrier upstream in the network. |
sent | The nearest carrier upstream in the network has accepted the message. |
delivered | Confirmation of receipt of the message by the nearest carrier upstream in the network. |
undelivered | SignalWire has received notice from the nearest carrier upstream in the network that the message was not delivered. |
failed | SignalWire could not send the message. There is no charge for failed messages. |
receiving | SignalWire has received and is currently processing an inbound message. |
received | The message has been received by one of the numbers in your account. Applies to inbound messages only. |
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.
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 Name | Parameter Description | Parameter Type |
---|---|---|
ForwardedFrom | The number this call was forwarded from. | string |
CallerName | The name of the caller. Only available if Caller ID lookup is enabled. | string |
CallDuration | The duration, in seconds, of the finished call. Only present on the completed event. | integer |
RecordingUrl | The URL of the recorded audio call | string |
RecordingSid | The unique identifier for the audio recording | string |
RecordingDuration | The duration, in seconds, of the recording | integer |
CallbackSource | The source of the status callback | string |
Timestamp | The timestamp, in RFC 2822 format, of when the event occurred. | string |
SequenceNumber | The 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 |
CallSid | A unique identifier for the call. | string |
AccountSid | The unique ID of the project this call is associated with. | string |
From | The phone number that sent this call, in E.164 format. | string |
To | The phone number of the call recipient, in E.164 format. | string |
CallStatus | The status of the call. Can be one of the following values: ringing , in-progress , queued , failed , busy , no-answer , or completed . | string |
ApiVersion | The 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 |
Direction | An 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 |
ParentCallSid | A unique identifier for the call that created this call. | string |
AudioInMos | A 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 StatusCallback
URL 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 Status | Status Description |
---|---|
ringing | The call is ringing |
in-progress | The call was answered and is in progress. |
queued | The call is ready and in line to initiate. |
failed | The call could not be completed. Usually occurs when phone number does not exist. |
busy | The caller encountered a busy signal. |
no-answer | The call ended without an answer. |
completed | The call was answered and ended normally. |
canceled | The 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.
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.
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 Name | Parameter Description | Parameter Type |
---|---|---|
AccountSid | The unique ID of the project this call is associated with. | String |
CallSid | A unique identifier for the call. May be used to later retrieve this message from the REST API. | String |
RecordingSid | The unique identifier for the recording. | String |
RecordingUrl | The URL for the audio recording. | String |
RecordingStatus | The status of the recording. | String |
RecordingDuration | The duration, in seconds, of the recording. | Integer |
RecordingChannels | The number of channels in the recording. | Integer |
RecordingSource | The 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 Status | Status Description |
---|---|
in-progress | This status occurs as soon as the call recording has begun |
completed | This status occurs when the file is available for access. |
absent | The 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 Title | Parameter Description | Parameter Type |
---|---|---|
TranscriptionSid | The unique, 34 character ID of the transcription. | String |
TranscriptionText | The text of the transcription. | String |
TranscriptionStatus | The status of the transcription (completed or failed). | String |
TranscriptionUrl | The URL for the transcription's REST API resource. | String |
RecordingSid | The unique, 34 character identifier for the recording from which the transcription was generated from. | String |
RecordingUrl | The 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 Name | Parameter Description | Parameter Type |
---|---|---|
CallSid | A unique identifier for the call. May be used to later retrieve this message from the REST API. | String |
AccountSid | The unique ID of the project this call is associated with. | String |
From | The From number in E.164 format. | String |
To | The To number in E.164 format. | String |
Timestamp | The timestamp of the call creation date/time. | String |
CallStatus | The status of the call. In this case it will either be failed or completed . | String |
CallDuration | The duration, in seconds, of the call. | String |
AudioInMos | The 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.
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.
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()