Frequently Asked Questions
Track SMS delivery status using Plivo's webhook feature, which sends real-time updates to your application. Configure a callback URL in your Plivo settings and your application will receive delivery reports via HTTP POST requests to this URL. These reports contain details like message status, UUID, and error codes.
A Plivo Message UUID is a unique identifier assigned to each SMS message sent through the Plivo platform. This UUID is crucial for tracking the delivery status of individual messages and associating them with specific delivery reports received via webhooks.
Plivo uses webhooks (callbacks) for delivery reports to provide real-time status updates as they happen. Instead of your application constantly polling Plivo for updates, Plivo pushes the information to your application as soon as it's available, making the process more efficient and responsive.
Use ngrok during local development with Plivo to expose your local server to the internet, allowing Plivo to send webhooks to your machine. Ngrok creates a public URL that tunnels requests to your localhost, essential for testing webhook functionality before deployment.
Yes, you can send SMS messages using Plivo's Node.js SDK. The SDK simplifies interaction with the Plivo API. Include the recipient's number, the message text, and the callback URL for receiving delivery status updates.
Set up the SMS delivery report URL by specifying the url parameter in the client.messages.create() function when sending a message using the Plivo Node.js SDK. This URL points to your application's endpoint where Plivo will send the delivery status updates. The URL should use HTTPS if possible.
The Plivo Node.js SDK simplifies interaction with the Plivo API, allowing developers to easily send SMS messages, make calls, and manage other Plivo services directly from their Node.js applications. The SDK handles authentication, request formatting, and response parsing.
Verify the Plivo webhook signature using the X-Plivo-Signature-V2 header and the crypto module in Node.js. Compute the HMAC-SHA256 hash of the webhook request URL concatenated with the nonce (X-Plivo-Signature-V2-Nonce) using your Plivo Auth Token as the key. Compare this hash with the received signature using crypto.timingSafeEqual() to prevent timing attacks.
The 'Status: delivered' in a Plivo callback indicates that the SMS message was successfully delivered to the recipient's handset. This signifies a successful transmission and confirms that the message reached its intended destination, allowing your system to proceed accordingly, for example by marking the message as successfully sent in your application's database.
Plivo requires phone numbers to be in E.164 format (e.g., +14155551234) to ensure consistent and unambiguous number representation for global SMS delivery. This standardized format facilitates accurate routing and delivery of messages across different countries and carriers.
Handle multiple Message UUIDs in Plivo callbacks by processing each UUID individually, as each represents a segment of a long SMS. Update the status for each segment in your database. You might choose to track all segment statuses or focus on the first segment's status as a general indicator of delivery.
Common causes of Plivo signature verification failures include incorrect Auth Tokens, URL mismatches between the one sent to Plivo and the one reconstructed on your server, and incorrect usage of the nonce header. Double-check all parameters and ensure URL reconstruction accounts for proxies and non-standard ports.
Troubleshoot Plivo SMS sending issues by checking for accurate Plivo Auth ID, Auth Token, and Sender ID. Verify recipient numbers are in valid E.164 format and within allowed sending limits of your Plivo account type (e.g., sandbox limitations). Inspect Plivo logs for error messages and check your application logs for request failures or exceptions.
Tracking the delivery status of SMS messages is crucial for applications that rely on timely and reliable communication. Knowing whether a message reached the recipient's handset, failed, or was rejected allows you to build more robust workflows, provide better user feedback, and troubleshoot issues effectively.
This guide provides a complete walkthrough for building a Node.js application using the Express framework to send SMS messages via Plivo and receive real-time delivery status updates through webhooks (callbacks).
Project Goals:
Technologies Used:
dotenv: A module to load environment variables from a.envfile, keeping sensitive credentials out of source code.ngrok(for development): A tool to expose local development servers to the internet, enabling Plivo to send webhooks to your local machine.System Architecture:
The basic flow involves sending the message and receiving the status callback:
/sms/delivery-report).MessageUUID.MessageUUID, deliveryStatus, and other details.X-Plivo-Signature-V2header to ensure the request genuinely came from Plivo.200 OKstatus to acknowledge receipt of the callback.MessageUUIDfor monitoring or further processing.Prerequisites:
ngrok(Optional, for local development): Download and installngrok. (https://ngrok.com/download)1. Setting Up the Project
Let's create the project directory, initialize it, and install the necessary dependencies.
Create Project Directory: Open your terminal or command prompt and create a new directory for your project.
Initialize Node.js Project: This creates a
package.jsonfile to manage your project's dependencies and scripts.(The
-yflag accepts the default settings)Install Dependencies: We need Express for the web server, the Plivo SDK, and
dotenvfor managing environment variables.Set Up Environment Variables: Create a file named
.envin the root of your project directory. This file will store your sensitive Plivo credentials. Never commit this file to version control.PLIVO_AUTH_ID&PLIVO_AUTH_TOKEN: Find these on your Plivo Console dashboard (https://console.plivo.com/dashboard/). Copy the Auth ID and Auth Token.PLIVO_SENDER_ID: This is the Plivo phone number (in E.164 format, e.g.,+14155551212) or Alphanumeric Sender ID you'll use to send messages. You must own this number/ID in your Plivo account.PORT: The local port your Express server will listen on.BASE_URL: The public base URL where your application will be reachable. For local development withngrok, you'll update this later.Create
.gitignore: Ensure your.envfile andnode_modulesdirectory are not committed to Git. Create a file named.gitignore:Project Structure: Your basic project structure should look like this:
We will add our application code files shortly.
2. Implementing Core Functionality: Sending SMS
We'll create a simple script to send an SMS message using the Plivo SDK. Crucially, we will specify the
urlparameter in themessages.createcall. This URL tells Plivo where to send the delivery status updates.Create
sendSms.js: Create a file namedsendSms.jsin your project root.Explanation:
require('dotenv').config();: Loads variables from your.envfile intoprocess.env.new plivo.Client(): Initializes the Plivo client with your credentials.sendSms Function:deliveryReportUrlusing theBASE_URLfrom.envand a specific path (/sms/delivery-report). This is the endpoint we will create in our Express app.client.messages.create()with:src: Your Plivo sender ID/number.dst: The recipient's phone number (must be in E.164 format).text: The content of the SMS.url: The crucial parameter pointing to your webhook endpoint.method: Specifies that Plivo should use thePOSTHTTP method to send data to yoururl.POSTis generally preferred for sending data.+and digits.Testing (Initial): You can try running this now, but the callback will fail because we haven't set up the server to receive it yet.
(Replace
<your_recipient_phone_number>with a valid phone number in E.164 format, preferably one you can check. If using a trial account, this must be a number verified in your Plivo Sandbox).You should see the ""SMS Sent Successfully!"" message and the Message UUID. Check the Plivo logs (https://console.plivo.com/logs/message/) – you'll likely see the message initially as ""queued"" or ""sent"", and later an attempt (and failure) by Plivo to POST to your (non-existent) callback URL.
3. Building the API Layer: Receiving Callbacks
Now, let's create the Express server and the specific endpoint (
/sms/delivery-report) to receive the delivery status callbacks from Plivo.Create
server.js: Create a file namedserver.jsin your project root.Explanation:
express.urlencoded({ extended: true }): Parses incoming requests withContent-Type: application/x-www-form-urlencoded. Theextended: trueoption allows for rich objects and arrays to be encoded into the URL-encoded format. Plivo V2 signature verification does not require the raw request body.verifyPlivoSignature: This custom middleware is essential for security.X-Plivo-Signature-V2andX-Plivo-Signature-V2-Nonceheaders sent by Plivo, along with thePLIVO_AUTH_TOKENfrom environment variables.req.protocol,req.get('host')(which includes hostname and port, making it more robust behind proxies), andreq.originalUrl.baseStringby concatenating the full URL and the nonce.crypto.createHmac('sha256', ...)with yourPLIVO_AUTH_TOKENand thebaseString.crypto.timingSafeEqual(important to prevent timing attacks). Atry...catchblock handles potential errors during comparison (e.g., invalid base64), and an explicit length check is added beforetimingSafeEqual.next()to proceed to the route handler. Otherwise, it sends a403 Forbiddenor400 Bad Requestresponse./health(GET): A simple endpoint to check if the server is running./sms/delivery-report(POST):verifyPlivoSignaturemiddleware before the main handler.req.body), which includesStatus,MessageUUID,From,To, and potentiallyErrorCode.200 OKstatus code promptly to acknowledge receipt. Failure to do so might cause Plivo to retry the callback.4. Integrating with Plivo & Local Testing (
ngrok)To receive callbacks on your local machine during development, you need a way to expose your local server to the public internet.
ngrokis perfect for this.Start Your Local Server: Open a terminal in your project directory and run:
You should see
Server listening on port 3000....Start
ngrok: Open a second terminal window (leave the server running) and startngrok, telling it to forward to the port your server is running on (e.g., 3000).Get Your Public
ngrokURL:ngrokwill display output similar to this:Copy the
https://forwarding URL (e.g.,https://<random_string>.ngrok.io). This is your temporary public URL. Using HTTPS is strongly recommended.Update
.env: Go back to your.envfile and update theBASE_URLwith your publicngrokHTTPS URL.Restart Your Server (Important): Stop your Node.js server (
Ctrl+Cin the first terminal) and restart it (node server.js) after changing the.envfile to ensure it picks up the newBASE_URL.Send Another Test SMS: Now_ run the
sendSms.jsscript again with a valid recipient number.Verify Callback:
node server.jsis running. Within a few seconds to minutes (depending on the carrier), you should see the log output from the/sms/delivery-reportroute, including ""Plivo signature verified successfully."", theStatus(e.g.,delivered,sent,failed,undelivered), andMessageUUID.ngrokLogs: The terminal runningngrokwill show incomingPOST /sms/delivery-reportrequests with a200 OKresponse. You can also inspect requests in detail via thengrokweb interface (usuallyhttp://127.0.0.1:4040).200 OKstatus.5. Error Handling, Logging, and Retry Mechanisms
sendSms.jsscript includes basictry...catcharound the Plivo API call. Plivo errors often include specific codes. Refer to Plivo API error documentation: https://www.plivo.com/docs/api/overview/#api-status-codesserver.jshandles signature verification errors (400/403). Other potential errors (like database issues if you add them) should be caught within the route handler, logged, but still ideally return a2xxcode to Plivo unless the request itself was malformed (4xx). Avoid5xxresponses if possible, as Plivo might retry aggressively.console.logfor simplicity. For production, use a more robust logging library likewinstonorpino.sendSms.jsfails due to a temporary network issue or a Plivo server error (5xx), you might implement a simple retry logic (e.g., usingasync-retry) with exponential backoff.2xxwithin a timeout period (typically ~5 seconds). Your responsibility is to ensure your endpoint is reliable, responds quickly (under the timeout), and handles requests idempotently (if necessary, though usually not required for simple status logging based on uniqueMessageUUID).6. Database Schema and Data Layer (Conceptual)
While this guide doesn't implement a database, here's how you would typically integrate it:
Schema: You'd likely have a table to store message details and their status updates.
Integration:
sendSms.js: After successfully callingclient.messages.create, insert a new record intosms_messageswith themessageUuid, recipient, sender, text, initial response, and setstatusto 'queued' or 'sent' based on the API response.server.js(/sms/delivery-report):MessageUUIDandStatus(andErrorCodeif present) fromreq.body.sms_messagesusing theMessageUUID.status,status_timestamp, anderror_codefor that record.Data Layer: Use an ORM (like Sequelize, Prisma, TypeORM) or a query builder (like Knex.js) to interact with your database safely and efficiently.
7. Security Features
Webhook Signature Verification: Implemented and crucial. This prevents attackers from sending fake callbacks to your endpoint. Always use the latest signature version offered by Plivo (V2 currently).
Environment Variables: Keep
PLIVO_AUTH_ID,PLIVO_AUTH_TOKEN, and any other secrets out of your code and.envfiles out of version control.HTTPS: Always use HTTPS for your callback URL (
ngrokprovides this, and production deployments must use TLS/SSL). This encrypts the data in transit.Input Validation (Sending): If the destination number or message text in
sendSms.jscomes from user input, validate and sanitize it thoroughly to prevent injection attacks or abuse. Ensure numbers are in E.164 format.Rate Limiting: Apply rate limiting to your callback endpoint (
/sms/delivery-report) using middleware likeexpress-rate-limitto prevent abuse or accidental denial-of-service if Plivo retries excessively for some reason.Firewall: Configure server firewalls to only allow traffic on necessary ports (e.g., 443 for HTTPS). You might restrict access to the callback endpoint to only known Plivo IP addresses if feasible and documented by Plivo, but signature verification is generally the preferred and more flexible approach.
8. Handling Special Cases
dst) to be in E.164 format (e.g.,+14155551234). Ensure any user input is normalized to this format before sending.messages.createresponse might contain multiplemessageUuids. The delivery report callback will typically be sent per segment, each with its correspondingMessageUUID. Your database schema and logic should handle this many-to-one relationship if segment-level tracking is needed. Often, tracking the status of the first UUID is sufficient to know the overall message delivery attempt status.Statusvalues (queued,sent,delivered,failed,undelivered) and any associatedErrorCodevalues. See Plivo documentation for details. Build your application logic accordingly./sms/delivery-reportendpoint responds quickly (within Plivo's timeout, typically ~5 seconds, ideally under 1-2 seconds) with a200 OK. Long-running tasks should be offloaded to a background job queue (e.g., BullMQ, Kue) triggered by the callback, rather than being executed synchronously within the request handler.9. Performance Optimizations
/sms/delivery-reporthandler fast. Do minimal work: verify signature, parse data, acknowledge receipt (200 OK), and potentially enqueue a background job for heavier processing (like database updates, notifications).async/awaitfor I/O operations (like database calls, though ideally offloaded).message_uuidis indexed (ideally primary key) for fast lookups when updating status. Index other commonly queried fields likestatusorrecipient_number.k6,artillery, orautocannonto test how many concurrent callbacks your server can handle.clustermodule or a process manager likepm2in cluster mode to utilize multiple CPU cores.10. Monitoring, Observability, and Analytics
/healthendpoint provides a basic check. Production monitoring systems (like Prometheus/Grafana, Datadog, New Relic) should poll this endpoint.delivered,failed, etc.)./sms/delivery-reporthandler).failed/undeliveredstatuses.11. Troubleshooting and Caveats
401 Unauthorizederrors when sending SMS or signature verification failures. Double-check credentials in.envand the Plivo console.src(Sender ID): Ensure thePLIVO_SENDER_IDis a valid Plivo number (in E.164) or an approved Alphanumeric Sender ID associated with your account. Using an unowned number results in errors.dst(Recipient Number): Must be E.164 format. Sending to incorrectly formatted numbers will fail. Trial accounts can only send to numbers verified in the Sandbox.localhost. Usengrokfor local testing or deploy to a public server.sendSms: Ensure theurlparameter matches the endpoint route inserver.jsand uses the correctBASE_URL. Check for typos.ngrokand production URLs. Plivo may not send callbacks to HTTP URLs.node server.jsis running when expecting callbacks.200 OK: If your endpoint errors out (5xx) or times out, Plivo won't know you received the data and will retry, potentially causing duplicate processing if your handler isn't idempotent. Log errors but try to return200 OK.PLIVO_AUTH_TOKENis correct in your.envfile and matches the token in the Plivo Console.${req.protocol}://${req.get('host')}${req.originalUrl}) exactly matches what Plivo used. Checkngrokinspector or server logs for the exact URL Plivo called. Proxies or non-standard ports can sometimes alter hostname/protocol headers. Usingreq.get('host')is generally more robust thanreq.hostname.X-Plivo-Signature-V2,X-Plivo-Signature-V2-Nonce) are being read and used.express.urlencoded({ extended: true })middleware is used before your route handler if theContent-Typeisapplication/x-www-form-urlencoded(which is typical for Plivo callbacks). If Plivo were sending JSON (application/json), you would useexpress.json()instead.