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:
statusCallbackURL pointing back to a specific endpoint on your RedwoodJS API.statusCallbackURL.Prerequisites:
ngrokfor 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
.envfiles for environment variables. Create a.envfile 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 thestatusCallbackURL.Project Structure:
RedwoodJS organizes code into
web(frontend) andapi(backend) sides. You'll primarily work within theapidirectory: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
Messagetable in your database.Understanding Twilio Message Status Values:
Twilio status callbacks include the
MessageStatusparameter 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
ErrorCodefield only appears in callback events forfailedorundeliveredmessages. Common error codes include:Billing Note: Starting September 30, 2024, Twilio charges a "Failed message processing fee" for most messages in
failedstatus. 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
sendSmsfunction in the generated service file.Explanation:
db), Redwood's logger, and your configuredtwilioClientandtwilioPhoneNumber.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_URLcorrectly 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 yourtwilioCallbackfunction URL.Messagetable usingdb.message.create. Store thesidreturned by Twilio, along with other details and the initialstatus(usuallyqueued).try...catchblock 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:
MessageSidandMessageStatus(andErrorCodeif present) from the request body.200 OKresponse to Twilio.Explanation:
validateRequestfrom thetwiliolibrary checks if thex-twilio-signatureheader 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 Forbiddenstatus.MessageSid,MessageStatus,ErrorCode, andErrorMessagefrom the request parameters (event.body). Note: This code assumes you are using RedwoodJS v6 or later, which automatically parsesapplication/x-www-form-urlencodedrequest bodies into theevent.bodyobject.db.message.updateto find the message record by its uniquesidand update thestatus,errorCode, anderrorMessagefields. Include error handling, especially for the case where the message might not (yet) exist in the DB (Prisma errorP2025).200 OKstatus 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
sendSmsservice. 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.jsfile exports functions matching the mutation and query names (sendSmsandmessageStatus). 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_SIDandTWILIO_AUTH_TOKENsecurely in your.envfile and ensure they are available as environment variables in your deployment environment. Access them viaprocess.env.TWILIO_PHONE_NUMBERin.env.statusCallbackparameter in theclient.messages.createcall, 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_URLis correctly set.messagingServiceSidinstead of afromnumber, and don't provide astatusCallbackparameter in the API call, Twilio will use the URL configured on the service.twilioCallbackfunction (e.g.,https://your-app-domain.com/.redwood/functions/twilioCallback).messagingServiceSidinstead offrom.statusCallbackURL 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...catchblocks in your service (sendSms). Log errors clearly.twilioCallback), handle potential database errors gracefully (e.g., message not foundP2025, other DB connection issues). Return200 OKeven on handled DB errors post-signature validation to prevent excessive Twilio retries.MessageSid,MessageStatus).logger(import { logger } from 'src/lib/logger').MessageSidin logs for correlation.twilioCallbackendpoint returns a non-2xxstatus code (e.g.,4xx,5xx) or times out, Twilio will retry the request.updatewithwhere: { sid: ... }, which is generally safe).200 OKpromptly (even on some internal errors after validation) to prevent unnecessary retries.6. Database Schema and Data Layer
Messagemodel 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).sidcolumn in theMessagetable has an index (Prisma adds@uniquewhich 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-Signatureheader.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_TOKENandTWILIO_ACCOUNT_SID. Do not commit them to your repository. Use.envlocally and secure environment variable management in your deployment environment.sendSmsmutation (e.g., format of thetonumber, 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.sendSmsmutation endpoint to prevent abuse. The callback endpoint implicitly relies on Twilio's rate limiting for sending webhooks.8. Handling Special Cases
queued→sending→sentquickly, 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. TheRawDlrDoneDateproperty (present for delivered/undelivered SMS/MMS) can provide a carrier timestamp.MessageSid,MessageStatus,ErrorCode). BothSmsStatusandMessageStatusfields are included in callbacks for backward compatibility.sendSmsfunction 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 OKis sufficient.9. Performance Optimizations
twilioCallbackfunction lightweight. Perform the essential tasks (validate signature, parse body, update DB) and return200 OKquickly. Defer any heavy processing (e.g., complex analytics, triggering other workflows) to a background job queue if necessary.sidcolumn in theMessagetable is indexed for fast lookups during updates (@uniqueusually ensures this).async/awaitcorrectly to avoid blocking the Node.js event loop.10. Monitoring, Observability, and Analytics
MessageSid.sendSmsservice and thetwilioCallbackfunction. RedwoodJS has integrations available./healthz) that verifies database connectivity. Monitor this endpoint.twilioCallbackhandler.delivered,failed,undelivered).11. Troubleshooting Common Status Callback Issues
API_BASE_URL/statusCallbackURL: Double-check the URL being sent to Twilio and ensure it's publicly accessible. Usengrokfor local testing. VerifyAPI_BASE_URLis set in your environment.yarn rw log apior your production logging service) for errors in thetwilioCallbackhandler preventing it from returning200 OK.statusCallbackParameter: Ensure you are actually providing thestatusCallbackURL when callingmessages.createor that it's correctly configured on the Messaging Service.TWILIO_AUTH_TOKEN: Verify the token in your.envmatches the one in the Twilio console.validateRequest: Ensure thecallbackUrlpassed tovalidateRequestexactly 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.paramsobject passed tovalidateRequestaccurately represents the raw POST body parameters sent by Twilio (usuallyapplication/x-www-form-urlencoded). Ifevent.bodyisn'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 OKif the signature was valid.schema.prismafile (yarn rw prisma migrate dev).ngrokSpecifics:ngrok, the public URL changes each time you restart it. Remember to update yourAPI_BASE_URLin.env(or wherever it's set) and potentially restart your Redwood dev server (yarn rw dev) to reflect the new URL used in thestatusCallback.httpsURL 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
ErrorCodein 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.