Frequently Asked Questions
Track SMS delivery status using Plivo's webhook mechanism. When sending an SMS via the Plivo API, provide a callback URL in the 'url' parameter. Plivo will send real-time status updates (queued, sent, delivered, failed, etc.) to this URL as HTTP POST requests. Your application can then process these updates as needed.
A Plivo Message UUID (Universal Unique Identifier) is a unique ID assigned to each SMS message sent through the Plivo platform. It is essential for tracking the delivery status of individual messages and is included in the callback data sent to your webhook endpoint by Plivo.
Plivo utilizes webhooks (callbacks) for SMS status updates to provide real-time delivery information to your application. Instead of requiring you to poll the Plivo API repeatedly, webhooks enable Plivo to push these updates to your server as they occur, ensuring efficient and timely delivery status tracking.
A Plivo Application Message URL is used as a default callback URL for all messages sent from a specific phone number linked to that application. While convenient for a global setting, using per-message URLs within the send API request provides more granular control over callbacks and is the generally recommended practice for most applications.
SQLite can be used for storing SMS callback data in development or low-load production environments. However, for high-concurrency or large-scale applications, it's recommended to use a more robust database solution like PostgreSQL or MySQL to handle increased load and ensure data integrity.
Use the plivoClient.messages.create() method. Provide your Plivo number, the recipient's number, the message text, and optionally, the callback URL. Ensure your Plivo credentials are set up correctly in environment variables as PLIVO_AUTH_ID and PLIVO_AUTH_TOKEN.
The BASE_URL is the publicly accessible URL of your application. It's crucial for constructing the callback URL that Plivo uses to send delivery status updates. During local development, use your ngrok HTTPS URL, and in production, set it to your server's public domain or IP.
Secure your Plivo webhook endpoint by validating the Plivo signature using the plivo.validateRequestSignatureV3 function in your route handler. This verifies that the callback request originated from Plivo. Additionally, use rate limiting to protect against abuse.
If your Plivo callback isn't working, ensure your BASE_URL is correctly set to a publicly accessible address (like an ngrok URL for development). Verify that the callback URL you provide matches the route in your Express app, and that the Plivo signature validation is passing. Check your Plivo Console for error logs related to callbacks or webhooks.
Plivo SMS error codes provide specific reasons for message delivery failures. The ErrorCode is included in the callback data when the status is 'failed' or 'undelivered'. Refer to Plivo's SMS Error Code documentation to understand each code's meaning and troubleshoot delivery issues effectively.
Log all errors during callback processing, but always respond to Plivo with a 200 OK status to acknowledge receipt and prevent unnecessary retries. If the database update fails, log the error and consider adding the failed update to an error queue for later investigation. Handle Plivo's specific error codes (provided in callback data) to understand the reason for delivery failures.
Install ngrok and run ngrok http <your-server-port>, replacing <your-server-port> with the port your Express server is running on (typically 3000). Copy the HTTPS URL provided by ngrok and set it as your BASE_URL in the .env file. This allows Plivo to send callbacks to your locally running server.
Track SMS Delivery Status with Plivo, Node.js & Express (2025)
Master SMS delivery tracking with Plivo webhooks to monitor message lifecycle, handle failures, and improve deliverability. This guide shows you how to build a production-ready webhook server using Node.js v22 LTS, Express 5.1, and the Plivo Node.js SDK v4.74.0 with signature validation, database persistence, and comprehensive error handling.
Quick Reference: Plivo SMS Delivery Tracking
Message Status Flow:
queued→sent→delivered/failed/undelivered<!-- EXPAND: Add typical timing for each status transition (e.g., queued→sent: <1s, sent→delivered: 1-30s) (Type: Enhancement) -->
Key Components:
Callback URL Configuration: Per-message:
message_create({ dst, src, text, url })or Application-level: Plivo consoleRequired Technologies:
Security: HMAC SHA-256 signature validation with
X-Plivo-Signature-V2header (SMS) orX-Plivo-Signature-V3(Voice)What is SMS Delivery Status Tracking?
<!-- DEPTH: Lacks concrete examples of WHY tracking matters - add specific use cases with consequences (Priority: High) --> <!-- GAP: Missing explanation of what happens WITHOUT tracking - reader needs context (Type: Substantive) --> Tracking the delivery status of SMS messages is crucial for many applications, from ensuring critical alerts are received to managing marketing campaign effectiveness and enabling reliable two-way communication. Simply sending a message isn't enough; you need confirmation that it reached (or failed to reach) the intended recipient.
<!-- EXPAND: Could benefit from diagram showing status flow timeline with approximate durations (Type: Enhancement) --> This guide provides a complete walkthrough for building a production-ready system using Node.js, Express, and Plivo to send SMS messages and reliably receive delivery status updates via webhooks (callbacks). We'll cover everything from initial setup and core implementation to security, error handling, database persistence, and deployment.
Project Goal: Build a Node.js application that can:
Technologies Used:
dotenv: Module to load environment variables from a.envfile.ngrok(for local development): Exposes local servers to the public internet, enabling Plivo to send callbacks to your development machine.<!-- GAP: Missing explanation of webhook retry behavior - how often does Plivo retry failed callbacks? (Type: Critical) --> System Architecture:
urlparameter in this request_ specifying where Plivo should send status updates.queued_sent_delivered_failed_undelivered)_ Plivo sends an HTTP POST request containing the status details to theurlyou provided (your webhook endpoint).Prerequisites:
ngrokinstalled for local development. Install ngrok<!-- DEPTH: Prerequisites too vague - what LEVEL of JS/Node knowledge is actually needed? (Priority: Medium) -->
How Do You Set Up the Project?
Initialize your Node.js project and install the necessary dependencies.
1. Create Project Directory: Open your terminal and create a new directory for the project_ then navigate into it.
2. Initialize Node.js Project: Create a
package.jsonfile to manage dependencies and project metadata.3. Install Dependencies:
express: Web framework (v5.1.0+ recommended_ requires Node.js 18+).plivo: Plivo Node.js SDK (v4.74.0 as of January 2025).dotenv: Loads environment variables from.envfile.body-parser: Since Express 4.16+_ body parsing is built into Express viaexpress.json()andexpress.urlencoded(). You don't need the separatebody-parserpackage for most applications. This guide uses the built-in Express methods.sqlite3(Optional_ for database persistence): Driver for SQLite.helmet: Basic security headers middleware (v8.1.0 as of January 2025).express-rate-limit: Middleware for rate limiting (v8.1.0 as of January 2025).(or using yarn:
yarn add express plivo dotenv sqlite3 helmet express-rate-limit)Important: If you're using Express 4.16 or later_ you don't need to install
body-parserseparately. Useexpress.json()andexpress.urlencoded()instead.4. Install Development Dependency (Optional but recommended):
nodemon: Automatically restarts the server on file changes during development.(or using yarn:
yarn add --dev nodemon)5. Create Project Structure: Create the following files and directories:
6. Configure
.gitignore: Create a.gitignorefile in the root directory and add these lines to prevent sensitive information and unnecessary files from being committed to version control:7. Configure Environment Variables (
.env): Create a file named.envin the project root. This file stores sensitive credentials and configuration. Never commit this file to version control.Replace
YOUR_PLIVO_AUTH_ID_YOUR_PLIVO_AUTH_TOKEN_ and the placeholder phone number (+1##########) with your actual Plivo credentials and number. Also_ updateBASE_URLwith your ngrok or public URL when running the application.PLIVO_AUTH_ID/PLIVO_AUTH_TOKEN: Find these on your Plivo Console dashboard. They're essential for authenticating API requests.PLIVO_NUMBER: An SMS-enabled phone number you've rented through the Plivo Console (Phone Numbers > Buy Numbers). This will be the sender ID for messages to US/Canada.PORT: The port your Express server will listen on.BASE_URL: The publicly accessible base URL for your server. Plivo needs this to send callbacks. Crucially, update this placeholder with your actual ngrok URL during development or your public domain in production.DATABASE_PATH: Location where the SQLite database file will be stored.<!-- GAP: Missing guidance on where to find Auth ID/Token in console - step-by-step navigation needed (Type: Substantive) --> <!-- EXPAND: Add screenshot or detailed path to credentials in Plivo console (Type: Enhancement) -->
8. (Optional) Configure
nodemon: Add scripts to yourpackage.jsonto easily run the server withnodemonand set up the database.Note: Package versions updated as of January 2025. Always check npm for the latest stable releases.
Now you can run
npm run devto start the server (automatically restarts when you save changes) andnpm run db:setupto initialize the database schema.How Do You Implement the Express Server?
Set up the basic Express server structure, load configuration, initialize the database connection, and configure middleware.
<!-- DEPTH: Section jumps into full code without explaining middleware order importance (Priority: High) --> <!-- GAP: Missing explanation of WHY middleware order matters for security (Type: Critical) -->
File:
server.js(Initial Setup Part)How Do You Send SMS Messages and Handle Callbacks?
Add the core API routes: one to trigger sending an SMS and another to receive the delivery status callbacks from Plivo.
File:
server.js(Adding API Routes)Explanation:
/send-smsRoute (POST):sendSmsLimitermiddleware (express.json() already applied globally).toandtext_ checksBASE_URLconfiguration_ and performs basic E.164 format check onto.callbackUrl.plivoClient.messages.create_ passing the sender_ recipient_ text_ and the criticalurl: callbackUrloption.messageUuidfrom the response.messagesdatabase table with themessage_uuidand an initial status ofsubmitted. Handles potential DB errors gracefully (logs error but doesn't fail the user request).messageUuidif available./plivo/callbackRoute (POST):callbackLimiterandvalidatePlivoSignaturemiddleware.X-Plivo-Signature-V2header (not V3). The validation middleware now handles both V2 (SMS) and V3 (Voice) signatures appropriately.req.body).MessageUUID_Status_ andErrorCode.MessageUUID. Sets the newstatus_plivo_error_code_ and updateslast_updated_at. Handles cases where the UUID might not be found or DB errors occur.res.status(200).send(...)quickly. This acknowledges receipt to Plivo_ preventing retries. Any time-consuming logic based on the status should be handled asynchronously after sending this response.How Should You Store Message Status Updates?
Persist delivery data to track history_ analyze patterns_ and comply with audit requirements. This example uses SQLite3 for local development. For production_ migrate to PostgreSQL or MongoDB.
<!-- DEPTH: Database choice advice is superficial - lacks comparison criteria (Priority: High) --> <!-- GAP: Missing guidance on data retention policies and GDPR/compliance considerations (Type: Critical) -->
File:
db_setup.js(Utility script)This script explicitly creates the table and indices. Run this once (
npm run db:setup) before starting the server for the first time or whenever the schema needs creation/update.Running the Setup:
Explanation:
messagestable with relevant columns for tracking SMS details and status.message_uuidisUNIQUE NOT NULL.message_uuid(critical forUPDATEperformance in the callback handler),status, andlast_updated_atto speed up common queries.CREATE INDEX IF NOT EXISTSensures idempotency.server.jsroute handlers using thesqlite3driver. For larger applications, abstract this into separate data access modules or use an ORM (Object-Relational Mapper) like Sequelize or Prisma.<!-- EXPAND: Add migration guide from SQLite to PostgreSQL with code examples (Type: Enhancement) -->
How Do You Integrate with the Plivo Service?
We've used the SDK, but let's clarify the necessary Plivo Console configuration.
1. Obtain Credentials & Number:
.envfile (PLIVO_AUTH_ID,PLIVO_AUTH_TOKEN).+12025551234) intoPLIVO_NUMBERin.env.<!-- GAP: Trial account limitations not fully explained - what else is restricted? (Type: Substantive) -->
2. Configure Callback URL Handling:
Method Used: Per-Message URL (Implemented in
server.js)url: callbackUrlparameter in theclient.messages.createcall. This tells Plivo exactly where to send status updates for that specific message.Alternative Method: Plivo Application Message URL (Global Default)
urlparameter when sending, Plivo uses the ""Message URL"" configured in a Plivo Application linked to the sender number (PLIVO_NUMBER).https://<your-ngrok-subdomain>.ngrok.io/plivo/callbackorhttps://yourdomain.com/plivo/callback). Set method toPOST.PLIVO_NUMBER, and link it to this Application.Recommendation: Stick with the Per-Message URL approach implemented in the code.
How Do You Handle Errors and Troubleshoot Issues?
Robust applications need solid error handling and observability.
<!-- DEPTH: Error handling section lacks concrete troubleshooting steps (Priority: High) --> <!-- GAP: Missing decision tree for "what to do when X error occurs" (Type: Substantive) -->
Error Handling:
/send-sms): Thetry...catchblock handles errors fromplivoClient.messages.create(network, auth, invalid input, etc.). Logs the error server-side and returns an appropriate HTTP status (e.g., 500, 400) to the client initiating the send request./plivo/callback):console.error), but the route still returns 200 OK to Plivo. This prevents Plivo from retrying due to our internal processing failure. Critical failures (like inability to parse body, though Express's built-in parser handles most) could warrant a 4xx/5xx, but acknowledging receipt (200 OK) is generally preferred./send-sms): Checks for presence ofto/textand basictoformat. Returns 400 Bad Request on failure. Consider usinglibphonenumber-jsfor stricter phone number validation.ErrorCodeonfailed/undeliveredstatus. Store this code and refer to Plivo SMS Error Code Documentation to understand failure reasons.<!-- EXPAND: Add table of most common Plivo error codes with recommended actions (Type: Enhancement) -->
Sources: Plivo Node.js SDK v4.74.0 (npm, January 2025), Express.js 5.1.0 release notes (March 2025), Node.js v22 LTS announcement (October 2024), Plivo signature validation documentation (2024), helmet v8.1.0 and express-rate-limit v8.1.0 (npm, January 2025)
How Do You Test Webhooks Locally with ngrok?
Plivo sends callbacks to publicly accessible URLs. Use ngrok to expose your local server during development:
1. Install ngrok: Download from ngrok.com and follow installation instructions.
2. Start Your Server:
Your server runs on
http://localhost:3000by default.3. Start ngrok: Open a new terminal and run:
ngrok provides a public HTTPS URL (e.g.,
https://abc123.ngrok.io) that forwards to your local server.<!-- GAP: Missing ngrok authentication setup - free tier limitations not explained (Type: Substantive) -->
4. Update BASE_URL: Copy the ngrok HTTPS URL and update your
.envfile:5. Restart Your Server: Stop and restart
npm run devto load the newBASE_URL.6. Send Test SMS:
Plivo sends callbacks to
https://abc123.ngrok.io/plivo/callback, which ngrok forwards to your local server.<!-- EXPAND: Add expected output examples for each test step (Type: Enhancement) -->
What Production Best Practices Should You Follow?
Deploy your webhook server with these production-ready patterns:
1. Use Environment-Specific Configuration:
.envfiles for development, staging, production.envfiles to version control2. Implement Robust Logging:
<!-- DEPTH: Logging advice lacks concrete examples of WHAT to log and what to sanitize (Priority: Medium) -->
3. Add Health Checks and Monitoring:
/healthendpoint (already included)/readinessendpoint for Kubernetes/load balancer checks4. Scale Horizontally:
<!-- GAP: Queue system integration not explained - no code examples provided (Type: Substantive) -->
5. Secure Your Endpoints:
<!-- GAP: Missing Plivo's webhook IP ranges for whitelisting (Type: Critical) -->
6. Optimize Database:
7. Implement Graceful Degradation:
Frequently Asked Questions
How do Plivo SMS delivery callbacks work?
Plivo sends HTTP POST requests to your webhook URL when message status changes (queued → sent → delivered/failed). Your server receives the callback, validates the signature using HMAC SHA-256 with your auth token, and processes the status update. Configure callbacks per-message via the
urlparameter inmessage_create()or set application-level defaults in the Plivo console.What's the difference between X-Plivo-Signature-V2 and V3?
Plivo uses X-Plivo-Signature-V2 for SMS callbacks and X-Plivo-Signature-V3 for voice callbacks. SMS webhooks validate using
validateSignature(url, body, signature, auth_token), while voice usesvalidateRequestSignatureV3(url, nonce, signature, auth_token)with an additional nonce header. Always check which header is present to use the correct validation method.Why is my webhook signature validation failing?
Common causes: (1) Incorrect auth token in
.env, (2) URL mismatch between ngrok/deployed URL and Plivo configuration, (3) Using wrong validation method (V2 vs V3), (4) Body parsing middleware not applied before validation, (5) Request body modified before validation. Verify your auth token, ensure the exact URL matches, and validate before any body transformation.<!-- EXPAND: Add step-by-step debugging checklist for signature validation failures (Type: Enhancement) -->
How do I retry failed SMS messages?
Check the
Statusfield in callbacks. Forfailedorundelivered, inspectErrorCodeto determine if retry is appropriate. Don't retry30000-30049(invalid numbers) or30050-30099(carrier rejections). Retry network errors (30100-30199) with exponential backoff. Store failed messages in your database and implement a scheduled retry job with maximum attempt limits.<!-- DEPTH: Retry logic described but no code example provided (Priority: High) -->
Can I use this code with Express 4.x?
Yes. Change
package.jsondependency to"express": "^4.21.2"(latest 4.x version). Express 4.16+ includes body parsing viaexpress.json()andexpress.urlencoded(), so no changes needed to the middleware code. Express 5.1 requires Node.js 18+, while Express 4.x supports older Node.js versions down to v12 (check specific version requirements).How do I handle duplicate callback requests?
Plivo may send duplicate callbacks during network issues. Implement idempotency by checking if the
MessageUUIDalready exists in your database before processing. Use database constraints (UNIQUEonmessage_uuid) or application-level checks. Store callback timestamps to identify and skip duplicate recent requests within a time window (e.g., 5 minutes).<!-- DEPTH: Idempotency described conceptually but lacks implementation code (Priority: Medium) -->
What database should I use for production?
This guide uses SQLite3 for simplicity. For production, migrate to PostgreSQL (structured, ACID-compliant, excellent query performance) or MongoDB (flexible schema, horizontal scaling). PostgreSQL is recommended for transactional systems requiring complex queries and joins. MongoDB works well for high-write-volume logging with flexible event schemas.
How do I scale webhook handling for high volume?
Implement these patterns: (1) Use queue systems (Redis, RabbitMQ, AWS SQS) to decouple webhook receipt from processing, (2) Scale horizontally with load balancers and multiple server instances, (3) Optimize database writes with batch inserts and connection pooling, (4) Cache frequently accessed data with Redis, (5) Use async processing for non-critical operations, (6) Monitor with APM tools (New Relic, Datadog) to identify bottlenecks.
Do I need separate endpoints for SMS and voice callbacks?
Not required but recommended for clarity. Use
/api/webhooks/smsfor SMS delivery status and/api/webhooks/voicefor voice events. This makes debugging easier and allows different validation logic, rate limits, and processing workflows. Apply signature validation middleware per-route based on expected callback type (V2 for SMS, V3 for voice).How long should I wait before marking a message as failed?
Plivo typically delivers status updates within seconds to minutes. If no
deliveredstatus arrives within 24-48 hours, consider the message failed. Implement a scheduled job that queries messages stuck insentstatus beyond your threshold and marks them as failed. Check Plivo's delivery logs via the API or console for additional context before taking action.<!-- GAP: No code example for scheduled job to mark stuck messages as failed (Type: Substantive) -->
What Should You Do Next?
You've built a production-ready SMS delivery tracking system with Plivo webhooks, Node.js, and Express. Next steps:
<!-- DEPTH: Next steps are generic - lacks prioritization and time estimates (Priority: Medium) -->
Additional Resources:
Start tracking delivery status today to improve reliability, debug issues faster, and deliver better messaging experiences.