Frequently Asked Questions
Always validate Plivo webhook signatures. This crucial security step verifies the authenticity of incoming requests, ensuring that the callback data truly originated from Plivo and protecting your application from malicious actors.
Implement a webhook endpoint in your Node.js application using a framework like NestJS. This endpoint receives real-time delivery updates from Plivo, allowing you to track message statuses like 'delivered', 'failed', or 'rejected'. This guide demonstrates building a robust system for managing these callbacks.
A Plivo delivery status callback (or webhook) is a notification sent by Plivo to your application when the status of an SMS message changes. This notification contains crucial information about the message delivery outcome, such as 'sent', 'delivered', 'failed', or other statuses. Callbacks enable real-time monitoring and response to delivery events.
NestJS provides a structured and efficient way to handle Plivo SMS callbacks. Its features, like validation pipes and dependency injection, simplify development and improve code maintainability. This makes building a robust and scalable callback handling system easier.
Yes, use a tool like ngrok to expose your local development server to the internet. This allows Plivo to send webhooks to your local endpoint during development and testing. Configure your Plivo application with the ngrok URL as the callback URL.
In the Plivo console, create a new application, providing a name and configuring the 'Delivery Report URL' to point to your application's callback endpoint. Ensure the method is set to 'POST'. Optionally, link a Plivo number to the application, though specifying the callback URL per message offers greater flexibility.
Data Transfer Objects (DTOs) in NestJS define the structure of incoming callback data. They enable automatic validation using decorators from 'class-validator', ensuring data integrity and type safety in your TypeScript code.
Plivo uses signature validation to ensure the security and integrity of webhook deliveries. It verifies that requests received by your application genuinely originate from Plivo, preventing unauthorized or malicious actors from sending fake callbacks.
Store Plivo credentials (Auth ID, Auth Token) as environment variables in a .env
file. Use the @nestjs/config
package in your NestJS application to load these variables securely, preventing them from being hardcoded in your codebase.
SQLite is suitable for development and testing. For production, consider more robust options like PostgreSQL or MySQL. Prisma, a database toolkit for Node.js, simplifies database interactions and schema management regardless of the chosen database.
NestJS, coupled with a DTO and the 'class-transformer', automatically parses incoming JSON payloads from Plivo into strongly-typed objects. This simplifies data access and ensures type safety within your application logic.
Return a 204 No Content status code. Plivo primarily needs an acknowledgment that you received the callback. A 2xx status signals successful receipt. Returning content isn't required.
Prisma simplifies database interactions in NestJS. Use it to define your data models, manage database migrations, and perform type-safe database queries, regardless of whether you use SQLite, PostgreSQL, MySQL, or other databases.
Use the client.messages.create()
method of the Plivo Node.js SDK. Provide your sender number, recipient number, message text, and an object with the url
property set to your callback URL and method
set to 'POST'.
Track the delivery status of every SMS message you send. Know whether your message reached its destination, failed, or was rejected – enabling robust error handling, accurate reporting, and responsive user feedback. This matters most for:
Plivo sends delivery status updates via webhooks – real-time notifications delivered to a URL you specify.
This guide walks you through building a production-ready system in Node.js using NestJS to receive, validate, process, and store Plivo SMS delivery status callbacks. You'll master project setup, secure callback handling, data storage, testing, and deployment considerations.
By the end, you'll have a functional NestJS application that:
Technologies you'll use:
npm install plivo
(use the actively maintained "plivo" package, not the deprecated "plivo-node").@nestjs/config
: For managing environment variables securely.ngrok
: (For local development) Exposes your local server to the internet so you can receive Plivo webhooks during testing.System Architecture:
Prerequisites:
npm install -g @nestjs/cli
ngrok
(optional but recommended for local testing): Download and installngrok
to expose your local development server.Time requirement: Approximately 1–2 hours for initial setup and testing. Additional time needed for production deployment and customization.
1. Set up your NestJS project
Create a new NestJS project and navigate into the directory.
plivo
: The official Plivo Node.js SDK.@nestjs/config
: For handling environment variables.class-validator
,class-transformer
: Used by NestJS for request validation via DTOs.prisma
,@prisma/client
: The Prisma CLI and Client for database interactions.Common installation issues:
npm ERR! peer dependency
warningsnpm install --legacy-peer-deps
EACCES
permission errorsnvm
to install Node.js without sudo, or prefix commands withnpx
npm install typescript@~5.3.0 --save-dev
Project structure:
NestJS provides a standard structure. You'll add modules for specific features like
messaging
andcallbacks
.2. Configure your Plivo account and application
Configure your Plivo account to send callbacks to your application.
Phone Numbers
→Buy Numbers
and purchase a number capable of sending SMS messages in your desired region. Note this number.Messaging
→XML Applications
.Add New Application
.NestJS Callback App
).http://example.com/callbacks/delivery-status
. You'll update this later with your actual endpoint URL (likely anngrok
URL during development).Method
toPOST
.Create Application
. Note theApp ID
generated (though you won't use it directly when specifying callback URLs per message).Phone Numbers
→Your Numbers
, click on your number, select your newly created application from theApplication Type
dropdown, and update. For this guide, you'll specify the callback URL directly when sending the message. This approach offers flexibility – different messages (transactional vs. marketing) sent from the same number can use different callback endpoints or logic.Trial account limitations:
3. Configure your environment
Never hardcode sensitive credentials – use environment variables.
Create
.env
file: In your project root, create a file named.env
:.env
is listed in your.gitignore
file to prevent committing secrets.Create
.env.example
for team documentation:Configure NestJS
ConfigModule
: Update your rootAppModule
to load and manage environment variables.ConfigModule.forRoot({ isGlobal: true })
makes theConfigService
available throughout your application via dependency injection without needing to importConfigModule
in every feature module.4. Build the callback endpoint
This endpoint receives the
POST
requests from Plivo containing delivery status information.Generate Module, Controller, Service:
Define the DTO (Data Transfer Object): Create a DTO to define the expected structure of the callback payload and enable automatic validation using
class-validator
.Why DTOs? They provide clear contracts for your API endpoints, enable automatic request body validation using decorators, and improve type safety in your TypeScript code.
Handling unexpected fields: NestJS validation pipes strip properties not defined in your DTO when
whitelist: true
is enabled (configured in step 4.3). Plivo may add new fields over time – store the complete raw payload in your database to avoid losing data during API updates.Implement the Controller: Set up the route to listen for POST requests.
Why
@HttpCode(HttpStatus.NO_CONTENT)
? Plivo needs acknowledgment (a 2xx status code) that you received the callback. Sending back content isn't necessary, and 204 No Content is semantically correct.Plivo retry behavior: If your endpoint returns a non-2xx status (4xx or 5xx), Plivo retries the webhook with exponential backoff. Return 204 after receiving the callback to prevent unnecessary retries, even if internal processing encounters errors. Handle failures asynchronously using retry queues or error monitoring.
Implement the Service: Define the business logic for handling the status update.
Handling out-of-order delivery: Callbacks may arrive out of sequence (e.g., "delivered" before "sent"). Store timestamps and use the most recent status by comparing
plivoTimestamp
values during upsert operations. Consider adding a status priority system if your application logic depends on status progression.5. Security: Validate Plivo signatures
Verify that incoming webhook requests actually originate from Plivo. Plivo provides signatures for this purpose.
Important: Plivo uses different signature versions for different services:
X-Plivo-Signature-V2
headerX-Plivo-Signature-V3
andX-Plivo-Signature-V3-Nonce
headersThis guide focuses on SMS delivery status callbacks, which use V2 signature validation. The validation process involves verifying the HMAC-SHA256 signature using your Plivo Auth Token.
Security best practices:
@nestjs/throttler
to limit requests:@Throttle(100, 60)
(100 requests per minute)Create Signature Validation Helper: Based on Plivo's documentation, create a utility function.
Important: Plivo's V2 signature for SMS includes the full URL (scheme, host, path, query) and the raw request body concatenated. Ensure you reconstruct this correctly. Getting the raw body requires specific configuration in NestJS.
Common validation failures:
X-Forwarded-*
handlingx-plivo-signature-v2
vsX-Plivo-Signature-V2
Enable Raw Body Parsing: Modify
main.ts
to access the raw request body.Create a NestJS Guard: Implement the signature validation logic within a guard.
Handling reverse proxies: When your NestJS app runs behind nginx, AWS ALB, or similar proxies, the
protocol
andhost
values may reflect internal routing instead of the public URL. Configure your proxy to forward original headers:Update your NestJS app to trust proxy headers:
Apply the Guard: Add
@UseGuards(PlivoSignatureGuard)
to thehandleDeliveryStatus
method inCallbacksController
, as shown earlier. Ensure theCallbacksModule
importsConfigModule
if it's not global, or providesConfigService
appropriately.6. Create a database schema and data layer (Prisma)
Store delivery statuses using Prisma and SQLite.
Initialize Prisma:
This creates
prisma/schema.prisma
and updates.env
withDATABASE_URL="file:./dev.db"
.Define Schema: Edit
prisma/schema.prisma
.Why store
rawPayload
? It's invaluable for debugging issues with callbacks or understanding unexpected data formats.Useful queries for reporting:
Run Migration: Apply the schema to your database.
This creates the SQLite database file (
prisma/dev.db
) and generates the Prisma Client.Create Prisma Service: Create a reusable service for Prisma Client.
Make sure
CoreModule
exportsPrismaService
and is imported inAppModule
.Inject PrismaService: The
CallbacksService
shown in Step 4 already includes the injection and usage ofPrismaService
.7. Send a message and trigger callbacks
Implement the functionality to send an SMS and tell Plivo where to send the delivery report.
Generate Messaging Module/Service/Controller:
Implement Messaging Service:
Crucial Point: The
{ url: callbackUrl }
parameter inclient.messages.create
tells Plivo where to send the delivery status for this specific message. This overrides any default URL set in the Plivo Application settings.Handling rate limits: Plivo enforces rate limits based on your account tier. Implement exponential backoff or use a message queue (Bull, BullMQ) to handle rate limit errors gracefully and retry failed sends.
Implement Messaging Controller (for testing): Add a simple endpoint to trigger sending an SMS.
Security warning: This test endpoint has no authentication. Add an API key guard, JWT authentication, or IP whitelist before deploying to production. Without protection, anyone can send SMS messages through your account and incur charges.
8. Test your webhook locally with ngrok
Before deploying to production, test your Plivo delivery status webhook locally using ngrok.
Start your NestJS application:
Expose your local server with ngrok:
ngrok will display a forwarding URL like
https://abc123.ngrok.io
.Update your
.env
file:Restart your NestJS application to pick up the new URL.
Send a test SMS:
Monitor the logs:
Watch your NestJS console for incoming callback requests. You should see:
ngrok debugging tools:
http://localhost:4040
to see all HTTP requests and responsesCommon ngrok issues:
brew install ngrok
(macOS) or download from ngrok.com9. Production deployment considerations
When deploying your NestJS Plivo webhook handler to production:
Use HTTPS: Plivo requires HTTPS for webhook endpoints in production. Use a reverse proxy (nginx, Caddy) or deploy to platforms with built-in SSL (Heroku, AWS, Google Cloud).
Update environment variables: Replace your
APP_BASE_URL
with your production domain.Database migration: Switch from SQLite to PostgreSQL or MySQL for production. Update your
DATABASE_URL
and Prisma schema accordingly:Example production
DATABASE_URL
:Error handling and retries: Plivo retries failed webhook deliveries with exponential backoff (typically 15 minutes, 1 hour, 4 hours, 12 hours). Implement idempotent processing using the
messageUuid
as a unique identifier to handle duplicate callbacks safely.Monitoring and alerting: Set up application monitoring (Datadog, New Relic, Sentry) to track webhook failures and processing errors. Key metrics to monitor:
Rate limiting: Implement rate limiting on your webhook endpoint to protect against potential abuse:
Scaling considerations: For high-volume applications (1,000+ messages/day), use a message queue (Redis, RabbitMQ, AWS SQS) to process callbacks asynchronously. This prevents webhook timeouts and enables horizontal scaling.
Platform-specific deployment:
APP_BASE_URL
tohttps://your-app.herokuapp.com
X-Forwarded-Proto
trusttrust proxy
in main.ts for load balancer supportFrequently Asked Questions (FAQ)
How do I handle Plivo delivery status callbacks in NestJS?
Create a dedicated NestJS controller with a POST endpoint to receive Plivo webhooks. Validate incoming requests using Plivo's V2 signature verification (X-Plivo-Signature-V2 header for SMS callbacks), parse the payload with DTOs, and store status updates in your database using Prisma or your preferred ORM.
What's the difference between Plivo V2 and V3 signature validation?
Plivo uses V2 signatures for SMS delivery status callbacks (X-Plivo-Signature-V2 header) and V3 signatures for voice callbacks (X-Plivo-Signature-V3 with nonce). V2 validates using URL + raw body, while V3 adds a nonce parameter for replay protection. For SMS webhooks, always use V2 validation.
How do I secure my Plivo webhook endpoint?
Implement signature validation using Plivo's Auth Token to verify requests originate from Plivo. Use NestJS guards to validate the X-Plivo-Signature-V2 header before processing any callback data. Never skip signature validation in production – it prevents unauthorized access and webhook spoofing.
What Node.js and NestJS versions should I use for Plivo integration?
Use Node.js v22 LTS (recommended for January 2025) with NestJS v11.1.6 or later. Node.js v22 provides active support until October 2025 and maintenance until April 2027. NestJS 11 requires minimum Node.js v18 and offers improved startup performance and enhanced JSON logging.
How do I test Plivo webhooks locally?
Use ngrok to expose your local NestJS server to the internet. Run
ngrok http 3000
, copy the HTTPS URL, update yourAPP_BASE_URL
environment variable, and send a test SMS through your API. Plivo delivers callbacks to your ngrok URL, which forwards them to your local server.What Plivo message statuses should I handle?
Handle these status values:
queued
(message accepted),sent
(submitted to carrier),delivered
(reached recipient),failed
(permanent failure),undelivered
(temporary failure), andrejected
(invalid recipient or content). Store each status with timestamp and error code for comprehensive delivery tracking.Common error codes:
10001
: Invalid destination number10002
: Sender ID not configured10003
: Insufficient account balance10004
: Spam detected or blocked content10006
: Message expired (carrier timeout)How do I store Plivo delivery statuses in a database?
Use Prisma with SQLite for development or PostgreSQL for production. Create a
DeliveryStatus
model with fields formessageUuid
(unique identifier),status
,errorCode
, recipient, sender, timestamps, and the raw payload JSON. Use Prisma'supsert
operation to handle multiple status updates for the same message.Can I use the same callback endpoint for multiple Plivo applications?
Yes. Specify the callback URL per message using the
url
parameter inclient.messages.create()
rather than relying on application-level settings. This approach offers flexibility – different message types (transactional, marketing, OTP) can use different callback URLs and processing logic while sharing the same Plivo phone number.How do I handle Plivo webhook retries?
Plivo automatically retries failed webhook deliveries with exponential backoff. Implement idempotent processing by using
messageUuid
as a unique constraint in your database. Use Prisma'supsert
operation to safely handle duplicate callbacks – update existing records instead of creating duplicates. Always return a 2xx status code (204 No Content) once you've received the callback, even if internal processing encounters errors.Plivo retry schedule:
What's the best way to debug Plivo webhook issues?
Enable detailed logging for signature validation, store the raw callback payload in your database, and monitor your application logs. Check that your
APP_BASE_URL
matches the URL Plivo sends requests to (including protocol and host). Use ngrok's web interface (http://localhost:4040) to inspect incoming webhook requests and responses during local development.Conclusion
You've built a production-ready NestJS application that handles Plivo SMS delivery status callbacks with secure signature validation, persistent data storage, and comprehensive error handling. This system gives you real-time visibility into message delivery, enabling you to build reliable SMS communication features with proper monitoring and debugging capabilities.
The V2 signature validation implementation protects your webhook endpoint from unauthorized requests, while Prisma provides type-safe database access for tracking delivery statuses. Your NestJS application now handles the complete SMS lifecycle – from sending messages through the Plivo API to processing delivery confirmations and storing results for reporting and analytics.
Next steps:
/messaging/send-test
endpoint with JWT or API key authenticationAdvanced patterns to consider:
TotalAmount
fieldAs you scale your SMS infrastructure, consider implementing message queues for asynchronous processing, migrating to PostgreSQL for production workloads, and adding monitoring tools to track webhook performance. The modular NestJS architecture you've built makes these enhancements straightforward to implement.
Ready to send your first message? Start your development server, expose it with ngrok, and test the complete webhook flow. Your delivery status tracking system is ready to power reliable SMS communication for your application.