Frequently Asked Questions
Use Plivo's webhooks (callbacks) to receive real-time delivery status updates. Set up a Node.js (Express) backend to receive HTTP POST requests from Plivo containing message status information like 'queued', 'sent', 'delivered', or 'failed'.
The callback URL is the publicly accessible URL of your Node.js server endpoint that will receive status updates. In development, use Ngrok to expose your local server, then provide the Ngrok HTTPS URL to Plivo as your BASE_CALLBACK_URL
. In production, it's the public URL of your deployed application.
Callback validation ensures that incoming requests genuinely originate from Plivo and haven't been tampered with. This prevents unauthorized access and protects your application from malicious attacks.
Acknowledge Plivo's callback immediately with a 200 OK response before performing any database operations or sending WebSocket updates. This prevents Plivo from retrying the callback due to timeouts, ensuring reliable notification delivery.
Yes, you can implement real-time status updates in your frontend (e.g., React/Vue) by setting up a WebSocket server in your Node.js backend. Broadcast status updates to connected clients after receiving and processing a valid Plivo callback.
Create a POST endpoint in your Node.js Express app that uses the Plivo Node.js SDK's messages.create()
method. Provide your Plivo phone number, the recipient's number, the message text, and the callback URL for delivery updates.
Use app.post('/plivo/callback', validatePlivoSignature, callbackHandler)
to define your route. The validatePlivoSignature
middleware validates requests, and callbackHandler
processes and saves the status updates to the database.
Common statuses include 'queued', 'sent', 'delivered', 'failed', and 'undelivered'. 'Failed' indicates an issue on Plivo's end, while 'undelivered' indicates a carrier-side problem. ErrorCode
provides details for failures.
Use plivo.validateV3Signature
method from the Plivo Node SDK, including the request method, full URL, nonce, timestamp, your Plivo Auth Token, provided signature, and the raw request body string.
Use Prisma ORM (or your preferred database library) and a PostgreSQL (or your preferred) database. The provided example creates a MessageStatus
table to record the unique message UUID, current status, error codes, the full callback payload, and timestamps.
The messageUUID
is a unique identifier assigned by Plivo to each message you send. Use it to track individual messages, update status records in your database, and correlate callbacks to sent messages.
The key libraries are express
for the web server, plivo
for the Plivo Node.js SDK, dotenv
to load environment variables, and ws
to implement a WebSocket server for real-time updates to the frontend (optional).
Use an upsert operation in your database logic. This allows you to update the existing record for the messageUUID
if one exists, or create a new one if it doesn't, handling multiple status updates for a single message.
The project utilizes Node.js with Express, the Plivo Node SDK, PostgreSQL database, Prisma ORM, optionally WebSockets with the 'ws' library, and Ngrok for local development testing.
Reliably tracking the delivery status of SMS and MMS messages is crucial for applications that depend on timely communication. Misdelivered or failed messages can lead to poor user experience, missed notifications, and operational issues. Plivo provides a robust mechanism using webhooks (callbacks) to notify your application in real-time about the status of sent messages.
This guide provides a step-by-step walkthrough for building a production-ready Node.js (Express) backend service to receive, validate, process, and store Plivo SMS/MMS delivery status updates. We'll also cover sending messages and optionally pushing these status updates to a frontend (like React/Vue built with Vite) via WebSockets.
Project Goals:
queued
,sent
,delivered
,failed
).Technologies Used:
ws
: WebSocket library for real-time communication.dotenv
: Module to load environment variables from a.env
file.Prerequisites:
System Architecture:
Final Outcome:
By the end of this guide_ you will have a robust Node.js service capable of:
1. Project Setup
Let's initialize our Node.js project and install the necessary dependencies.
Create Project Directory: Open your terminal and create a new directory for the project.
Initialize Node.js Project:
This creates a
package.json
file.Install Dependencies: We need Express for the server_ the Plivo SDK_
dotenv
for environment variables_ws
for WebSockets_ and Prisma for database interaction.express
: Web server framework.plivo
: Plivo's official Node.js SDK.dotenv
: Loads environment variables from a.env
file.ws
: WebSocket server implementation.prisma
: Prisma CLI (dev dependency).@prisma/client
: Prisma database client.Initialize Prisma: Set up Prisma with PostgreSQL as the provider.
This creates a
prisma
directory with aschema.prisma
file and a.env
file (if one doesn't exist).Configure Environment Variables: Open the
.env
file created by Prisma (or create one) and add your Plivo credentials and database connection string. Replace the placeholder values with your actual credentials and URLs.BASE_CALLBACK_URL
is crucial. Plivo needs a public URL to send callbacks to. We'll set this later using Ngrok for development.Create
.gitignore
: Create a.gitignore
file in the root directory to avoid committing sensitive information and unnecessary files.Define Database Schema: Open
prisma/schema.prisma
and define a model to store message status updates.MessageStatus
table with relevant fields.messageUUID
is marked as unique to potentially update existing records (upsert) if multiple callbacks arrive for the same message.rawPayload
stores the complete JSON received from Plivo_ useful for debugging or future analysis.Apply Database Schema: Run the Prisma migration command to create the
MessageStatus
table in your database. Prisma will prompt you to create a migration file.Alternatively_ if you prefer not to use migration files during early development (use with caution):
Ensure your PostgreSQL database server is running and accessible using the
DATABASE_URL
in your.env
file.Create Server File: Create a file named
server.js
in the root directory. This will contain our Express application logic.This sets up the basic Express server, initializes Prisma and the WebSocket server, and defines placeholders for our routes and error handling. Note the use of
express.raw
specifically for the callback route – Plivo's signature validation needs the raw, unparsed request body.2. Plivo Setup and Callback URL
Before Plivo can send status updates, you need to tell it where to send them. This requires a publicly accessible URL.
Auth ID
andAuth Token
. Ensure they are correctly set in your.env
file.https://random-string.ngrok.io
). Copy thehttps
URL. This is your public base URL..env
: Paste the Ngrok HTTPS URL (or your production URL) into your.env
file as theBASE_CALLBACK_URL
.node server.js
or usingnodemon
) after updating the.env
file for the changes to take effect. Check the server startup logs to ensureBASE_CALLBACK_URL
is loaded.${BASE_CALLBACK_URL}/plivo/callback
. Example:https://<your-ngrok-subdomain>.ngrok.io/plivo/callback
.App ID
.3. Building the Callback Endpoint & Validation
Now, let's implement the
/plivo/callback
endpoint to receive and validate requests. Security is paramount here; we must ensure requests actually come from Plivo.Implement Validation Middleware: Plivo includes
X-Plivo-Signature-V3
,X-Plivo-Signature-V3-Nonce
, andX-Plivo-Signature-V3-Timestamp
headers. The SDK provides a utility to validate these. Modify theserver.js
file:validatePlivoSignature
middleware function.req.body
(which contains the raw Buffer thanks toexpress.raw
) and theplivo.validateV3Signature
method. Note: Plivo's V3 signature uses the Auth Token.next()
. Otherwise, it sends a403 Forbidden
or400 Bad Request
.rawBody
back intoreq.body
as JSON if the content type was JSON. This makesreq.body
accessible as an object in the main route handler. Added robust handling in the main route to manage cases where parsing might fail or the body isn't a buffer./plivo/callback
POST route.4. Processing Callbacks
The previous step already included the processing logic inside the
/plivo/callback
route after validation and the immediateres.status(200).send()
:res.status(200).send(...)
happens before database or WebSocket operations. This confirms receipt to Plivo promptly, preventing timeouts and retries on their end.MessageUUID
,Status
, andErrorCode
are extracted from the validatedpayload
object.Common Statuses:
queued
: Plivo has accepted the message.sent
: Plivo has sent the message to the downstream carrier.delivered
: The carrier confirmed delivery to the recipient's handset.failed
: Plivo couldn't send the message (e.g., invalid number, API error).ErrorCode
provides details.undelivered
: The carrier couldn't deliver the message (e.g., number blocked, phone off, out of coverage).ErrorCode
provides details.Refer to Plivo Message States for a full list and
ErrorCode
details.5. Storing Status Updates (Database)
We use Prisma to save the status updates to our PostgreSQL database. This logic was implemented within the
try...catch
block of the/plivo/callback
route in Step 3:prisma.messageStatus.upsert
is used:messageUUID
matches the incoming one.update
), it updates thestatus
,errorCode
,rawPayload
, andupdatedAt
timestamp.create
), it creates a new record with all the details.queued
thensent
thendelivered
). TherawPayload
field stores the entire JSON object received from Plivo for reference.6. Real-time Frontend Updates (WebSockets)
To push updates to a frontend, we use the WebSocket server. This logic was also added within the
try...catch
block of the/plivo/callback
route in Step 3, after the database save:broadcast
function (defined in Step 1) sends the status update data as a JSON string to all currently connected WebSocket clients.type
field (status_update
) so client-side code can differentiate message types.Client-Side Implementation (Conceptual Example for React/Vue):
ws:
orwss:
based on the page protocol.status_update
.7. Sending a Test Message
To trigger the callback flow, we need an endpoint to send an SMS/MMS message using the Plivo SDK.
Implement Send Endpoint: Add the following route to
server.js
.