Frequently Asked Questions
Implement Plivo webhooks with a Fastify server to receive real-time delivery updates. By specifying a webhook URL in your Plivo SMS API requests, the system automatically sends status updates to the specified URL as the message progresses through various stages, from queued to delivered or failed.
The Plivo Node SDK (@plivo/node-sdk) simplifies interaction with the Plivo API within your Node.js application. It handles authentication, API requests, and responses. The project goals of this article are to install dependencies needed for plivo, like: fastify, node.js, etc.
Fastify is a high-performance web framework for Node.js, offering speed and a developer-friendly experience. Its efficiency makes it well-suited for handling real-time updates from Plivo with minimal overhead.
Signature validation is paramount for security and should be performed at the very beginning of your /plivo/status route handler. This verification confirms that incoming requests genuinely originate from Plivo and haven't been tampered with.
Create a dedicated route (e.g., /plivo/status) in your Fastify application. This endpoint will receive POST requests from Plivo with message status updates like queued, sent, delivered, or failed.
Ngrok creates a secure tunnel to your local development server, making it publicly accessible. This is essential because Plivo webhooks require an HTTPS URL, even during development. ngrok fulfills this requirement, enabling status updates from plivo.
Always respond with a 200 OK to Plivo, even if your internal processing encounters errors. Log those errors for later investigation, but the immediate 200 OK prevents Plivo from continuously retrying the webhook.
The webhook payload includes essential information such as MessageUUID, Status (e.g., 'sent', 'delivered', 'failed'), Timestamp, From, To, and ErrorCode (in case of failures). This data helps in updating internal systems and triggering appropriate actions.
Yes, use a tool like ngrok to create a public HTTPS URL for your local server. Then, configure your Plivo SMS API requests to use this ngrok URL as the webhook URL, enabling Plivo to reach your local endpoint.
Implement robust signature validation using the X-Plivo-Signature-V3 and X-Plivo-Signature-V3-Nonce headers. This ensures only legitimate requests from Plivo are processed. Always use HTTPS and validate all incoming data.
Verify ngrok's status, ensure the BASE_URL in your .env file matches the ngrok URL, check server and Plivo console logs, examine firewalls, and confirm the URL path used in the API call to Plivo.
A 403 Forbidden error usually indicates a signature validation failure. Double-check your Plivo Auth Token, URL construction (especially behind proxies), proper handling of the nonce and raw request body, and ensure a timing-safe string comparison is used.
Design database updates to be idempotent. Use unique constraints based on MessageUUID and check timestamps to prevent older status updates from overwriting newer ones. This ensures data consistency even if Plivo sends duplicate webhooks.
Use a table with columns for message_uuid (unique), sender_number, recipient_number, message_body, initial_status, current_status, status_timestamp, error_code, and standard timestamps (created_at, updated_at). Index message_uuid for efficient lookups.
Real-time SMS Delivery Status: Implementing Plivo Webhooks with Fastify
Tracking the delivery status of SMS messages is crucial for applications that rely on timely communication. Knowing whether a message was successfully delivered, failed, or is still queued enables developers to build more robust and reliable systems, trigger follow-up actions, and provide better user feedback.
This guide provides a complete walkthrough for building a Fastify application that receives and processes SMS delivery status updates from Plivo via webhooks. We'll cover everything from initial project setup to deployment considerations, ensuring you have a production-ready solution.
Project Goals:
Technology Stack:
plivo(formerly@plivo/node-sdkor the legacyplivo-node)..envfile.System Architecture:
The basic flow is as follows:
urlparameter pointing to your webhook endpoint.queued,sent,delivered,failed), Plivo sends an HTTPPOSTrequest containing the status details to the specified webhook URL.POSTrequest, processes the payload (e.g., logs the status), and responds to Plivo.(Note: The diagram above uses Mermaid syntax. You may need a Markdown previewer or environment that supports Mermaid to render it correctly.)
Prerequisites:
ngrokor a similar tunneling service installed for local development testing.Final Outcome:
By the end of this guide, you will have a running Fastify application capable of sending SMS messages through Plivo and receiving real-time delivery status updates at a dedicated webhook endpoint. You'll also understand the essential considerations for making this system robust and secure.
1. Setting up the Project
Let's start by creating our project directory and setting up the basic structure and dependencies.
Step 1: Create Project Directory
Open your terminal and create a new directory for the project, then navigate into it.
Step 2: Initialize Node.js Project
Initialize the project using npm. The
-yflag accepts the default settings.Step 3: Install Dependencies
We need Fastify for the web server, the Plivo Node SDK to interact with the Plivo API, and
dotenvto manage environment variables.Step 4: Install Development Dependencies
We'll use
nodemonto automatically restart the server during development when files change andpino-prettyfor more readable logs.Step 5: Configure
package.jsonScriptsOpen your
package.jsonfile and add the following scripts to the"scripts"section. This allows us to easily start the server in development (withnodemonand pretty logs) or production mode.Step 6: Create
.gitignoreFileCreate a
.gitignorefile in the root of your project to prevent sensitive information and unnecessary files from being committed to version control.Step 7: Create
.envFile for Environment VariablesCreate a file named
.envin the project root. This file will store sensitive credentials and configuration. Never commit this file to Git.Step 8: Obtain Plivo Credentials and Configure
.envPlivo Auth ID & Token:
PLIVO_AUTH_IDandPLIVO_AUTH_TOKENfields in your.envfile.Plivo Phone Number:
+14155551212).PLIVO_PHONE_NUMBERfield in your.envfile.Recipient Phone Number:
PHONE_NUMBER_TOto your own mobile number in E.164 format (e.g.,+14155552323) for testing.Base URL:
BASE_URLblank for now. We will fill this in when we runngroklater (Section 4). This URL tells Plivo where to send the status updates. It must be publicly accessible. Plivo requires HTTPS for webhooks in production, and it's highly recommended even for development.ngrokprovides HTTPS URLs automatically.Project Structure:
Your project should now look like this:
2. Implementing Core Functionality
Now_ let's create the Fastify server and implement the routes for sending SMS and receiving status updates.
Step 1: Create
server.jsCreate a file named
server.jsin the project root.Step 2: Basic Server Setup and Environment Loading
Add the following code to initialize Fastify_ load environment variables_ and configure the Plivo client.
dotenvfirst to ensure environment variables are available.logger: true).BASE_URLcheck is moved, as it's only needed when sending the first SMS that requires a callback URL.startfunction attempts to listen on the specified port (defaulting to 3000) and logs success or failure. Listening on0.0.0.0makes the server accessible from outside the container/machine if needed.Step 3: Implement the Webhook Endpoint (
/plivo/status)This route will listen for
POSTrequests from Plivo containing message status updates.Add the following code inside
server.js, before the// --- Start the server ---comment:/plivo/status(POST):request.body.MessageUUID,Status).200 OKresponse promptly. Heavy processing should occur after this response./health(GET): Standard health check endpoint./send-sms(GET):BASE_URLbefore attempting to send, returning an error if it's missing.webhookUrl.plivoClient.messages.createwith theurlandmethodfor the callback.3. Building the API Layer (Webhook Focus)
For this guide, the primary "API" is the webhook endpoint
/plivo/statusdesigned to be consumed by Plivo.X-Plivo-Signature-V3header and your Auth Token.zodfor more complex checks.POST /plivo/statusapplication/x-www-form-urlencodedorapplication/json. Example Payload (Form-urlencoded):X-Plivo-Signature-V3,X-Plivo-Signature-V3-Nonce.200 OK: Success. Body like"Webhook received successfully".401/403 Forbidden: If signature validation fails.4xx/5xx: Plivo may retry. Avoid unless necessary.4. Integrating with Plivo (Setup Recap & Ngrok)
We've configured the Plivo client. Now, make your local server accessible using
ngrok.Step 1: Start Ngrok
Open a new terminal window. Run
ngrokto expose your Fastify port (default 3000).Step 2: Get Ngrok URL
Copy the HTTPS forwarding URL provided by
ngrok(e.g.,https://xxxxxxxxxxxx.ngrok.io).Step 3: Update
.envFilePaste the copied HTTPS
ngrokURL into theBASE_URLvariable in your.envfile.Step 4: Restart Fastify Server
Stop your Fastify server (
Ctrl+C) and restart it (npm run dev) to load the updated.envfile.Your server is now running, and the
BASE_URLis set, allowing the/send-smsroute to work correctly and Plivo to reach your/plivo/statusendpoint via thengrokURL.5. Error Handling, Logging, and Retry Mechanisms
fastify.log.info,.warn,.error). Log context, especially error objects:fastify.log.error({ err: error }, '...'). Configure production logging appropriately./plivo/status):try...catchfor internal logic (DB updates, etc.).200 OKto Plivo even if internal processing fails (log the internal error). Handle internal failures separately (alerts, queues).401/403 Forbiddenonly if signature validation fails./send-sms):plivoClient.messages.createintry...catch.500,502) to the client triggering the send.2xxresponses or timeouts. Respond200 OKquickly unless the request is invalid/unauthorized.plivoClient.messages.createfails due to transient issues.6. Database Schema and Data Layer (Conceptual)
A production system needs to store status updates.
Conceptual Schema (PostgreSQL):
Data Layer (
/plivo/status):200 OK(or asynchronously):MessageUUID,Status,Timestamp,ErrorCode.UPDATEstatement based onmessage_uuid, potentially checkingstatus_timestampto avoid overwriting newer updates with older ones.Data Layer (
/send-sms):plivoClient.messages.create:INSERTa new record with status'pending'.UPDATEthe record with the returnedMessageUUIDandinitial_status(e.g.,'queued').7. Adding Security Features
Securing your webhook endpoint is critical.
Webhook Signature Validation (CRITICAL):
X-Plivo-Signature-V3(andX-Plivo-Signature-Ma-V3for additional verification) along withX-Plivo-Signature-V3-Nonceheaders with every webhook request.X-Plivo-Signature-V3andX-Plivo-Signature-V3-Noncefrom the request headers.https://yourdomain.com/plivo/status). Important: If behind a proxy or load balancer, you may need to inspect headers likeX-Forwarded-Proto,X-Forwarded-Host, orHostto reconstruct the correct URL.url + nonce. Note that for V3 signature validation, Plivo does NOT include POST parameters in the signature calculation (unlike some earlier versions).url + nonce) with yourPLIVO_AUTH_TOKENas the secret key, then Base64-encode the result.X-Plivo-Signature-V3header value. If they match, the request is valid.validateSignature()or similar helpers that can simplify this process.HTTPS: Enforce HTTPS for all webhook endpoints. Plivo requires HTTPS for production webhooks.
ngrokprovides HTTPS automatically for development. In production, use a reverse proxy (Nginx, Caddy) or load balancer with TLS termination.Input Validation: Sanitize and validate all incoming data before database operations, even after signature validation.
Rate Limiting: Consider using
@fastify/rate-limitplugin as a defense-in-depth measure to protect against abuse.Secrets Management: Never commit credentials to version control. In production, use secure environment variable management provided by your platform (AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, etc.) or tools like HashiCorp Vault.
8. Handling Special Cases
queued,sent,failed,delivered,undelivered,rejected.ErrorCodefield forfailed/undeliveredstatuses. Refer to Plivo docs for code meanings.TIMESTAMPTZfor storage.UPDATE ... WHERE message_uuid = ? AND (current_status != ? OR status_timestamp < ?)). Check timestamps to avoid overwriting newer statuses with delayed older ones.9. Implementing Performance Optimizations
200 OKimmediately.message_uuid.infoorwarnin production.10. Adding Monitoring_ Observability_ and Analytics
/healthfor basic monitoring.11. Troubleshooting and Caveats
ngrokstatus/URL_BASE_URLin.env_ server logs_ Plivo console logs (Monitor -> Logs), firewalls, URL path in API call.PLIVO_AUTH_TOKEN, URL construction (ensure you're using the exact URL Plivo sees, including protocol and host), nonce handling, and that you're calculating the signature correctly (URL + nonce only, no POST params for V3).cloudflareddaemon and configure a tunnel to your local/staging server.12. Deployment and CI/CD
BASE_URLto your production domain with proper DNS and HTTPS configured.pm2, systemd, Docker, or container orchestration (Kubernetes, AWS ECS, GCP Cloud Run) for process lifecycle management and automatic restarts.nvmor specify the version in your deployment configuration.Dockerfile:/healthendpoint for automatic restart on failures.13. Verification and Testing
Manual Verification Checklist:
npm run dev).ngrokrunning,BASE_URLupdated and server restarted./health-> shows{"status":"ok"}./send-sms.ngrokweb UI (http://127.0.0.1:4040): SeePOSTs to/plivo/statuswith200 OK.4xxerror.Automated Testing (Integration): Use Fastify's testing capabilities.
npm install --save-dev tapserver.jsfor Testability: Modifyserver.jsto export a function that builds and returns the Fastify app instance, instead of starting it directly.test/routes.test.js):