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-V2
header (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.env
file.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:
url
parameter 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 theurl
you provided (your webhook endpoint).Prerequisites:
ngrok
installed 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.json
file 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.env
file.body-parser
: Since Express 4.16+_ body parsing is built into Express viaexpress.json()
andexpress.urlencoded()
. You don't need the separatebody-parser
package 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-parser
separately. 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.gitignore
file 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.env
in 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_URL
with 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.json
to easily run the server withnodemon
and 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 dev
to start the server (automatically restarts when you save changes) andnpm run db:setup
to 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-sms
Route (POST):sendSmsLimiter
middleware (express.json() already applied globally).to
andtext
_ checksBASE_URL
configuration_ and performs basic E.164 format check onto
.callbackUrl
.plivoClient.messages.create
_ passing the sender_ recipient_ text_ and the criticalurl: callbackUrl
option.messageUuid
from the response.messages
database table with themessage_uuid
and an initial status ofsubmitted
. Handles potential DB errors gracefully (logs error but doesn't fail the user request).messageUuid
if available./plivo/callback
Route (POST):callbackLimiter
andvalidatePlivoSignature
middleware.X-Plivo-Signature-V2
header (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:
messages
table with relevant columns for tracking SMS details and status.message_uuid
isUNIQUE NOT NULL
.message_uuid
(critical forUPDATE
performance in the callback handler),status
, andlast_updated_at
to speed up common queries.CREATE INDEX IF NOT EXISTS
ensures idempotency.server.js
route handlers using thesqlite3
driver. 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:
.env
file (PLIVO_AUTH_ID
,PLIVO_AUTH_TOKEN
).+12025551234
) intoPLIVO_NUMBER
in.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: callbackUrl
parameter in theclient.messages.create
call. This tells Plivo exactly where to send status updates for that specific message.Alternative Method: Plivo Application Message URL (Global Default)
url
parameter 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/callback
orhttps://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...catch
block 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
/text
and basicto
format. Returns 400 Bad Request on failure. Consider usinglibphonenumber-js
for stricter phone number validation.ErrorCode
onfailed
/undelivered
status. 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:3000
by 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
.env
file:5. Restart Your Server: Stop and restart
npm run dev
to 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:
.env
files for development, staging, production.env
files 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:
/health
endpoint (already included)/readiness
endpoint 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
url
parameter 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
Status
field in callbacks. Forfailed
orundelivered
, inspectErrorCode
to 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.json
dependency 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
MessageUUID
already exists in your database before processing. Use database constraints (UNIQUE
onmessage_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/sms
for SMS delivery status and/api/webhooks/voice
for 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
delivered
status arrives within 24-48 hours, consider the message failed. Implement a scheduled job that queries messages stuck insent
status 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.