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.envfile.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.jsonfile.Install Dependencies: We need Express for the server_ the Plivo SDK_
dotenvfor environment variables_wsfor WebSockets_ and Prisma for database interaction.express: Web server framework.plivo: Plivo's official Node.js SDK.dotenv: Loads environment variables from a.envfile.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
prismadirectory with aschema.prismafile and a.envfile (if one doesn't exist).Configure Environment Variables: Open the
.envfile 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_URLis crucial. Plivo needs a public URL to send callbacks to. We'll set this later using Ngrok for development.Create
.gitignore: Create a.gitignorefile in the root directory to avoid committing sensitive information and unnecessary files.Define Database Schema: Open
prisma/schema.prismaand define a model to store message status updates.MessageStatustable with relevant fields.messageUUIDis marked as unique to potentially update existing records (upsert) if multiple callbacks arrive for the same message.rawPayloadstores the complete JSON received from Plivo_ useful for debugging or future analysis.Apply Database Schema: Run the Prisma migration command to create the
MessageStatustable 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_URLin your.envfile.Create Server File: Create a file named
server.jsin 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.rawspecifically 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 IDandAuth Token. Ensure they are correctly set in your.envfile.https://random-string.ngrok.io). Copy thehttpsURL. This is your public base URL..env: Paste the Ngrok HTTPS URL (or your production URL) into your.envfile as theBASE_CALLBACK_URL.node server.jsor usingnodemon) after updating the.envfile for the changes to take effect. Check the server startup logs to ensureBASE_CALLBACK_URLis 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/callbackendpoint 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-Timestampheaders. The SDK provides a utility to validate these. Modify theserver.jsfile:validatePlivoSignaturemiddleware function.req.body(which contains the raw Buffer thanks toexpress.raw) and theplivo.validateV3Signaturemethod. Note: Plivo's V3 signature uses the Auth Token.next(). Otherwise, it sends a403 Forbiddenor400 Bad Request.rawBodyback intoreq.bodyas JSON if the content type was JSON. This makesreq.bodyaccessible 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/callbackPOST route.4. Processing Callbacks
The previous step already included the processing logic inside the
/plivo/callbackroute 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, andErrorCodeare extracted from the validatedpayloadobject.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).ErrorCodeprovides details.undelivered: The carrier couldn't deliver the message (e.g., number blocked, phone off, out of coverage).ErrorCodeprovides details.Refer to Plivo Message States for a full list and
ErrorCodedetails.5. Storing Status Updates (Database)
We use Prisma to save the status updates to our PostgreSQL database. This logic was implemented within the
try...catchblock of the/plivo/callbackroute in Step 3:prisma.messageStatus.upsertis used:messageUUIDmatches the incoming one.update), it updates thestatus,errorCode,rawPayload, andupdatedAttimestamp.create), it creates a new record with all the details.queuedthensentthendelivered). TherawPayloadfield 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...catchblock of the/plivo/callbackroute in Step 3, after the database save:broadcastfunction (defined in Step 1) sends the status update data as a JSON string to all currently connected WebSocket clients.typefield (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.