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.env
file, 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-V2
header to ensure the request genuinely came from Plivo.200 OK
status to acknowledge receipt of the callback.MessageUUID
for 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.json
file to manage your project's dependencies and scripts.(The
-y
flag accepts the default settings)Install Dependencies: We need Express for the web server, the Plivo SDK, and
dotenv
for managing environment variables.Set Up Environment Variables: Create a file named
.env
in 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.env
file andnode_modules
directory 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
url
parameter in themessages.create
call. This URL tells Plivo where to send the delivery status updates.Create
sendSms.js
: Create a file namedsendSms.js
in your project root.Explanation:
require('dotenv').config();
: Loads variables from your.env
file intoprocess.env
.new plivo.Client()
: Initializes the Plivo client with your credentials.sendSms Function
:deliveryReportUrl
using theBASE_URL
from.env
and 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 thePOST
HTTP method to send data to yoururl
.POST
is 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.js
in your project root.Explanation:
express.urlencoded({ extended: true })
: Parses incoming requests withContent-Type: application/x-www-form-urlencoded
. Theextended: true
option 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-V2
andX-Plivo-Signature-V2-Nonce
headers sent by Plivo, along with thePLIVO_AUTH_TOKEN
from environment variables.req.protocol
,req.get('host')
(which includes hostname and port, making it more robust behind proxies), andreq.originalUrl
.baseString
by concatenating the full URL and the nonce.crypto.createHmac('sha256', ...)
with yourPLIVO_AUTH_TOKEN
and thebaseString
.crypto.timingSafeEqual
(important to prevent timing attacks). Atry...catch
block 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 Forbidden
or400 Bad Request
response./health
(GET): A simple endpoint to check if the server is running./sms/delivery-report
(POST):verifyPlivoSignature
middleware before the main handler.req.body
), which includesStatus
,MessageUUID
,From
,To
, and potentiallyErrorCode
.200 OK
status 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.
ngrok
is 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
ngrok
URL:ngrok
will 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.env
file and update theBASE_URL
with your publicngrok
HTTPS URL.Restart Your Server (Important): Stop your Node.js server (
Ctrl+C
in the first terminal) and restart it (node server.js
) after changing the.env
file to ensure it picks up the newBASE_URL
.Send Another Test SMS: Now_ run the
sendSms.js
script again with a valid recipient number.Verify Callback:
node server.js
is running. Within a few seconds to minutes (depending on the carrier), you should see the log output from the/sms/delivery-report
route, including ""Plivo signature verified successfully."", theStatus
(e.g.,delivered
,sent
,failed
,undelivered
), andMessageUUID
.ngrok
Logs: The terminal runningngrok
will show incomingPOST /sms/delivery-report
requests with a200 OK
response. You can also inspect requests in detail via thengrok
web interface (usuallyhttp://127.0.0.1:4040
).200 OK
status.5. Error Handling, Logging, and Retry Mechanisms
sendSms.js
script includes basictry...catch
around 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.js
handles 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 a2xx
code to Plivo unless the request itself was malformed (4xx
). Avoid5xx
responses if possible, as Plivo might retry aggressively.console.log
for simplicity. For production, use a more robust logging library likewinston
orpino
.sendSms.js
fails 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.2xx
within 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_messages
with themessageUuid
, recipient, sender, text, initial response, and setstatus
to 'queued' or 'sent' based on the API response.server.js
(/sms/delivery-report
):MessageUUID
andStatus
(andErrorCode
if present) fromreq.body
.sms_messages
using theMessageUUID
.status
,status_timestamp
, anderror_code
for 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.env
files out of version control.HTTPS: Always use HTTPS for your callback URL (
ngrok
provides 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.js
comes 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-limit
to 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.create
response might contain multiplemessageUuid
s. 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.Status
values (queued
,sent
,delivered
,failed
,undelivered
) and any associatedErrorCode
values. See Plivo documentation for details. Build your application logic accordingly./sms/delivery-report
endpoint 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-report
handler 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/await
for I/O operations (like database calls, though ideally offloaded).message_uuid
is indexed (ideally primary key) for fast lookups when updating status. Index other commonly queried fields likestatus
orrecipient_number
.k6
,artillery
, orautocannon
to test how many concurrent callbacks your server can handle.cluster
module or a process manager likepm2
in cluster mode to utilize multiple CPU cores.10. Monitoring, Observability, and Analytics
/health
endpoint provides a basic check. Production monitoring systems (like Prometheus/Grafana, Datadog, New Relic) should poll this endpoint.delivered
,failed
, etc.)./sms/delivery-report
handler).failed
/undelivered
statuses.11. Troubleshooting and Caveats
401 Unauthorized
errors when sending SMS or signature verification failures. Double-check credentials in.env
and the Plivo console.src
(Sender ID): Ensure thePLIVO_SENDER_ID
is 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
. Usengrok
for local testing or deploy to a public server.sendSms
: Ensure theurl
parameter matches the endpoint route inserver.js
and uses the correctBASE_URL
. Check for typos.ngrok
and production URLs. Plivo may not send callbacks to HTTP URLs.node server.js
is 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_TOKEN
is correct in your.env
file and matches the token in the Plivo Console.${req.protocol}://${req.get('host')}${req.originalUrl}
) exactly matches what Plivo used. Checkngrok
inspector 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-Type
isapplication/x-www-form-urlencoded
(which is typical for Plivo callbacks). If Plivo were sending JSON (application/json
), you would useexpress.json()
instead.