This guide shows you how to use Twilio's status callbacks to track changes of the message status of outbound messages you send with Programmable Messaging.
Message status changes occur throughout the lifecycle of a message from creation, through sending, to delivery, and even read receipt for supporting messaging channels.
The guide focuses on outbound messages created using the Message Resource of the Programmable Messaging REST API and covers the necessary considerations when using a Messaging Service.
Before you dive into this guide, make sure you're familiar with the following:
Note: The code samples in this guide require some local setup steps. Select your language of choice below to learn how to set up your development environment.
Let's get started!
Tracking the message status of an outbound message is a two-step process
In order to track the message status of an outbound message, you must first create an API endpoint that:
A status callback URL must contain a valid hostname. Underscores are not allowed.
How you implement your status callback endpoint depends on your use case and technology preferences. This may mean you
Twilio sends status callback requests as HTTP POST
requests with a Content-Type
of application/x-www-form-urlencoded
.
The properties included in Twilio's request to the StatusCallback URL vary by messaging channel and event type and are subject to change.
Twilio occasionally adds new properties without advance notice.
When integrating with status callback requests, it is important that your implementation is able to accept and correctly run signature validation on an evolving set of parameters.
Twilio strongly recommends using the signature validation methods provided in the Helper Libraries and not implementing your own signature validation.
In a status callback request, Twilio provides a subset of the standard request properties, and additionally MessageStatus
and ErrorCode
. These properties are described in the table below.
Property | Description |
---|---|
MessageStatus | The status of the Message resource at the time the status callback request was sent. |
ErrorCode | If an error occurred (i.e. the MessageStatus is failed or undelivered ), this property provides additional information about the failure. |
For example, a status callback request sent when the Message resource for an outbound SMS changes status
to sent
, may contain the following content:
1"AccountSid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"2"From": "+15017250604"3"MessageSid": "SM1342fe1b2c904d1ab04f0fc7a58abca9"4"MessageStatus": "sent"5"SmsSid": "SM1342fe1b2c904d1ab04f0fc7a58abca9"6"SmsStatus": "sent"
For most SMS/MMS Messages that have a Status
of delivered
or undelivered
, Twilio's request to the StatusCallback
URL contains an additional property:
Property | Description |
---|---|
RawDlrDoneDate | This property is a passthrough of the Done Date included in the DLR (Delivery Receipt) that Twilio received from the carrier. The value is in YYMMDDhhmm format.
|
If the Message resource uses WhatsApp or another messaging channel, Twilio's request to the StatusCallback
URL contains additional properties. These properties are listed in the table below.
Property | Description |
---|---|
ChannelInstallSid | The Installed Channel SID that was used to send this message |
ChannelStatusMessage | The error message returned by the underlying messaging channel if Message delivery failed. This property is present only if the Message delivery failed. |
ChannelPrefix | The channel-specific prefix identifying the messaging channel associated with this Message |
EventType | This property contains information about post-delivery events. If the channel supports read receipts (currently WhatsApp only), this property's value is READ after the recipient has read the message. |
You may want to explore how status callback requests behave before working through your actual implementation. A light-weight way to accomplish this goal is to use Twilio Serverless Functions and inspect the status callbacks in the Console using the Function Editor's debugging feature.
status-callback-prototyping
.
/message-status
with the following handler code:
1// Log Status Callback requests23exports.handler = function(context, event, callback) {4console.log("Invoked with: ", event);5return callback(null, "OK");6};
By default your new serverless function is created as a protected endpoint, which means Twilio Serverless performs signature validation to ensure only valid Twilio requests invoke your handler.
https://status-callback-prototyping-1234.twil.io/message-status
.
You can now use your copied status callback URL in the next step of this guide: Step 2. Send a message with status callback URL.
Your response to Twilio's status callback request should have an HTTP status code of 200
(OK). No response content is required.
What your status callback handler should do when receiving a status callback request, depends on your use case.
The following simplified web application illustrates how you could log the MessageSid
and MessageStatus
of outbound messages as they move through their lifecycle.
Status callback requests are HTTP requests and are therefore subject to differences in latency caused by changing network conditions.
Status callback requests are sent in accordance with the message status transitions described in the guide Outbound Message Status in Status Callbacks. Some of these status transitions may occur in quick succession.
As a result, there is no guarantee that the status callback requests always arrive at your endpoint in the order they were sent.
You should bear this consideration in mind when implementing your status callback handler.
Read our guide Best Practices for Messaging Delivery Status Logging for advanced considerations when implementing a production-grade status logging solution.
1const http = require('http');2const express = require('express');3const bodyParser = require('body-parser');45const app = express();67app.use(bodyParser.urlencoded({ extended: true }));89app.post('/message-status', (req, res) => {10const messageSid = req.body.MessageSid;11const messageStatus = req.body.MessageStatus;1213console.log(`SID: ${messageSid}, Status: ${messageStatus}`);1415res.sendStatus(200);16});1718http.createServer(app).listen(1337, () => {19console.log('Express server listening on port 1337');20});
In Step 1 you implemented a status callback handler which is publicly available at your status callback URL.
In this step you learn how to ensure Twilio sends status callback requests to your status callback URL for outbound messages.
How you do this may depend on whether you use a Messaging Service to send messages or not.
For Twilio to send status callback requests, you need to provide your status callback URL in the StatusCallback
parameter of each message for which you want to track the MessageStatus
.
To get the following code sample to run, replace the following information:
From
phone number with
one of your Twilio numbers
To
number with your mobile number
StatusCallback
URL with your status callback URL
1// Download the helper library from https://www.twilio.com/docs/node/install2const twilio = require("twilio"); // Or, for ESM: import twilio from "twilio";34// Find your Account SID and Auth Token at twilio.com/console5// and set the environment variables. See http://twil.io/secure6const accountSid = process.env.TWILIO_ACCOUNT_SID;7const authToken = process.env.TWILIO_AUTH_TOKEN;8const client = twilio(accountSid, authToken);910async function createMessage() {11const message = await client.messages.create({12body: "McAvoy or Stewart? These timelines can get so confusing.",13from: "+15017122661",14statusCallback: "http://example.com/MessageStatus",15to: "+15558675310",16});1718console.log(message.body);19}2021createMessage();
1{2"account_sid": "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",3"api_version": "2010-04-01",4"body": "McAvoy or Stewart? These timelines can get so confusing.",5"date_created": "Thu, 24 Aug 2023 05:01:45 +0000",6"date_sent": "Thu, 24 Aug 2023 05:01:45 +0000",7"date_updated": "Thu, 24 Aug 2023 05:01:45 +0000",8"direction": "outbound-api",9"error_code": null,10"error_message": null,11"from": "+15017122661",12"num_media": "0",13"num_segments": "1",14"price": null,15"price_unit": null,16"messaging_service_sid": "MGaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",17"sid": "SMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",18"status": "queued",19"subresource_uris": {20"media": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Messages/SMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Media.json"21},22"tags": {23"campaign_name": "Spring Sale 2022",24"message_type": "cart_abandoned"25},26"to": "+15558675310",27"uri": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Messages/SMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json"28}
Messaging Services can be configured to have a service-level Delivery Status Callback. When creating a new Messaging Service in Console, you can specify this service-level Delivery Status Callback in Step 3 - Set up Integrations.
You can use the status callback URL from Step 1 of this guide for this Delivery Status Callback integration. If you do so, you do not have to provide the status callback URL as a message-specific parameter.
Alternatively, you can provide a message-specific status callback URL in the StatusCallback
parameter for a message created with the Messaging Service.
Which of these two options is more appropriate depends on your use case.
To get the following code sample to run, replace the following information:
MessagingServiceSid
with the SID of one of your Messaging Services that has a Deliver Status Callback integration configured in Console
To
number with your mobile number
1// Download the helper library from https://www.twilio.com/docs/node/install2const twilio = require("twilio"); // Or, for ESM: import twilio from "twilio";34// Find your Account SID and Auth Token at twilio.com/console5// and set the environment variables. See http://twil.io/secure6const accountSid = process.env.TWILIO_ACCOUNT_SID;7const authToken = process.env.TWILIO_AUTH_TOKEN;8const client = twilio(accountSid, authToken);910async function createMessage() {11const message = await client.messages.create({12body: "McAvoy or Stewart? These timelines can get so confusing.",13messagingServiceSid: "MG9752274e9e519418a7406176694466fa",14to: "+15558675310",15});1617console.log(message.body);18}1920createMessage();
1{2"account_sid": "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",3"api_version": "2010-04-01",4"body": "McAvoy or Stewart? These timelines can get so confusing.",5"date_created": "Thu, 24 Aug 2023 05:01:45 +0000",6"date_sent": "Thu, 24 Aug 2023 05:01:45 +0000",7"date_updated": "Thu, 24 Aug 2023 05:01:45 +0000",8"direction": "outbound-api",9"error_code": null,10"error_message": null,11"from": "+14155552345",12"num_media": "0",13"num_segments": "1",14"price": null,15"price_unit": null,16"messaging_service_sid": "MG9752274e9e519418a7406176694466fa",17"sid": "SMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",18"status": "queued",19"subresource_uris": {20"media": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Messages/SMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Media.json"21},22"tags": {23"campaign_name": "Spring Sale 2022",24"message_type": "cart_abandoned"25},26"to": "+15558675310",27"uri": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Messages/SMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json"28}
If your Messaging Service has a service-level Delivery Status Callback configured in the Console and you provide a messages-specific StatusCallback
URL as shown in the next code sample, Twilio sends the status callback requests to the message-specific StatusCallback
URL.
To get the following code sample to run, replace the following information:
MessagingServiceSid
with the SID of one of your Messaging Services
To
number with your mobile number
StatusCallback
URL with your status callback URL
1// Download the helper library from https://www.twilio.com/docs/node/install2const twilio = require("twilio"); // Or, for ESM: import twilio from "twilio";34// Find your Account SID and Auth Token at twilio.com/console5// and set the environment variables. See http://twil.io/secure6const accountSid = process.env.TWILIO_ACCOUNT_SID;7const authToken = process.env.TWILIO_AUTH_TOKEN;8const client = twilio(accountSid, authToken);910async function createMessage() {11const message = await client.messages.create({12body: "McAvoy or Stewart? These timelines can get so confusing.",13messagingServiceSid: "MG9752274e9e519418a7406176694466fa",14statusCallback: "http://example.com/MessageStatus",15to: "+15558675310",16});1718console.log(message.body);19}2021createMessage();
1{2"account_sid": "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",3"api_version": "2010-04-01",4"body": "McAvoy or Stewart? These timelines can get so confusing.",5"date_created": "Thu, 24 Aug 2023 05:01:45 +0000",6"date_sent": "Thu, 24 Aug 2023 05:01:45 +0000",7"date_updated": "Thu, 24 Aug 2023 05:01:45 +0000",8"direction": "outbound-api",9"error_code": null,10"error_message": null,11"from": "+14155552345",12"num_media": "0",13"num_segments": "1",14"price": null,15"price_unit": null,16"messaging_service_sid": "MG9752274e9e519418a7406176694466fa",17"sid": "SMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",18"status": "queued",19"subresource_uris": {20"media": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Messages/SMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Media.json"21},22"tags": {23"campaign_name": "Spring Sale 2022",24"message_type": "cart_abandoned"25},26"to": "+15558675310",27"uri": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Messages/SMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json"28}
Now that you know how to track the message status of your outbound messages, check out the resources below for additional information and related Twilio product documentation: