Frequently Asked Questions
Twilio's status callbacks (webhooks) notify your application about message status changes. You need to configure a webhook endpoint in your application to receive these real-time updates, which include delivery, failure, or in-transit statuses. This enables better communication and debugging.
A Twilio status callback is a webhook that Twilio sends to your application to provide updates on the delivery status of your SMS messages. These updates are sent as HTTP POST requests to a URL you specify, containing details like message SID and status (e.g., queued, sent, delivered, failed).
Twilio offers a reliable and programmable messaging API that simplifies sending SMS and receiving delivery status updates via webhooks. It handles the complexities of carrier networks, providing a robust foundation for SMS communication.
Use Twilio status callbacks whenever real-time tracking of SMS message delivery is important. This includes scenarios requiring user notifications, message debugging, delivery confirmation, and analytics on message success/failure rates.
Yes, this article provides a comprehensive guide on integrating Twilio's SMS delivery status callbacks into a RedwoodJS application. You'll set up a webhook endpoint, validate requests securely, and update message statuses in your database.
Set up a serverless function in your RedwoodJS API to act as the webhook endpoint. Then, configure your Twilio account to send status updates to this endpoint by providing its URL when sending messages or configuring it at the Messaging Service level in the Twilio Console.
The statusCallback
URL is the endpoint in your application that Twilio will send HTTP POST requests to with message delivery status updates. It must be a publicly accessible URL and is provided when sending the message or configured in your Twilio Messaging Service.
Validating Twilio webhook signatures is crucial for security. It ensures that incoming requests genuinely originated from Twilio and haven't been tampered with, protecting your application from fraudulent status updates.
Use the validateRequest
function from the Twilio Node.js helper library to verify the signature of incoming webhook requests. This function compares the signature in the x-twilio-signature
header with a calculated signature using your auth token and request details, ensuring authenticity.
The article recommends Prisma as the database toolkit with RedwoodJS, allowing for flexible schema definition, migrations, and data access. It supports PostgreSQL, SQLite, and MySQL as the underlying database.
Use ngrok
during local development to create a publicly accessible URL for your RedwoodJS API. This allows Twilio to send status callbacks to your local machine for testing before deployment.
Wrap your callback handler logic in a try...catch
block to handle potential database errors or missing parameters. Log errors for debugging, but return a 200 OK
response to Twilio even if errors occur after signature validation to prevent excessive retries.
The x-twilio-signature
header in Twilio webhook requests contains a cryptographic signature of the request. This signature is used to validate the request's authenticity and ensure it came from Twilio, preventing unauthorized access.
Use a tool like ngrok
to create a temporary public URL that forwards requests to your local development server. Configure this URL as your statusCallback
URL in Twilio, enabling you to test webhook handling locally.
Tracking SMS delivery status is essential for reliable messaging applications. Twilio status callbacks (webhooks) notify your application when message status changes—from queued to sent, delivered, or failed. This allows you to monitor delivery in real-time, handle failures, and provide users with accurate delivery updates.
This guide shows you how to implement Twilio SMS delivery status webhooks in your RedwoodJS application. You'll learn to set up a webhook endpoint, validate Twilio callback requests securely, and track message status using Prisma database integration.
Project Goals:
Technologies Used:
System Architecture:
statusCallback
URL pointing back to a specific endpoint on your RedwoodJS API.statusCallback
URL.Prerequisites:
ngrok
for testing webhooks locally during development.1. Setting Up Your RedwoodJS Project for SMS Tracking
Create a new RedwoodJS project if you don't have one:
Install Twilio Helper Library:
Navigate to the API side and install the official Twilio Node.js helper library:
Configure Environment Variables:
RedwoodJS uses
.env
files for environment variables. Create a.env
file in your project root if it doesn't exist:TWILIO_ACCOUNT_SID
/TWILIO_AUTH_TOKEN
: Find these on your Twilio Console dashboard. Keep these secret! Never commit them to version control.TWILIO_PHONE_NUMBER
: Your Twilio number for sending messages.API_BASE_URL
: The publicly accessible base URL where your RedwoodJS API runs. Use your ngrok forwarding URL during local development. In production, use your deployed application's domain. You need this to construct thestatusCallback
URL.Project Structure:
RedwoodJS organizes code into
web
(frontend) andapi
(backend) sides. You'll primarily work within theapi
directory:api/src/functions/
: For serverless function handlers (like your webhook endpoint).api/src/services/
: For business logic and interacting with third-party APIs (like Twilio).api/db/schema.prisma
: For database schema definition.api/src/lib/
: For shared utilities (like your Twilio client instance).2. Implementing SMS Delivery Status Tracking: Core Functionality
You need two main pieces: logic to send an SMS and specify the callback URL, and logic to receive and process the callback.
2.1. Create Database Schema to Store Message Status
Define a model in your Prisma schema to store message information, including its status.
Apply Migrations:
Generate and apply the database migration:
This creates the
Message
table in your database.Understanding Twilio Message Status Values:
Twilio status callbacks include the
MessageStatus
parameter indicating the current state of your message. The possible values are:queued
: Message is queued and waiting to be sentsending
: Message is currently being sent to the carriersent
: Message has been sent from Twilio to the carrierdelivered
: Carrier confirmed delivery to the recipient's deviceundelivered
: Carrier could not deliver the message to the devicefailed
: Message failed to send (API request failed or carrier rejected)Note: The
ErrorCode
field only appears in callback events forfailed
orundelivered
messages. Common error codes include:Billing Note: Starting September 30, 2024, Twilio charges a "Failed message processing fee" for most messages in
failed
status. See Twilio's Error and Warning Dictionary for a complete list of error codes.2.2. Creating a Twilio Client Instance
Initialize the Twilio client once and reuse it throughout your application.
2.3. Service for Sending SMS
Create a RedwoodJS service to encapsulate the logic for sending SMS messages via Twilio and creating the initial record in your database.
Implement the
sendSms
function in the generated service file.Explanation:
db
), Redwood's logger, and your configuredtwilioClient
andtwilioPhoneNumber
.statusCallbackUrl
: Construct the full URL for your webhook endpoint. It combines theAPI_BASE_URL
(which must be publicly accessible) with the conventional Redwood path for functions (/.redwood/functions/
) and your specific function name (twilioCallback
). SetAPI_BASE_URL
correctly for your environment.twilioClient.messages.create
: Call the Twilio API to send the message.body
,from
,to
: Standard message parameters.statusCallback
: Tell Twilio to send POST requests with status updates for this specific message to yourtwilioCallback
function URL.Message
table usingdb.message.create
. Store thesid
returned by Twilio, along with other details and the initialstatus
(usuallyqueued
).try...catch
block logs errors. Production applications need more robust error handling.2.4. Handle Twilio Status Callback Webhooks
Create the RedwoodJS function that Twilio will call.
Implement the handler logic in the generated file. This endpoint needs to:
MessageSid
andMessageStatus
(andErrorCode
if present) from the request body.200 OK
response to Twilio.Explanation:
validateRequest
from thetwilio
library checks if thex-twilio-signature
header matches a signature calculated using yourTWILIO_AUTH_TOKEN
, the request URL (callbackUrl
), and the POST parameters (params
). This verifies the request originated from Twilio and wasn't tampered with. Reject requests with invalid signatures using a403 Forbidden
status.MessageSid
,MessageStatus
,ErrorCode
, andErrorMessage
from the request parameters (event.body
). Note: This code assumes you are using RedwoodJS v6 or later, which automatically parsesapplication/x-www-form-urlencoded
request bodies into theevent.body
object.db.message.update
to find the message record by its uniquesid
and update thestatus
,errorCode
, anderrorMessage
fields. Include error handling, especially for the case where the message might not (yet) exist in the DB (Prisma errorP2025
).200 OK
status code with an empty TwiML<Response></Response>
body. This acknowledges receipt to Twilio, preventing unnecessary retries. Even if a database error occurs after validating the signature, returning 200 is often preferred to avoid excessive retries for potentially transient DB issues.3. Exposing the Send Functionality (API Layer)
While the callback handler is a direct webhook, you need a way to trigger the
sendSms
service. A common RedwoodJS approach is via a GraphQL mutation.Define GraphQL Schema:
Implement Resolvers:
Redwood maps the SDL definitions to the service functions. Ensure your
api/src/services/sms/sms.js
file exports functions matching the mutation and query names (sendSms
andmessageStatus
). We already did this in Step 2.3.Call this mutation from your RedwoodJS web side (or any GraphQL client) after implementing authentication (
@requireAuth
).Example Frontend Call (React Component):
4. Configure Twilio Status Callback URL
TWILIO_ACCOUNT_SID
andTWILIO_AUTH_TOKEN
securely in your.env
file and ensure they are available as environment variables in your deployment environment. Access them viaprocess.env
.TWILIO_PHONE_NUMBER
in.env
.statusCallback
parameter in theclient.messages.create
call, you tell Twilio where to send updates for that specific message. This is flexible but requires constructing the correct URL in your code. EnsureAPI_BASE_URL
is correctly set.messagingServiceSid
instead of afrom
number, and don't provide astatusCallback
parameter in the API call, Twilio will use the URL configured on the service.twilioCallback
function (e.g.,https://your-app-domain.com/.redwood/functions/twilioCallback
).messagingServiceSid
instead offrom
.statusCallback
URL provided in the API call overrides the Messaging Service setting for that specific message.5. Handle Errors and Retry Logic for Delivery Callbacks
messages.create
) intry...catch
blocks in your service (sendSms
). Log errors clearly.twilioCallback
), handle potential database errors gracefully (e.g., message not foundP2025
, other DB connection issues). Return200 OK
even on handled DB errors post-signature validation to prevent excessive Twilio retries.MessageSid
,MessageStatus
).logger
(import { logger } from 'src/lib/logger'
).MessageSid
in logs for correlation.twilioCallback
endpoint returns a non-2xx
status code (e.g.,4xx
,5xx
) or times out, Twilio will retry the request.update
withwhere: { sid: ... }
, which is generally safe).200 OK
promptly (even on some internal errors after validation) to prevent unnecessary retries.6. Database Schema and Data Layer
Message
model inschema.prisma
(Step 2.1) defines the structure.yarn rw prisma migrate dev
) handle schema changes (Step 2.1).sms.js
) using the Prisma client (db
) for creating and updating records (Steps 2.3 & 2.4).sid
column in theMessage
table has an index (Prisma adds@unique
which typically creates an index). Database connection pooling (handled by Prisma) is essential.7. Secure Your Webhook Endpoint with Signature Validation
validateRequest
). It prevents unauthorized actors from forging status updates. Never skip this step.X-Twilio-Signature
header.https://
URLs with valid SSL certificates. While signature validation prevents request tampering, HTTPS prevents replay attacks where captured requests could be resent by attackers.TWILIO_AUTH_TOKEN
andTWILIO_ACCOUNT_SID
. Do not commit them to your repository. Use.env
locally and secure environment variable management in your deployment environment.sendSms
mutation (e.g., format of theto
number, length of thebody
). Redwood's forms provide basic validation helpers.@requireAuth
) to protect the GraphQL mutation (sendSms
) so only authenticated users can trigger sending messages, if applicable. The callback endpoint itself is secured by signature validation, not user login.sendSms
mutation endpoint to prevent abuse. The callback endpoint implicitly relies on Twilio's rate limiting for sending webhooks.8. Handling Special Cases
queued
→sending
→sent
quickly, but the callbacks could arrive asqueued
→sent
→sending
. Design your logic to handle this. Usually, updating with the latest received status is sufficient, but you might store timestamps if order is critical. TheRawDlrDoneDate
property (present for delivered/undelivered SMS/MMS) can provide a carrier timestamp.MessageSid
,MessageStatus
,ErrorCode
). BothSmsStatus
andMessageStatus
fields are included in callbacks for backward compatibility.sendSms
function finishes writing the initial record to the database (a race condition, less likely but possible). The error handling intwilioCallback
(checking for PrismaP2025
) should log this. You might implement a small delay/retry mechanism within the callback if this becomes a frequent issue, but often logging and returning200 OK
is sufficient.9. Performance Optimizations
twilioCallback
function lightweight. Perform the essential tasks (validate signature, parse body, update DB) and return200 OK
quickly. Defer any heavy processing (e.g., complex analytics, triggering other workflows) to a background job queue if necessary.sid
column in theMessage
table is indexed for fast lookups during updates (@unique
usually ensures this).async/await
correctly to avoid blocking the Node.js event loop.10. Monitoring, Observability, and Analytics
MessageSid
.sendSms
service and thetwilioCallback
function. RedwoodJS has integrations available./healthz
) that verifies database connectivity. Monitor this endpoint.twilioCallback
handler.delivered
,failed
,undelivered
).11. Troubleshooting Common Status Callback Issues
API_BASE_URL
/statusCallback
URL: Double-check the URL being sent to Twilio and ensure it's publicly accessible. Usengrok
for local testing. VerifyAPI_BASE_URL
is set in your environment.yarn rw log api
or your production logging service) for errors in thetwilioCallback
handler preventing it from returning200 OK
.statusCallback
Parameter: Ensure you are actually providing thestatusCallback
URL when callingmessages.create
or that it's correctly configured on the Messaging Service.TWILIO_AUTH_TOKEN
: Verify the token in your.env
matches the one in the Twilio console.validateRequest
: Ensure thecallbackUrl
passed tovalidateRequest
exactly matches the URL Twilio is sending the request to, including protocol (http
/https
), domain, and path (/.redwood/functions/twilioCallback
). Check for trailing slashes or port number discrepancies, especially behind proxies or load balancers.params
object passed tovalidateRequest
accurately represents the raw POST body parameters sent by Twilio (usuallyapplication/x-www-form-urlencoded
). Ifevent.body
isn't automatically parsed correctly in your environment, manual parsing is required.P2025
): As discussed, this can happen due to race conditions. Log the error and return200 OK
if the signature was valid.schema.prisma
file (yarn rw prisma migrate dev
).ngrok
Specifics:ngrok
, the public URL changes each time you restart it. Remember to update yourAPI_BASE_URL
in.env
(or wherever it's set) and potentially restart your Redwood dev server (yarn rw dev
) to reflect the new URL used in thestatusCallback
.https
URL provided byngrok
.Related Resources
Learn more about SMS delivery tracking and RedwoodJS integration:
Frequently Asked Questions
How do I check SMS delivery status in Twilio?
Set up a status callback webhook endpoint in your application and provide the webhook URL when sending messages via the Twilio API. Twilio will POST status updates to your endpoint as the message progresses through queued, sent, delivered, or failed states.
What is the difference between Twilio sent and delivered status?
"Sent" means Twilio successfully handed the message to the carrier network. "Delivered" means the carrier confirmed the message reached the recipient's device. Delivery confirmation depends on carrier support and may not be available for all networks.
Why are my Twilio SMS messages undelivered?
Common causes include invalid phone numbers, devices powered off or out of service (error 30003), numbers no longer in service (error 30005), carrier filtering, or network issues. Check the
ErrorCode
in your webhook callback for specific failure reasons.How much does Twilio charge for failed SMS messages?
As of September 30, 2024, Twilio charges a $0.001 failed message processing fee per message that terminates in "Failed" status. This applies to most failure scenarios but not all error codes.