code examples
code examples
Implementing Plivo SMS Delivery Status Callbacks in Next.js
A guide on building a Next.js API route webhook to securely receive and process Plivo SMS delivery status callbacks, including setup, validation, and deployment.
Reliably tracking the delivery status of SMS messages is crucial for applications that depend on timely communication. When you send an SMS via Plivo, the initial API response only confirms that Plivo accepted the message, not that it reached the recipient's handset. To get real-time updates on message delivery (e.g., delivered, failed, undelivered), you need to implement a webhook endpoint that Plivo can call back.
This guide provides a step-by-step walkthrough for building a robust webhook endpoint using Next.js API Routes to receive and process Plivo's message status callbacks. We will cover project setup, secure handling of Plivo requests, data persistence (optional), error handling, deployment, and verification.
Project Goal: Build a Next.js application with a dedicated API endpoint that securely receives message status updates from Plivo, validates the requests, and optionally stores the status information.
Technologies Used:
- Next.js: A React framework providing server-side rendering, static site generation, and simplified API route creation. Chosen for its developer experience and ease of deploying serverless functions.
- Plivo: A cloud communications platform providing SMS and Voice APIs. We'll use its SMS API and webhook features.
- Plivo Node SDK: Simplifies interaction with the Plivo API, particularly for validating webhook signatures.
- TypeScript: (Recommended) Adds static typing for improved code quality and maintainability.
- (Optional) Prisma: A modern database toolkit for Node.js and TypeScript, used here for optionally storing status updates.
- (Optional) ngrok: A tool to expose local servers to the internet, essential for testing webhooks during development.
System Architecture:
+-----------------+ Sends SMS +------------+ Sends Status +---------------------+
| Your Application| -----------------> | Plivo API | -----------------> | Next.js API Route |
| (e.g., Next.js) | +------------+ (Webhook) | (/api/plivo/status) |
+-----------------+ +----------+----------+
| Processes &
| Validates Status
v
+---------------------+
| (Optional) Database |
| (e.g., PostgreSQL) |
+---------------------+Prerequisites:
- Node.js (LTS version recommended) and npm/yarn/pnpm installed.
- A Plivo account with Auth ID and Auth Token. (Free trial available).
- A code editor (e.g., VS Code).
- Basic understanding of Next.js, APIs, and asynchronous JavaScript/TypeScript.
- (Optional, for local testing)
ngrokinstalled globally (npm install -g ngrok) or available vianpx. - (Optional, for database persistence) Docker or a running PostgreSQL instance for Prisma.
1. Setting up the Project
Let's initialize a new Next.js project and install necessary dependencies.
-
Create a Next.js App: Open your terminal and run:
bashnpx create-next-app@latest plivo-nextjs-callbacks --typescript --eslint --tailwind --src-dir --app --import-alias '@/*'- We use TypeScript (
--typescript) for better type safety. --appenables the App Router, which is standard for new Next.js projects and where we'll build our API route.- Other flags set up common tools like ESLint and Tailwind CSS (optional but common).
- We use TypeScript (
-
Navigate to Project Directory:
bashcd plivo-nextjs-callbacks -
Install Plivo SDK:
bashnpm install plivo-nodeThis package provides helper functions, most importantly for validating incoming webhook requests from Plivo.
-
(Optional) Install Prisma for Database Persistence: If you want to store the delivery statuses:
bashnpm install prisma --save-dev npm install @prisma/clientInitialize Prisma:
bashnpx prisma init --datasource-provider postgresqlThis creates a
prismadirectory with aschema.prismafile and a.envfile for your database connection string. -
Configure Environment Variables: Create a file named
.env.localin the root of your project. Never commit this file to version control. Add your Plivo credentials and (if using Prisma) your database URL:dotenv# .env.local # Plivo Credentials # Find these in your Plivo Console: https://console.plivo.com/dashboard/ PLIVO_AUTH_ID="YOUR_PLIVO_AUTH_ID" PLIVO_AUTH_TOKEN="YOUR_PLIVO_AUTH_TOKEN" # (Optional) Database URL for Prisma # Example for local PostgreSQL using default user/password 'postgres'/'postgres' # Replace with your actual database connection string DATABASE_URL="postgresql://postgres:postgres@localhost:5432/plivo_callbacks?schema=public" # Base URL for your application (needed for signature validation) # For local dev with ngrok, this will be your ngrok URL (e.g., https://random-subdomain.ngrok-free.app) # For production, this will be your deployed URL (e.g., https://your-app.vercel.app) APP_BASE_URL="http://localhost:3000" # Replace later with ngrok/production URL- Purpose: Storing sensitive credentials like API keys and database URLs outside your codebase is crucial for security.
.env.localis used by Next.js for local development environment variables. APP_BASE_URL: This is critical for Plivo signature validation, as the validation function needs the exact URL Plivo is sending the request to. We'll update this later for local testing and production.
- Purpose: Storing sensitive credentials like API keys and database URLs outside your codebase is crucial for security.
2. Implementing the Callback API Route
We'll create a Next.js API Route to handle incoming POST requests from Plivo.
-
Create the API Route File: Inside the
src/app/api/directory, create the following folder structure and file:src/app/api/plivo/status/route.ts -
Implement the API Logic: Paste the following code into
src/app/api/plivo/status/route.ts:typescript// src/app/api/plivo/status/route.ts import { NextRequest, NextResponse } from 'next/server'; import * as plivo from 'plivo-node'; // Optional: Import Prisma client if storing data // import { PrismaClient } from '@prisma/client'; // Optional: Initialize Prisma Client. // Note: For serverless environments, initializing the client directly in the module scope // can lead to connection pool exhaustion. Consider using a cached helper function // as recommended in Prisma's documentation for serverless environments. // See: https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#serverless-environments // const prisma = new PrismaClient(); export async function POST(request: NextRequest) { console.log('Received request on /api/plivo/status'); // --- 1. Get Required Headers and Base URL --- const signature = request.headers.get('X-Plivo-Signature-V3'); const nonce = request.headers.get('X-Plivo-Signature-V3-Nonce'); const appBaseUrl = process.env.APP_BASE_URL; const plivoAuthId = process.env.PLIVO_AUTH_ID; const plivoAuthToken = process.env.PLIVO_AUTH_TOKEN; // --- 2. Input Validation --- if (!signature || !nonce) { console.error('Missing Plivo signature headers'); return NextResponse.json({ error: 'Missing signature headers' }, { status: 400 }); } if (!plivoAuthId || !plivoAuthToken) { console.error('Plivo Auth ID or Token not configured in environment variables'); return NextResponse.json({ error: 'Server configuration error' }, { status: 500 }); } if (!appBaseUrl) { console.error('APP_BASE_URL not configured in environment variables'); return NextResponse.json({ error: 'Server configuration error: APP_BASE_URL missing' }, { status: 500 }); } // Construct the full URL Plivo is sending the request to const requestUrl = appBaseUrl + '/api/plivo/status'; try { // --- 3. Get Request Body (Plivo sends form data) --- const formData = await request.formData(); const bodyParams: Record<string, string> = {}; formData.forEach((value, key) => { if (typeof value === 'string') { bodyParams[key] = value; } }); console.log('Received body params:', bodyParams); // --- 4. Validate Plivo Signature --- // Why: Ensures the request genuinely came from Plivo and wasn't tampered with. const isValid = plivo.validateV3Signature( requestUrl, // The FULL URL Plivo sends the request to nonce, signature, plivoAuthToken // Use Auth Token for validation ); if (!isValid) { console.error('Invalid Plivo signature'); return NextResponse.json({ error: 'Invalid signature' }, { status: 403 }); // Use 403 Forbidden } console.log('Plivo signature validated successfully.'); // --- 5. Process the Status Update --- const messageUuid = bodyParams.MessageUUID; const status = bodyParams.Status; const errorCode = bodyParams.ErrorCode; // Present if status is 'failed' or 'undelivered' const fromNumber = bodyParams.From; // Sender ID or source number const toNumber = bodyParams.To; // Destination number console.log(`Processing status for MessageUUID: ${messageUuid}`); console.log(`Status: ${status}, ErrorCode: ${errorCode || 'N/A'}`); // --- (Optional) 6. Store Status in Database --- /* if (messageUuid && status) { try { // Ensure prisma client is initialized if using this block // const prisma = new PrismaClient(); // Or use cached instance const updatedStatus = await prisma.messageStatus.upsert({ where: { messageUuid: messageUuid }, update: { status: status, errorCode: errorCode, updatedAt: new Date(), }, create: { messageUuid: messageUuid, status: status, errorCode: errorCode, fromNumber: fromNumber, // Store additional context if needed toNumber: toNumber, // Store additional context if needed receivedAt: new Date(), updatedAt: new Date(), }, }); console.log(`Successfully saved status for ${messageUuid}: ${status}`); } catch (dbError) { console.error(`Database error saving status for ${messageUuid}:`, dbError); // Decide if this should be a 500 error, or if logging is sufficient. // For now, we log but still return 200 to Plivo (e.g., to prevent Plivo retries // for potentially transient DB issues, while ensuring the failure is logged for investigation). // Consider queuing the update for retry as an alternative strategy. } } else { console.warn('Missing MessageUUID or Status in callback data.'); } */ // --- 7. Respond to Plivo --- // Why: Plivo expects a 2xx response to acknowledge receipt. // Failure to respond correctly will cause Plivo to retry the callback. return NextResponse.json({ message: 'Status received successfully' }, { status: 200 }); } catch (error) { console.error('Error processing Plivo callback:', error); // Generic error response return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); } } // Add a simple GET handler for basic testing/health check if desired export async function GET() { return NextResponse.json({ message: 'Plivo status endpoint is active. Use POST for callbacks.' }); }Code Explanation:
- Headers & URL: Retrieves the necessary
X-Plivo-Signature-V3andX-Plivo-Signature-V3-Nonceheaders sent by Plivo. Crucially, it constructs therequestUrlusing theAPP_BASE_URLenvironment variable and the route's path (/api/plivo/status). This exact URL is required for signature validation (e.g., ifAPP_BASE_URLishttps://foo.ngrok.app, the URL used must behttps://foo.ngrok.app/api/plivo/status). - Input Validation: Checks if required headers and environment variables are present.
- Request Body: Plivo sends callback data as
application/x-www-form-urlencoded. We userequest.formData()to parse it into a usable JavaScript object (bodyParams). - Signature Validation: This is the core security mechanism.
plivo.validateV3Signatureuses the full request URL, the nonce (a unique value per request), the signature provided by Plivo, and your Plivo Auth Token to verify the request's authenticity. If validation fails, a403 Forbiddenresponse is returned. - Process Status: Extracts relevant fields like
MessageUUID,Status, andErrorCodefrom the validated data. - (Optional) Store Status: If using Prisma, this section demonstrates an
upsertoperation. It tries to find a record bymessageUuidand update its status, or creates a new record if one doesn't exist. Usingupserthelps handle potential duplicate callbacks from Plivo gracefully (idempotency). Error handling for the database operation is included, with a note on deciding how to respond to Plivo upon database failure. - Respond to Plivo: Returns a
200 OKJSON response. This is critical – Plivo needs this acknowledgment to know the callback was received successfully. If Plivo receives a non-2xxresponse or times out, it will retry sending the callback according to its retry policy.
- Headers & URL: Retrieves the necessary
3. (Optional) Creating a Database Schema
If you chose to store status updates, define the schema.
-
Define Prisma Schema: Open
prisma/schema.prismaand add theMessageStatusmodel:prisma// prisma/schema.prisma generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model MessageStatus { id String @id @default(cuid()) // Unique database ID messageUuid String @unique // Plivo's unique message identifier status String // e.g., 'queued', 'sent', 'delivered', 'failed', 'undelivered' errorCode String? // Plivo error code if status is 'failed' or 'undelivered' fromNumber String? // Optional: store sender toNumber String? // Optional: store receiver receivedAt DateTime @default(now()) // When the first callback was received updatedAt DateTime @updatedAt // When the status was last updated @@index([status]) // Index status for faster querying @@index([updatedAt]) // Index update time }messageUuid: Marked as@uniquebecause it's the primary identifier from Plivo.errorCode: Optional (?) as it's only present for certain statuses.- Indexes: Added for potentially common query patterns (e.g., finding all failed messages).
-
Run Database Migration: Apply the schema changes to your database:
bashnpx prisma migrate dev --name add_message_statusThis command creates an SQL migration file and applies it to your database, creating the
MessageStatustable. It will also generate/update the Prisma Client based on your schema. -
Uncomment Prisma Code: Go back to
src/app/api/plivo/status/route.tsand uncomment the Prisma-related lines (import, client initialization, and the "Store Status in Database" section). Remember to handle Prisma client instantiation appropriately for your environment (e.g., using a cached helper in serverless).
4. Configuring Plivo
Now, tell Plivo where to send the status updates.
-
Log in to Plivo Console: Go to https://console.plivo.com/.
-
Navigate to Messaging -> Applications: https://console.plivo.com/messaging/application/
-
Create or Edit an Application:
- Click ""Add New Application"".
- Give it a recognizable name (e.g., ""Next.js Callbacks App"").
- Find the Message URL field under ""Messaging Settings"".
- Crucially, set the ""Method"" dropdown next to Message URL to
POST. - In the Message URL field, you need to enter the publicly accessible URL of your API route (
/api/plivo/status).- For Local Development: You'll use
ngrok. See Section 5 below. - For Production: You'll use your deployed application URL (e.g.,
https://your-app-name.vercel.app/api/plivo/status).
- For Local Development: You'll use
- Leave other fields (like Answer URL, Hangup URL) blank unless you are also handling voice calls with this application.
- Click ""Create Application"".
-
(Optional) Assign a Plivo Number: If you want Plivo to automatically use this application when receiving messages on a specific Plivo number, go to Phone Numbers -> Your Numbers, select a number, and choose your newly created application from the ""Application"" dropdown. This is not strictly required for outbound message status callbacks but is good practice if you handle both inbound and outbound.
-
Using the Application When Sending SMS: When sending an outbound SMS using the Plivo API, you need to specify the
urlparameter in your API request, pointing to your application's Message URL or directly to your webhook endpoint URL. Using an Application is generally cleaner. If you associated a number with the app, messages sent from that number might automatically use the app's settings (check Plivo defaults), but explicitly setting theurlparameter when sending is the most reliable way to ensure status callbacks are sent to the correct endpoint.Example (Conceptual Node.js sending code):
javascript// Example of sending SMS and specifying the callback URL const plivo = require('plivo-node'); const client = new plivo.Client(process.env.PLIVO_AUTH_ID, process.env.PLIVO_AUTH_TOKEN); client.messages.create( '+14155551212', // Source number (Must be a Plivo number or Alphanumeric Sender ID) '+14155551213', // Destination number 'Hello from Next.js with callbacks!', // Text { // THIS IS KEY: Point to your callback endpoint url: process.env.APP_BASE_URL + '/api/plivo/status', method: 'POST' // Ensure method matches your endpoint } ).then(function(message_created) { console.log(message_created); }).catch(function(err) { console.error(err); });
5. Local Development and Testing with ngrok
Plivo needs to reach your development machine to send callbacks. ngrok creates a secure tunnel.
-
Start Your Next.js Dev Server:
bashnpm run devThis usually starts the server on
http://localhost:3000. -
Start
ngrok: Open a new terminal window and run:bashngrok http 3000- Replace
3000if your Next.js app runs on a different port.
- Replace
-
Get the
ngrokURL:ngrokwill display output similar to this:textSession Status online Account Your Name (Plan: Free) Version x.x.x Region United States (us-cal-1) Forwarding https://<random-subdomain>.ngrok-free.app -> http://localhost:3000 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00Copy the
https://URL (e.g.,https://<random-subdomain>.ngrok-free.app). This is your temporary public URL. -
Update Environment Variable: Open your
.env.localfile and updateAPP_BASE_URLwith thisngrokHTTPS URL:dotenv# .env.local # ... other vars APP_BASE_URL="https://<random-subdomain>.ngrok-free.app" # Use your actual ngrok URLRestart your Next.js development server (
Ctrl+Candnpm run dev) for the change to take effect. -
Update Plivo Application: Go back to your Plivo Application settings in the Plivo Console. Paste the full
ngrokcallback URL into the Message URL field:https://<random-subdomain>.ngrok-free.app/api/plivo/statusEnsure the method isPOST. Save the application settings. -
Test by Sending an SMS: Use the Plivo API (via code like the example in Section 4, the Plivo console, Postman, or
curl) to send an SMS from a Plivo number associated with your application or by explicitly setting theurlparameter to yourngrokcallback URL. -
Observe Logs:
- Watch the terminal running your Next.js app (
npm run dev). You should see logs like "Received request...", "Received body params...", "Plivo signature validated...", and potentially "Successfully saved status..." if using the database. - Watch the terminal running
ngrok. You should seePOST /api/plivo/statusrequests listed with200 OKresponses.
- Watch the terminal running your Next.js app (
6. Error Handling, Logging, and Retries
- Error Handling: The provided API route includes basic
try...catchblocks. For production, consider more specific error handling:- Catch database errors separately from validation errors.
- Return appropriate HTTP status codes (400 for bad requests, 403 for auth failures, 500 for server errors).
- Logging: The current
console.logis suitable for development. For production:- Use a structured logging library (e.g., Pino, Winston) to output logs in JSON format.
- Include request IDs for tracing.
- Adjust log levels (e.g., log informational messages in dev, but only warnings/errors in prod).
- Integrate with log management services (e.g., Datadog, Logtail, Axiom).
- Plivo Retries: Plivo automatically retries sending callbacks if it doesn't receive a
2xxresponse within a timeout period (typically 5 seconds). Retries happen with an exponential backoff. This means your endpoint needs to be:- Fast: Process the request quickly. Offload heavy tasks (e.g., complex database updates, calling other APIs) to background jobs if necessary.
- Idempotent: Design your logic (especially database writes) so that processing the same callback multiple times doesn't cause incorrect side effects. The
upsertexample helps achieve this.
7. Security Considerations
- Signature Validation: This is the most critical security measure. Always validate the
X-Plivo-Signature-V3header usingplivo.validateV3Signatureand your Auth Token. Ensure you are using the exact full URL (includinghttps://and the path/api/plivo/status) that Plivo is configured to call. For example, ifAPP_BASE_URLishttps://foo.ngrok.app, the URL used for validation must behttps://foo.ngrok.app/api/plivo/status. - Environment Variables: Keep your Plivo Auth ID, Auth Token, and Database URL secure in environment variables. Do not commit them to your repository. Use
.env.localfor local development and your hosting provider's mechanism (e.g., Vercel Environment Variables) for production. - HTTPS: Always use HTTPS for your callback URL.
ngrokprovides this automatically for local testing, and platforms like Vercel enforce it for deployments. - Input Sanitization: While Plivo's data is generally structured, it's good practice to treat all incoming data as potentially untrusted. If you use the callback data in database queries or display it in a UI, ensure proper sanitization or escaping to prevent injection attacks (though Prisma helps with SQL injection).
- Rate Limiting: While Plivo is unlikely to abuse your endpoint, consider implementing rate limiting on your API route if you expose it publicly for other reasons, or as a general security hardening measure.
8. Handling Different Statuses and Edge Cases
- Status Types: Plivo sends various statuses:
queued,sent,delivered,undelivered,failed. Your application logic might need to react differently based on the status (e.g., notify support forfailedmessages, retry sendingundeliveredmessages later). - Error Codes: When the status is
failedorundelivered, theErrorCodefield provides more detail. Consult the Plivo Error Codes documentation to understand the reasons for failure. - Duplicate Callbacks: Due to network issues or retry logic, Plivo might occasionally send the same status update more than once. Ensure your endpoint is idempotent (as discussed with
upsert). - Callback Order: Callbacks might not always arrive in chronological order (e.g., a
deliveredstatus might arrive before asentstatus in rare network conditions). Rely on the timestamp (updatedAtin the Prisma schema) if strict ordering is critical, or design your logic to handle out-of-order updates gracefully.
9. Performance Optimizations
- Stateless API Route: Next.js API Routes deployed on platforms like Vercel are often serverless functions. They should be fast and stateless. Avoid heavy computations directly within the request handler.
- Asynchronous Processing: If handling a callback requires significant work (e.g., calling multiple other services, complex data processing), acknowledge Plivo's request quickly (return
200 OK) and then trigger a background job (using services like Vercel Background Functions, BullMQ, or external queueing systems) to perform the heavy lifting. - Database Indexing: If storing statuses, ensure appropriate database indexes are created (like on
messageUuidandstatus) to speed up queries. The Prisma schema example includes basic indexes. - Caching: Caching is generally not applicable for the receiving endpoint itself, but might be relevant if you build a separate API or UI to query the stored statuses.
10. Monitoring and Observability
- Logging: As mentioned, structured logging is key. Monitor logs for errors (signature validation failures, database errors, unexpected statuses).
- Error Tracking: Integrate an error tracking service (e.g., Sentry, Bugsnag) to capture and alert on exceptions within your API route.
- Platform Metrics: Use your hosting platform's monitoring tools (e.g., Vercel Analytics and Logs) to track invocation counts, execution duration, and error rates for your
/api/plivo/statusfunction. - Health Checks: The simple
GEThandler in the example can serve as a basic health check, although monitoring invocation logs and error rates is usually more informative for webhook endpoints. - Plivo Logs: Regularly check the Plivo Console Logs (Messaging -> Logs) to see the status of callbacks Plivo attempted to send and any errors reported by Plivo itself.
11. Troubleshooting and Caveats
- Callbacks Not Received:
- Check URL: Double-check the
Message URLin your Plivo Application settings. Ensure it exactly matches your public endpoint URL (ngrokor production), includes/api/plivo/status, and useshttps. - Check Method: Ensure the method in Plivo is set to
POST. - Check
ngrok: If testing locally, ensurengrokis running and hasn't expired. Check thengrokconsole for request logs. - Check Server Logs: Look for any errors in your Next.js application logs when a callback should have arrived.
- Firewall: Ensure no firewall is blocking incoming requests to your endpoint (less common with
ngrokor standard hosting platforms like Vercel, but possible in self-hosted scenarios). - Check Plivo Logs: Look in the Plivo Console for logs related to the message; it might show errors encountered when trying to send the callback.
- Check URL: Double-check the
403 Forbidden/ Invalid Signature Errors:- Verify
APP_BASE_URL: Ensure theAPP_BASE_URLenvironment variable in your Next.js app exactly matches the base URL part of the endpoint configured in Plivo (e.g.,https://<random-subdomain>.ngrok-free.apporhttps://your-app.vercel.app). Remember to includehttps://. - Verify Auth Token: Ensure the
PLIVO_AUTH_TOKENenvironment variable is correct and matches the Auth Token used in your Plivo account. The validation function uses the Auth Token, not the Auth ID. - Restart Server: If you changed environment variables, ensure you restarted your Next.js server.
- Full URL: Confirm the validation logic uses the full path (
process.env.APP_BASE_URL + '/api/plivo/status').
- Verify
500 Internal Server Error:- Check your Next.js server logs for detailed error messages (e.g., database connection issues, coding errors, missing environment variables like
DATABASE_URL).
- Check your Next.js server logs for detailed error messages (e.g., database connection issues, coding errors, missing environment variables like
400 Bad Request/ Missing Headers:- Indicates Plivo might not be sending the expected signature headers, or there's an issue with how your server/proxy handles headers. Check Plivo configuration and any intermediate proxy settings.
- Data Not Saving to Database:
- Check Next.js logs for database-specific errors.
- Verify
DATABASE_URLis correct. - Ensure migrations have been run (
npx prisma migrate dev). - Check database permissions.
ngrokFree Tier Limitations: Freengroktunnels have rate limits and temporary URLs. For sustained testing or production, consider a paidngrokplan or deploying to a staging environment.
12. Deployment and CI/CD
Deploying a Next.js app with API routes is straightforward on platforms like Vercel or Netlify.
Deploying to Vercel (Example):
- Push to Git: Ensure your project is pushed to a Git repository (GitHub, GitLab, Bitbucket). Do not commit
.env.local. Use a.gitignorefile (Next.js includes a default one). - Import Project in Vercel: Log in to Vercel and import the Git repository. Vercel usually auto-detects Next.js projects.
- Configure Environment Variables: In the Vercel project settings (Settings -> Environment Variables), add:
PLIVO_AUTH_IDPLIVO_AUTH_TOKENDATABASE_URL(Use your production database connection string)APP_BASE_URL(Set this to your Vercel production URL, e.g.,https://your-project-name.vercel.app) Ensure these are configured for the ""Production"" environment (and optionally ""Preview"" and ""Development"").
- Deploy: Trigger a deployment (usually happens automatically on push to the main branch).
- Update Plivo Application URL: Once deployed, Vercel provides a production URL. Update the
Message URLin your Plivo Application settings to use this production URL (e.g.,https://your-project-name.vercel.app/api/plivo/status). Ensure the method isPOST. - CI/CD: Vercel automatically sets up CI/CD. Pushing to your main branch will trigger a build and deployment. You can configure preview deployments for other branches.
- (Optional) Run Migrations: For database changes, you might need to add a build step to your
package.jsonor Vercel build settings to run migrations:""build"": ""prisma generate && prisma migrate deploy && next build"". Note: Running migrations during the build process requires careful consideration, especially regarding database credentials and permissions. Sometimes manual migration execution or a separate migration service is preferred.
- (Optional) Run Migrations: For database changes, you might need to add a build step to your
13. Verification and Testing
- Manual Verification:
- Deploy your application (staging or production).
- Configure the Plivo Application
Message URLto point to your deployed endpoint. - Send an SMS using the Plivo API (like the example code, or via Plivo's API explorer) ensuring you specify the
urlparameter pointing to your deployed endpoint. - Check your application logs (Vercel Logs or your configured logging service) to confirm the callback was received, validated, and processed (including database saves if applicable).
- Check the Plivo Console Logs to see the callback status from Plivo's perspective.
- Automated Testing:
- Unit Tests: Write unit tests for the signature validation logic (you might need to mock Plivo's SDK functions or create test vectors). Test the data extraction and processing logic.
- Integration Tests: Create tests that simulate a POST request to your
/api/plivo/statusendpoint with sample Plivo payloads (including valid and invalid signatures/data). Assert that your endpoint returns the correct HTTP status codes and responses. If using a database, verify data persistence. - End-to-End Tests (Optional/Complex): A full E2E test would involve sending a real SMS via Plivo and verifying the callback is received by a deployed test instance. This is more complex due to the external dependencies and potential costs.
Frequently Asked Questions
How to set up Plivo SMS status callbacks in Next.js?
Create a Next.js API route at `/api/plivo/status` to receive POST requests. Install the `plivo-node` SDK to validate signatures, ensuring requests originate from Plivo. Configure your Plivo application's Message URL to point to this endpoint, selecting POST as the method.
What is the purpose of Plivo's X-Plivo-Signature-V3 header?
The `X-Plivo-Signature-V3` header, along with the nonce, is crucial for verifying the authenticity of incoming webhook requests from Plivo. It ensures that the request genuinely came from Plivo and hasn't been tampered with, preventing security risks.
Why does Plivo send SMS status callbacks?
Plivo sends status callbacks to provide real-time updates on the delivery status of your SMS messages. The initial API response only confirms Plivo accepted the message, not final delivery. Callbacks provide updates such as 'delivered', 'failed', or 'undelivered'.
When should I use ngrok for Plivo callback testing?
Use `ngrok` during local development to create a secure tunnel, allowing Plivo to reach your local server. This lets you test your webhook endpoint before deploying, ensuring it handles callbacks correctly.
Can I store Plivo SMS statuses in a database?
Yes, the article recommends using Prisma, a database toolkit, to optionally store status updates in a database like PostgreSQL. This allows you to maintain a history of message delivery statuses for analysis or reporting.
How to validate Plivo webhook signatures in Next.js?
Use the `plivo.validateV3Signature` function from the `plivo-node` SDK. Provide the full request URL, nonce, signature, and your Plivo Auth Token. Ensure the URL used matches your Plivo application's Message URL exactly, including `https://` and the path.
What is the role of the APP_BASE_URL environment variable?
`APP_BASE_URL` stores the base URL of your application, crucial for constructing the full URL used in signature validation. This variable is essential for Plivo to correctly verify incoming webhook requests, and it changes depending on your environment (local vs. production).
Why does my Next.js Plivo callback API route return a 403 error?
A 403 Forbidden error usually indicates signature validation failure. Double-check your `APP_BASE_URL` and `PLIVO_AUTH_TOKEN` environment variables. Ensure they match your Plivo application settings and that the URL used for validation includes the full path (`/api/plivo/status`).
How to handle Plivo callback retries in Next.js?
Ensure your API route is idempotent, meaning processing the same callback multiple times doesn't have unintended side effects. Using Prisma's `upsert` for database updates can achieve this. Design your logic to handle potential duplicate callbacks gracefully.
What are the different Plivo SMS callback statuses?
Plivo callbacks include statuses like `queued`, `sent`, `delivered`, `undelivered`, and `failed`. Implement logic in your Next.js route to handle each status appropriately, such as notifying support for failed messages or retrying undelivered ones.
When setting up a Plivo application, what HTTP method should the Message URL use?
The Message URL in your Plivo application settings *must* use the `POST` method because Plivo sends message status callbacks as POST requests. Ensure the method is set to POST in the Plivo console to receive delivery updates.
How to troubleshoot Plivo callbacks not being received in Next.js?
Verify the Message URL in your Plivo application is correct and uses HTTPS. Check your Next.js server logs and the Plivo console logs for errors. Ensure `ngrok` is running if testing locally. Confirm no firewalls block incoming requests to your endpoint.
What should the status code of my Next.js API route response to Plivo callbacks be?
Your Next.js API route should return a 2xx status code (ideally 200 OK) to acknowledge successful receipt of the Plivo callback. Failing to return a 2xx response or timing out will cause Plivo to retry sending the callback.
What are Plivo message status error codes?
Error codes provide additional context when a message status is 'failed' or 'undelivered'. Refer to the Plivo documentation for the complete list of error codes. This helps diagnose specific delivery issues and implement targeted handling mechanisms in your Next.js application.