Frequently Asked Questions
Set up a webhook endpoint in your Next.js application using an API route. This endpoint will receive real-time status updates from Twilio as your messages are sent, delivered, or encounter issues. The guide recommends using Prisma to store these updates in a database for reliable tracking.
A Twilio status callback is an HTTP POST request sent by Twilio to your application. It contains information about the delivery status of your SMS/MMS messages, such as 'queued', 'sent', 'delivered', or 'failed'. This allows for real-time monitoring and handling of message delivery events.
Prisma is a next-generation ORM that simplifies database interactions in Node.js and TypeScript. It's used in this guide to store the message status updates received from Twilio, ensuring type safety and efficient database operations. Prisma's schema management and migrations make it easy to manage database changes.
ngrok is essential for local development when your Next.js application isn't publicly accessible. ngrok creates a public tunnel to your localhost, allowing Twilio to reach your webhook endpoint during testing. You'll need to update your NEXT_PUBLIC_APP_BASE_URL environment variable with the ngrok URL.
Yes, Prisma supports various databases like PostgreSQL, MySQL, and SQLite. The guide uses SQLite for simplicity, but you can change the datasource-provider in your schema.prisma and update the DATABASE_URL environment variable accordingly. Choose the database best suited for your project's needs.
The most important security measure is validating the Twilio request signature. The provided code demonstrates how to use the twilio.validateRequest function to verify that incoming requests originate from Twilio and haven't been tampered with. Always use HTTPS in production.
The statusCallback parameter in the Twilio Messages API is the URL where Twilio will send status updates about your message. It should point to your Next.js API route, e.g., /api/twilio/status, which is set up to handle these updates. ngrok or your deployed application URL should be used for the public portion of the URL.
Proper error handling involves using try-catch blocks in your webhook route to catch potential errors during validation, database interactions, or request processing. Return appropriate status codes (e.g., 403 for invalid signatures, 500 for server errors) so Twilio knows to retry or stop. Logging these errors is crucial for debugging.
Twilio doesn't guarantee status updates will arrive in order of occurrence. The guide suggests creating a new database log entry per update, preserving the received status and timestamp. This approach eliminates order dependency, and provides status update history via the database.
The Twilio Error Dictionary (available on Twilio's website) provides detailed explanations of error codes you might encounter during message sending or delivery. Refer to it when you receive ErrorCode values in status callbacks to understand what went wrong and implement appropriate handling logic.
Use ngrok to expose your local development server, ensuring the NEXT_PUBLIC_APP_BASE_URL in your .env file is set to your ngrok URL. Send a test SMS through your app's interface, or use a tool like Postman to send requests directly to the webhook, observing logs for request processing and status updates.
Storing the rawPayload as JSON in your database captures all data sent by Twilio, including channel-specific details (like WhatsApp metadata) that might not be explicitly handled by your code. This provides valuable information for debugging, analysis, and adapting to future changes in Twilio's payload structure.
Idempotency means your webhook can handle receiving the same status update multiple times without adverse effects. Twilio might retry sending callbacks if there are network issues, so ensure your logic (database updates, etc.) functions correctly even if a callback is processed more than once. The example code provides idempotency for database entries via a unique constraint.
Log key events like webhook receipt, validation outcome, extracted data, and database operations. Include the MessageSid in your logs to correlate events related to a specific message. Use structured logging and a centralized logging platform (e.g., Datadog, Logtail) for production-level logging and analysis.
Track Twilio SMS Delivery Status with Next.js Webhooks: Complete StatusCallback Guide
Track the delivery status of SMS and MMS messages sent via Twilio directly within your Next.js application. This guide provides a complete walkthrough for setting up a webhook endpoint to receive status updates, storing them in a database, and handling common scenarios.
Real-time status of sent messages – whether delivered, failed, or in transit – is crucial for applications relying on SMS for notifications, alerts, or two-factor authentication. Implementing Twilio's status callbacks provides this visibility, enabling better error handling, logging, and user experience.
Build a Next.js application that sends SMS via Twilio and uses a dedicated API route to listen for and record status updates pushed by Twilio. Use Prisma for database interaction and implement security, error handling, and deployment best practices.
Citation1. From source: https://www.twilio.com/docs/messaging/guides/track-outbound-message-status, Title: Twilio StatusCallback Webhook Parameters 2024, Text: 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 parameters without advance notice. When integrating with status callback requests, your implementation should be 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 SDKs.
Project Overview and Goals
What You'll Build:
/api/twilio/status) acting as a webhook endpoint for Twilio status callbacks.statusCallbackparameter.MessageSid,MessageStatus,ErrorCode).Problem Solved:
Gain real-time visibility into the delivery lifecycle of outbound Twilio messages, enabling robust tracking, debugging, and follow-up actions based on message status (e.g., retrying failed messages, logging delivery confirmations).
Technologies Used:
System Architecture:
The system operates as follows:
StatusCallbackURL pointing back to the application.StatusCallbackURL (must be publicly accessible viangrokduring development or the deployed application URL in production)./api/twilio/status).MessageSid,MessageStatus) in the database using Prisma.200 OKHTTP response back to Twilio to acknowledge receipt.Prerequisites:
ngrok) if testing locally.Final Outcome:
A functional Next.js application capable of sending SMS messages via Twilio and reliably receiving, validating, and storing delivery status updates pushed by Twilio to a secure webhook endpoint.
Setting up the Project
Initialize the Next.js project and install necessary dependencies.
Create a new Next.js App: Open your terminal and run:
Choose your preferred settings (TypeScript recommended). This guide assumes the
appdirectory structure.Install Dependencies: Install the Twilio Node.js helper library and Prisma.
twilio: Official library for interacting with the Twilio API.@prisma/client: Prisma's database client.prisma: Prisma's command-line tool for migrations and schema management.Initialize Prisma: Set up Prisma in your project. This creates a
prismadirectory with aschema.prismafile and a.envfile for environment variables.--datasource-provider sqlite: Uses SQLite for simplicity. Change this (e.g.,postgresql,mysql) for a different database and update theDATABASE_URLaccordingly.Configure Environment Variables: Open the
.envfile created by Prisma and add your Twilio credentials and other necessary variables. Never commit this file to version control if it contains secrets.DATABASE_URL: Connection string for your database. Prisma set this up for SQLite.TWILIO_ACCOUNT_SID: Your unique Twilio account identifier.TWILIO_AUTH_TOKEN: Your secret Twilio token. Twilio uses this same token to sign webhook requests, serving as the webhook validation secret.TWILIO_PHONE_NUMBER_OR_MSG_SID: The 'From' number for sending messages, or the SID of a configured Messaging Service.NEXT_PUBLIC_APP_BASE_URL: The publicly accessible base URL of your application. While prefixed withNEXT_PUBLIC_(allowing client-side access), it's used server-side in this guide to construct the fullstatusCallbackURL. Update this when using ngrok or deploying.Project Structure: Your basic structure will look something like this (using
approuter):Creating a Database Schema and Data Layer
Store incoming status updates in a database.
Define the Prisma Schema: Open
prisma/schema.prismaand define a model to log message statuses.messageSid(unique per message), thestatus, an optionalerrorCode, the fullrawPayloadas JSON for auditing, and atimestamp.Apply the Schema to the Database: Run the Prisma command to create the database file (for SQLite) and the
MessageStatusLogtable.schema.prismadefinition. For production workflows, useprisma migrate devandprisma migrate deploy.Create a Prisma Client Instance: Create a reusable Prisma client instance. Create
app/lib/prisma.ts:Citation2. From source: https://www.prisma.io/docs/orm/more/help-and-troubleshooting/help-articles/nextjs-prisma-client-dev-practices, Title: Prisma Client Singleton Pattern Next.js 2024, Text: The best practice is to instantiate a single instance of PrismaClient and save it on the globalThis object. This approach ensures only one instance of the client is created, preventing connection pool exhaustion during Next.js hot reloading in development mode. This pattern is specifically documented for Next.js 15 compatibility.
Implementing the Core Functionality (Webhook API Route)
Create the API route that receives
POSTrequests from Twilio.Create the API Route File: Create the file
app/api/twilio/status/route.ts.Implement the Webhook Handler:
Explanation:
TWILIO_AUTH_TOKEN.req.text()for signature validation.x-twilio-signatureheader and the full request URL. Twilio uses the full URL in signature calculation.application/x-www-form-urlencodedstring into key-value pairs usingURLSearchParamsand convert to an object.twilio.validateRequestwith your Auth Token (acting as the secret), the signature, the full URL, and the parsed parameters object. This is the critical security step.403 Forbidden.MessageSid,MessageStatus, andErrorCodefrom the parsed parameters.P2002) and log other DB errors.200 OKresponse back to Twilio to acknowledge receipt.try...catchlogs unexpected errors and returns500 Internal Server Error.Integrating with Twilio (Sending the Message)
Now we need a way to send a message and tell Twilio where to send status updates.
Create a Simple UI (Optional but helpful for testing): Let's add a basic button in
app/page.tsxto trigger sending an SMS. Note that the example UI code includes a placeholder phone number (+15558675309) which must be replaced with a valid number you can send messages to for testing.Create the Sending API Route: Create
app/api/send-sms/route.tsto handle the actual Twilio API call.Explanation:
POSThandler expecting JSON withtoandbody.statusCallbackURL usingNEXT_PUBLIC_APP_BASE_URLand the webhook route path (/api/twilio/status).client.messages.create, passing recipient, sender, body, and the crucialstatusCallbackURL.message.sidand initialstatus(e.g.,queued).Local Development with
ngrok: Twilio needs to sendPOSTrequests to your application, so your local server (http://localhost:3000) must be publicly accessible.ngrokwill display a ""Forwarding"" URL (e.g.,https://<random-subdomain>.ngrok-free.app). This is your public URL..env: Copy thehttps://...ngrok URL and updateNEXT_PUBLIC_APP_BASE_URLin your.envfile:Ctrl+C) and restart (npm run dev) your Next.js server to load the updated environment variable.Now, when sending a message:
statusCallbackURL sent to Twilio will use your publicngrokURL.POSTrequests to this URL.ngrokforwards these requests tohttp://localhost:3000/api/twilio/status.ngrokterminal and your Next.js logs.Implementing Proper Error Handling, Logging, and Retry Mechanisms
/api/twilio/statusroute usestry...catchfor validation, DB operations, and general processing.200status codes (403,500) signals issues to Twilio. Twilio webhook retry behavior varies by configuration.P2002for duplicates) prevents noise if Twilio resends data.console.log,console.warn,console.errorfor basic logging. In production, adopt a structured logging library (e.g.,pino,winston) and send logs to an aggregation service (e.g., Datadog, Logtail, Axiom).MessageSidfor correlation.#rc=5to your webhook URL. Without configuration, Twilio does not automatically retry on5xxHTTP errors for SMS webhooks.200 OK.Citation4. From source: https://www.twilio.com/docs/usage/webhooks/webhooks-connection-overrides and https://stackoverflow.com/questions/35449228/what-is-max-number-of-retry-attempts-for-twilio-sms-callback, Title: Twilio Webhook Retry Configuration 2024, Text: Twilio can retry webhooks up to 5 times on connection failures. By default, Twilio retries once on TCP connect or TLS handshake failures. You can configure additional retries (0-5 attempts) using connection overrides by adding #rc=5 to your webhook URL. For SMS/phone call webhooks, Twilio does not automatically retry to the same URL on 5xx errors without explicit configuration.
TWILIO_AUTH_TOKENin.env, restart, send a message. Webhook validation should fail (403). Alternatively, usecurlor Postman to send a request without a valid signature.prisma.messageStatusLog.createcall (e.g., misspell a field) to observe the500response and logs.MessageSid. Verify the400 Bad Requestresponse.Adding Security Features
/api/twilio/statususingtwilio.validateRequest. Prevents fake status updates.ngrokprovides HTTPS locally. Deployment platforms (Vercel, Netlify) enforce HTTPS. HTTPS encrypts traffic using TLS and prevents replay attacks.TWILIO_ACCOUNT_SID,TWILIO_AUTH_TOKEN) using environment variables. Do not hardcode or commit them. Use platform secret management in production.MessageSid,MessageStatus) are implemented as good practice.upstash/ratelimit) for high-traffic public endpoints to prevent abuse, though less critical for specific webhooks unless targeted attacks are a concern.Handling Special Cases Relevant to the Domain
sentcallback might arrive afterdelivered.timestampfield inMessageStatusLoghelps reconstruct history.Citation5. From source: https://www.twilio.com/docs/messaging/guides/track-outbound-message-status, Title: Twilio Webhook Order and Timing 2024, Text: Status callback requests are HTTP requests subject to differences in latency caused by changing network conditions. There is no guarantee that status callback requests always arrive at your endpoint in the order they were sent. Applications should be designed to handle out-of-order webhook delivery.
MessageStatusisfailedorundelivered, check theErrorCodefield. Consult the Twilio Error Dictionary for meanings (e.g.,30003– Unreachable,30007– Carrier Violation). Log these codes.rawPayloadas JSON preserves this data.RawDlrDoneDate: SMS/MMS callbacks might includeRawDlrDoneDate(YYMMDDhhmm) for carrier's final status timestamp. Parse and store if needed.Implementing Performance Optimizations
@id. The@uniqueconstraint onmessageSidalso creates an index, ensuring efficient lookups/checks.async/awaitcorrectly, avoiding blocking operations.200 OKafter validation. Perform time-consuming tasks after responding or asynchronously (background jobs/queues) if needed. The current implementation (validate, DB write, respond) is typically fast enough.Adding Monitoring, Observability, and Analytics
/api/health) returning200 OK.MessageStatusdistribution (delivered vs. failed) and commonErrorCodevalues.5xxor403errors from the webhook.ErrorCodevalues.Troubleshooting and Caveats
ngrokor deployment URL is correct and accessible.statusCallbackURL? Check the URL used inmessages.create(includinghttps://, domain, path/api/twilio/status). Verify logs from the sending API route.TWILIO_AUTH_TOKEN? Ensure theTWILIO_AUTH_TOKENin your.envexactly matches the Auth Token shown in your Twilio Console. Remember to restart the server after.envchanges.twilio.validateRequestuses the exact URL Twilio called, including protocol (https://) and any query parameters (though unlikely for status webhooks).req.urlin Next.js App Router should provide this.paramsObjectin the example) are passed tovalidateRequest. Do not pass the raw string or a re-serialized version.prisma/schema.prismamatches the actual database structure (npx prisma db pushor migrations applied).DATABASE_URLis correct and the database is reachable.P2002)? This is expected if Twilio retries and sends the exact sameMessageSid. The code handles this by logging a warning and returning200 OK. If happening unexpectedly, investigate why duplicate SIDs are being processed.ngrokSession Expiry: Freengroksessions expire and provide a new URL each time you restartngrok. Remember to updateNEXT_PUBLIC_APP_BASE_URLand restart your Next.js server whenever thengrokURL changes. Consider a paidngrokplan for stable subdomains if needed frequently during development.Frequently Asked Questions About Twilio Delivery Status Callbacks
How do I set up Twilio StatusCallback webhooks in Next.js?
Set up Twilio StatusCallback webhooks in Next.js by creating an API route at
/api/twilio/status/route.tsand passing the webhook URL in thestatusCallbackparameter when sending messages viaclient.messages.create(). The webhook endpoint must be publicly accessible (use ngrok for local development) and validate incoming requests usingtwilio.validateRequest()with your Auth Token to ensure requests originate from Twilio. Store status updates in a database using Prisma for tracking delivery, failures, and error codes.How does Twilio webhook signature validation work?
Twilio webhook signature validation uses HMAC-SHA1 to sign requests with your Auth Token as the secret key. Extract the
x-twilio-signatureheader from incoming requests and validate it usingtwilio.validateRequest(authToken, signature, fullUrl, paramsObject)from the Twilio Node.js SDK. This prevents fake status updates from unauthorized sources. Always use the exact full URL Twilio called (including protocol and query parameters) and the parsed request body parameters for validation. Twilio strongly recommends using SDK validation methods rather than implementing custom validation.What information does Twilio send in StatusCallback webhooks?
Twilio sends
MessageSid(unique message identifier),MessageStatus(queued, sent, delivered, failed, undelivered),ErrorCode(if delivery failed), and channel-specific parameters in StatusCallback webhooks. The parameters vary by messaging channel (SMS, MMS, WhatsApp) and are subject to change without notice. Store the completerawPayloadas JSON to preserve all webhook data for debugging and future use. Common status values include 'queued' (accepted), 'sent' (dispatched to carrier), 'delivered' (confirmed by carrier), 'failed' (permanent failure), and 'undelivered' (temporary failure).How do I handle failed SMS deliveries with Twilio webhooks?
Handle failed SMS deliveries by checking the
MessageStatusfield for 'failed' or 'undelivered' values in your webhook handler. Extract theErrorCodefield to determine the failure reason – consult the Twilio Error Dictionary for meanings (e.g., 30003 for unreachable numbers, 30007 for carrier violations). Log error codes to your database for analysis and implement retry logic for transient failures. For permanent failures (invalid numbers, blocked content), mark messages as undeliverable and notify users through alternative channels. Design your webhook endpoint to be idempotent to handle duplicate status updates.Does Twilio automatically retry failed webhook deliveries?
Twilio does not automatically retry webhook deliveries on 5xx HTTP errors for SMS callbacks by default. By default, Twilio retries once on TCP connect or TLS handshake failures only. Configure additional retry attempts (up to 5 times) by appending connection override parameters like
#rc=5to your webhook URL. Ensure your endpoint responds with200 OKwithin the timeout period and implements idempotent processing to handle duplicate status updates gracefully. Return500status codes for temporary errors where retry is appropriate, and200 OKfor successfully processed requests (including duplicates).What database schema should I use for storing Twilio status updates?
Use a database schema with
messageSid(unique identifier),status(current message state),errorCode(optional error details),rawPayload(full JSON webhook data), andtimestamp(when status was received) fields. SetmessageSidas unique to prevent duplicate entries. Store the complete webhook payload as JSON for debugging and accessing channel-specific fields. Create indexes onmessageSidfor efficient lookups and consider addingcreatedAttimestamps for time-series analysis. Use Prisma with the singleton pattern to prevent connection pool exhaustion during Next.js hot reloading in development.How do I secure Twilio webhooks in production?
Secure Twilio webhooks by implementing signature validation using
twilio.validateRequest(), hosting endpoints on HTTPS (prevents replay attacks via TLS encryption), storing credentials in environment variables (never hardcode), and optionally adding rate limiting for high-traffic scenarios. Always validate thex-twilio-signatureheader before processing webhook data. Use platform secret management (Vercel Environment Variables, AWS Secrets Manager) for production deployments. Consider adding IP allowlisting for Twilio's webhook source IPs and implement proper error handling to avoid exposing internal system details in error responses.How do I test Twilio webhooks locally with ngrok?
Test Twilio webhooks locally by installing ngrok (
ngrok.com/download), runningngrok http 3000in a separate terminal to expose your local server, copying the HTTPS forwarding URL (e.g.,https://abc123.ngrok-free.app), updatingNEXT_PUBLIC_APP_BASE_URLin your.envfile with the ngrok URL, and restarting your Next.js development server. The webhook endpoint will be accessible athttps://abc123.ngrok-free.app/api/twilio/status. Monitor incoming requests in the ngrok terminal and your Next.js logs. Free ngrok sessions expire and generate new URLs on each restart, requiring environment variable updates.Why are Twilio status callbacks arriving out of order?
Twilio status callbacks arrive out of order because they are HTTP requests subject to network latency variations. Twilio does not guarantee callbacks arrive in chronological order – a 'sent' callback might arrive after 'delivered'. Design your application to handle out-of-order delivery by creating a new log entry for each status update with timestamps rather than updating a single record. Use the
timestampfield to reconstruct the actual event timeline. Consider implementing state machine logic if you need to enforce status progression rules, but accept that callbacks reflect network delivery timing rather than actual event sequence.What Twilio error codes should I monitor in webhook callbacks?
Monitor critical Twilio error codes including 30003 (unreachable destination), 30004 (message blocked), 30005 (unknown destination), 30006 (landline or unreachable carrier), 30007 (carrier violation/spam filter), 30008 (unknown error), and 21610 (message not sent due to account suspension). Log error codes to your database with full context for analysis. Set up alerts for high error rates or specific codes indicating systemic issues. Consult the Twilio Error Dictionary for complete error code meanings and recommended remediation steps. Implement different handling strategies for temporary errors (retry) versus permanent failures (mark undeliverable).