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 `, replacing `` 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.
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.
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: To 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.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 ngrok1. Setting up the Project
Let's initialize our 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: This creates a
package.json
file to manage dependencies and project metadata.3. Install Dependencies:
express
: Web framework.plivo
: Plivo Node.js SDK.dotenv
: Loads environment variables from.env
file.body-parser
: Middleware to parse incoming request bodies (needed for Plivo callbacks).sqlite3
(Optional_ for database persistence): Driver for SQLite.helmet
: Basic security headers middleware.express-rate-limit
: Middleware for rate limiting.(or using yarn:
yarn add express plivo dotenv body-parser sqlite3 helmet express-rate-limit
)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 the following 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 will store sensitive credentials and configuration. Never commit this file to version control.Remember to 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 are 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.8. (Optional) Configure
nodemon
: Add scripts to yourpackage.json
to easily run the server withnodemon
and set up the database.Now you can run
npm run dev
to start the server, which will automatically restart when you save changes, andnpm run db:setup
to initialize the database schema.2. Implementing the Express Server
First, let's set up the basic Express server structure, load configuration, initialize the database connection, and set up middleware.
File:
server.js
(Initial Setup Part)3. Sending SMS and Handling Callbacks
Now, let's 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
andexpress.json()
middleware.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 likesubmitted
. Handles potential DB errors gracefully (logs error but doesn't fail the user request).messageUuid
if available./plivo/callback
Route (POST):callbackLimiter
andvalidatePlivoSignature
middleware.req.body
).MessageUUID
_Status
_ andErrorCode
.MessageUUID
. It 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.4. Database Schema and Data Layer
We need a way to store the message status. Here's the setup script for our SQLite database.
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.5. Integrating with 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
.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.
6. Error Handling, Logging, and Retry Mechanisms
Robust applications need solid error handling and observability.
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, thoughbody-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.Logging:
console.log
,console.warn
,console.error
.console.*
with a structured logging library like Winston or Pino for features like: