code examples

Sent logo
Sent TeamMay 3, 2025 / code examples / Article

Build Two-Way SMS with Plivo, Next.js & Node.js: Webhook Tutorial

Build automated two-way SMS messaging in Next.js using Plivo webhooks. Complete guide to receiving incoming messages, sending replies, signature validation, and deployment.

Build Two-Way SMS Messaging with Plivo Webhooks and Next.js

Build production-ready two-way SMS messaging using Plivo webhooks and Next.js. This comprehensive guide shows you how to implement Plivo Node.js SDK with Next.js API routes to receive incoming SMS messages and send automated replies. Learn webhook validation, signature verification, and deployment best practices for real-time SMS conversations.

This implementation enables real-time, automated SMS conversations essential for customer support bots, two-factor authentication, appointment reminders, and interactive marketing campaigns. You'll create a serverless webhook endpoint that processes incoming messages and responds instantly.

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 performance features.
  • Node.js: The JavaScript runtime environment enabling server-side logic execution. Required by Next.js.
  • Plivo: A cloud communications platform providing SMS API services. Chosen for its reliable message delivery and developer-friendly SDKs.
  • Ngrok (for local development): A tool to expose local development servers to the internet, necessary for Plivo webhooks to reach your local machine during testing.

System Architecture:

mermaid
sequenceDiagram
    participant User as User's Phone
    participant Plivo as Plivo Platform
    participant NextApp as Next.js App (API Route)
    participant Ngrok as Ngrok Tunnel (Local Dev)

    User->>+Plivo: Sends SMS to Plivo Number
    alt Local Development
        Plivo->>+Ngrok: POST Request to Ngrok URL (Webhook)
        Ngrok->>+NextApp: Forwards POST Request to localhost:3000/api/plivo/inbound-sms
        NextApp-->>-Ngrok: Generates Plivo XML Response
        Ngrok-->>-Plivo: Returns XML Response
    else Production (e.g., Vercel)
        Plivo->>+NextApp: POST Request to Deployed URL (Webhook)
        NextApp-->>-Plivo: Generates Plivo XML Response
    end
    Plivo->>-User: Sends Reply SMS based on XML

Prerequisites:

  • Node.js (v18.17.0 minimum required by Next.js; v20 or v22 LTS recommended for production as of 2024-2025) and npm or yarn installed. Node.js 18 reaches end-of-life in April 2025; use Node.js 20 (Active LTS until April 2026) or Node.js 22 (LTS until April 2027) for new projects.
  • A Plivo account (a free trial account is sufficient to start).
  • An SMS-enabled Plivo phone number.
  • ngrok installed for local development testing (or an alternative tunneling service/deployment preview method).
  • A text editor or IDE (e.g., VS Code).

Final Outcome:

By the end of this guide, you will have a functional Next.js application deployed (or ready for deployment) with an API endpoint that:

  1. Receives incoming SMS messages sent to your Plivo number.
  2. Logs the incoming message details.
  3. Sends an automated reply back to the sender using Plivo's messaging capabilities.

How to Set Up Your Next.js Project for Plivo SMS

Let's initialize a new Next.js project and install the necessary dependencies.

  1. Create Next.js App: Open your terminal and run the following command, replacing plivo-nextjs-sms with your desired project name:

    bash
    npx create-next-app@latest plivo-nextjs-sms

    Follow the prompts (using TypeScript is recommended but not required for this guide). Select defaults for other options if unsure.

  2. Navigate to Project Directory:

    bash
    cd plivo-nextjs-sms
  3. Install Plivo SDK: Add the Plivo Node.js helper library to your project:

    bash
    npm install plivo

    or

    bash
    yarn add plivo
  4. Set Up Environment Variables: Plivo requires authentication credentials (Auth ID and Auth Token). Store these securely using environment variables.

    • Create a file named .env.local in the root of your project. Never commit this file to Git.

    • Add the following lines to .env.local:

      plaintext
      # .env.local
      PLIVO_AUTH_ID=YOUR_PLIVO_AUTH_ID
      PLIVO_AUTH_TOKEN=YOUR_PLIVO_AUTH_TOKEN
      PLIVO_PHONE_NUMBER=YOUR_PLIVO_PHONE_NUMBER
    • How to find these values:

      • Log in to your Plivo Console.
      • Your PLIVO_AUTH_ID and PLIVO_AUTH_TOKEN are displayed prominently on the dashboard overview page.
      • Your PLIVO_PHONE_NUMBER is the SMS-enabled number you purchased or have available in your account (found under Phone Numbers > Your Numbers). Use the E.164 format (e.g., +14151112222).
  5. Configure .gitignore: Ensure .env.local is listed in your .gitignore file (Next.js usually adds .env*.local by default, but double-check).

    text
    # .gitignore (ensure this line exists)
    .env*.local

Project Structure Explanation:

  • pages/api/: This directory is crucial. Any file inside pages/api/ is treated as an API endpoint by Next.js. We will create our webhook handler here.
  • .env.local: Stores sensitive credentials locally, preventing them from being exposed in your codebase.
  • node_modules/: Contains installed dependencies like the Plivo SDK.
  • package.json: Lists project dependencies and scripts.

How to Implement Plivo Webhook Handler in Next.js

We'll create a Next.js API route to act as the webhook endpoint that Plivo will call when an SMS is received.

  1. Create the API Route File: Create a new file at pages/api/plivo/inbound-sms.js (or .ts if using TypeScript).

  2. Implement the Webhook Logic: Paste the following code into pages/api/plivo/inbound-sms.js:

    javascript
    // pages/api/plivo/inbound-sms.js
    const plivo = require('plivo'); // Plivo SDK uses CommonJS (requires Node.js 5.5+)
    
    export default function handler(req, res) {
      // Ensure this endpoint only accepts POST requests from Plivo
      if (req.method !== "POST") {
        console.warn(`[${new Date().toISOString()}] Received non-POST request to /api/plivo/inbound-sms`);
        res.setHeader("Allow", ["POST"]);
        return res.status(405).end(`Method ${req.method} Not Allowed`);
      }
    
      // --- Plivo Signature Validation (Highly Recommended) ---
      // See Section 7 for details and implementation notes.
      // It's crucial to validate requests *before* processing.
      // Example placeholder - implement according to Plivo docs:
      // if (!isValidPlivoRequest(req)) {
      //   return res.status(403).send('Invalid signature');
      // }
    
      // Extract message details from the Plivo request body
      // Plivo sends data as application/x-www-form-urlencoded or application/json
      // Next.js automatically parses the body based on Content-Type
      const fromNumber = req.body.From;
      const toNumber = req.body.To; // Your Plivo number
      const text = req.body.Text;
      const messageUUID = req.body.MessageUUID; // Unique ID for the incoming message
    
      console.log(
        `[${new Date().toISOString()}] Message Received - From: ${fromNumber}, To: ${toNumber}, Text: ""${text}"", UUID: ${messageUUID}`
      );
    
      // --- Basic Validation (Optional but Recommended) ---
      if (!fromNumber || !toNumber || !text || !messageUUID) {
        console.error(`[${new Date().toISOString()}] Missing required parameters in Plivo webhook request.`);
        // Respond with status 200 OK to Plivo, but log the error.
        // Avoid sending error codes back to Plivo unless absolutely necessary,
        // as it might trigger retries or alerts you don't want.
        return res.status(200).end();
      }
    
      // --- Prepare the Response using Plivo XML ---
      // Plivo expects an XML response to define actions, like sending a reply.
      const response = new plivo.Response();
    
      // Define the reply message content
      const replyText = `Thanks for your message! You said: ""${text}""`;
    
      // Add a <Message> element to the XML response
      // 'src' is the Plivo number the reply should come FROM (your number)
      // 'dst' is the number the reply should go TO (the original sender)
      const params = {
        src: toNumber, // Your Plivo number that received the message
        dst: fromNumber, // The user's number who sent the message
      };
      response.addMessage(replyText, params);
    
      // --- Send the XML Response ---
      const xmlResponse = response.toXML();
      console.log(`[${new Date().toISOString()}] Sending XML Response:\n${xmlResponse}`);
    
      // Set the Content-Type header to application/xml
      res.setHeader("Content-Type", "application/xml");
    
      // Send the XML response back to Plivo
      res.status(200).send(xmlResponse);
    }

Code Explanation:

  • Import plivo: Imports the Plivo library using CommonJS require() syntax. The Plivo Node.js SDK (version 4.x+) uses traditional CommonJS modules and is compatible with Node.js 5.5 and higher. Next.js API routes support both CommonJS and ES modules, but the Plivo SDK itself requires CommonJS.
  • Method Check: Ensures only POST requests (which Plivo uses for webhooks) are processed.
  • Extract Data: Retrieves From, To, Text, and MessageUUID from the req.body. Next.js automatically parses common body types like x-www-form-urlencoded and json.
  • Logging: Logs the received message details to the console (essential for debugging).
  • Basic Validation: Checks if essential parameters are present. It logs an error but still returns a 200 OK to Plivo to prevent unnecessary retries or error notifications from Plivo's side for simple validation failures. More complex error handling is discussed later.
  • Create plivo.Response: Initializes a Plivo response object, which helps build the required XML structure.
  • response.addMessage(): Adds a <Message> tag to the XML. This instructs Plivo to send an SMS.
    • src: Must be your Plivo number (the To number from the incoming webhook).
    • dst: Must be the sender's number (the From number from the incoming webhook).
    • replyText: The content of the SMS reply.
  • response.toXML(): Generates the final XML string.
  • Set Header: Sets the Content-Type header to application/xml, which Plivo requires.
  • Send Response: Sends the XML back to Plivo with a 200 OK status code, signaling successful receipt and providing instructions for the reply.

How to Configure Plivo Console for Next.js Webhooks

Now, configure Plivo to send incoming SMS events to your Next.js API route. Note that cloud platform UIs can change; these instructions are based on common layouts but verify against the current Plivo console.

  1. Navigate to Plivo Applications:

    • Log in to your Plivo Console.
    • Go to Messaging -> Applications -> XML (or a similar path if the UI has changed).
  2. Create a New Application:

    • Click Add New Application.
    • Application Name: Give it a descriptive name (e.g., NextJS SMS Handler).
    • Message URL: This is the crucial part. Plivo needs a publicly accessible URL to send the webhook POST request.
      • For Local Development: We'll use ngrok (covered in the next step). For now, leave a placeholder like http://temporary.local.
      • For Production: This will be your deployed application's API route URL (e.g., https://your-app-domain.com/api/plivo/inbound-sms).
    • Method: Select POST. Plivo will send data in the request body.
    • Hangup URL / Default Number App URL / Other fields: Leave these blank or default unless you have specific voice/fallback requirements.
    • Click Create Application.
  3. Link Your Plivo Number to the Application:

    • Go to Phone Numbers -> Your Numbers (or a similar path).
    • Find the SMS-enabled Plivo number you want to use for receiving messages.
    • Click on the number to edit its configuration.
    • Under Application Type, select XML Application.
    • From the Plivo Application dropdown, select the application you just created (NextJS SMS Handler).
    • Click Update Number.

Configuration Purpose:

  • The Plivo Application acts as a configuration hub, telling Plivo where to send webhook events (Message URL) and how (Method POST).
  • Linking the Phone Number to the Application directs all incoming SMS messages for that number to the specified Message URL.

How to Test Plivo Webhooks Locally with Ngrok

To test your webhook locally, you need to expose your Next.js development server (running on localhost) to the internet. ngrok is a popular tool for this, but alternatives exist like deploying to a preview branch (e.g., on Vercel/Netlify) or using other tunneling services.

  1. Start Your Next.js Development Server:

    bash
    npm run dev

    or

    bash
    yarn dev

    Your app should now be running, typically on http://localhost:3000.

  2. Start Ngrok: Open a new separate terminal window and run:

    bash
    ngrok http 3000

    (Replace 3000 if your Next.js app runs on a different port).

  3. Copy the Ngrok URL: Ngrok will display output similar to this:

    Session Status online Account Your Name (Plan: Free) Version x.x.x Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding http://xxxxxxxx.ngrok.io -> http://localhost:3000 Forwarding https://xxxxxxxx.ngrok.io -> http://localhost:3000 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00

    Copy the HTTPS forwarding URL (e.g., https://xxxxxxxx.ngrok.io). This URL now points to your local development server.

  4. Update Plivo Application Message URL:

    • Go back to your Plivo Application settings (Messaging -> Applications -> XML -> Click your app name).
    • Paste the full ngrok HTTPS URL including your API route path into the Message URL field. Example: https://xxxxxxxx.ngrok.io/api/plivo/inbound-sms
    • Ensure the Method is still POST.
    • Click Update Application.
  5. Test:

    • Send an SMS message from your personal phone to your Plivo phone number.
    • Observe:
      • Your Next.js Dev Server Terminal: You should see the console.log output from your inbound-sms.js file, showing the received message details and the XML response being generated.
      • Your Personal Phone: You should receive the automated reply SMS (""Thanks for your message! You said: ..."") if your Plivo account allows sending (see Trial Account Limitations).
      • Ngrok Terminal: You'll see HTTP requests being logged as they pass through the tunnel.
      • (Optional) Plivo Debug Logs: Check Logs -> Message Logs in the Plivo console to see details about the incoming and outgoing messages.

Error Handling and Logging Best Practices

The current implementation has basic logging. Production systems require more robust strategies.

  • Error Handling Strategy:

    • Use try...catch blocks to handle unexpected errors during processing.
    • Critical Errors: For errors preventing XML generation (e.g., Plivo SDK issues, critical logic failure), log detailed error information (stack trace, context). You might consider returning a 500 Internal Server Error to Plivo, but be aware this can trigger retries and alerts. Often, logging the error and returning 200 OK is preferred to acknowledge receipt while handling the failure internally.
    • Validation Errors: As shown, log specific validation failures (missing parameters) but typically return 200 OK to Plivo.
    • External Service Failures: If your webhook needs to call other APIs, implement specific error handling for those calls (timeouts, retries with backoff if appropriate).
  • Logging:

    • console.log is suitable for development and simple cases.
    • For production, use a dedicated logging library (like pino, winston) integrated with a logging service (e.g., Datadog, Logtail, Sentry). This enables structured logging, different log levels (debug, info, warn, error), and easier analysis.
    • Log key information: Timestamps, Message UUID, From/To numbers, status (success/failure), error messages, and stack traces for errors.
  • Plivo Error Handling: If your webhook endpoint returns non-2xx status codes repeatedly or times out, Plivo will log errors in its console (Logs -> Debug Logs). Configure Plivo alerts if needed. Plivo may retry sending the webhook request based on the error type.

Example (Enhanced Logging Placeholder):

javascript
// pages/api/plivo/inbound-sms.js (Illustrative - requires logger setup)
// import logger from '../../utils/logger'; // Assume a logger utility exists

// ... inside handler ...
try {
  // ... existing logic ...

  // logger.info({ // Example structured log
  //   message: 'Successfully processed inbound SMS and generated reply.',
  //   plivoMessageUUID: messageUUID,
  //   fromNumber: fromNumber,
  //   toNumber: toNumber,
  // });

  res.setHeader("Content-Type", "application/xml");
  res.status(200).send(xmlResponse);

} catch (error) {
  // logger.error({ // Example structured error log
  //   message: 'Failed to process inbound SMS.',
  //   plivoMessageUUID: messageUUID,
  //   fromNumber: fromNumber,
  //   error: error.message,
  //   stack: error.stack,
  // });

  // Decide response: Maybe still return 200 OK to Plivo to avoid retries
  // but ensure the error is captured by your monitoring.
  res.status(200).end(); // Acknowledge receipt even on internal failure
}

Database Integration for SMS Message Storage (Optional Extension)

This simple example doesn't store messages. For real applications, you'll likely want to save incoming/outgoing messages.

  • Schema: A simple messages table could include:

    • id (Primary Key, e.g., UUID or auto-increment)
    • plivo_message_uuid (VARCHAR, unique - useful for matching Plivo logs)
    • direction (ENUM('inbound', 'outbound'))
    • from_number (VARCHAR)
    • to_number (VARCHAR)
    • body (TEXT)
    • status (VARCHAR - e.g., 'received', 'sent', 'delivered', 'failed') - Note: Requires setting up Plivo Delivery Reports.
    • timestamp (TIMESTAMP WITH TIME ZONE)
  • Implementation:

    • Choose a database (PostgreSQL, MySQL, MongoDB).
    • Use an ORM like Prisma or TypeORM, or a query builder like Knex.js.
    • Add database interaction logic within your API route after receiving the message and before (or after) sending the XML response. Be mindful of latency – database operations add time to your webhook response.

How to Secure Plivo Webhooks with Signature Validation

Protecting your webhook and credentials is vital.

  • Environment Variables: Never hardcode PLIVO_AUTH_ID or PLIVO_AUTH_TOKEN. Use .env.local and configure environment variables in your deployment environment.

  • Webhook Validation (Highly Recommended): Plivo supports request validation using your Auth Token to sign requests. This ensures incoming requests genuinely originate from Plivo.

    • Enable Signature Validation in your Plivo Application settings.
    • Plivo's V3 signature validation uses X-Plivo-Signature-V3 and X-Plivo-Signature-V3-Nonce headers. The algorithm concatenates the full URL with the nonce, computes HMAC-SHA256 using your PLIVO_AUTH_TOKEN as the key, and base64-encodes the result.
    • Important: As of July 2024, some users reported signature validation issues that required backend fixes from Plivo support. If validation fails unexpectedly, verify your implementation first, then contact Plivo support if needed.
    • The Plivo Node.js SDK may not include a built-in validateV3Signature helper function in all versions. You may need to implement validation using Node.js's built-in crypto module.
    • Refer to the Plivo documentation on Signature Validation for the most current implementation details. Crucially, construct the full URL correctly – if your application runs behind a proxy or load balancer, using req.headers.host alone may not be sufficient. Consider using environment variables for the base URL in production.
    • Use timing-safe string comparison to prevent timing attacks when comparing signatures (e.g., Node.js crypto.timingSafeEqual()).
    javascript
    // Example Snippet Structure for Validation (Place near the start of handler)
    // const crypto = require('crypto');
    
    // const signature = req.headers['x-plivo-signature-v3'];
    // const nonce = req.headers['x-plivo-signature-v3-nonce'];
    // const authToken = process.env.PLIVO_AUTH_TOKEN;
    
    // --- Construct the URL EXACTLY as recommended by Plivo documentation ---
    // This is critical and may vary based on deployment (proxies etc.)
    // Option 1: Use environment variable for production
    // const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || `https://${req.headers.host}`;
    // const url = `${baseUrl}${req.url}`;
    
    // Option 2: For development/testing only
    // const url = `https://${req.headers.host}${req.url}`; // May be unreliable behind proxies
    
    // // Compute expected signature
    // const data = url + nonce;
    // const expectedSignature = crypto
    //   .createHmac('sha256', authToken)
    //   .update(data)
    //   .digest('base64');
    
    // try {
    //   // Use timing-safe comparison
    //   const signatureBuffer = Buffer.from(signature);
    //   const expectedBuffer = Buffer.from(expectedSignature);
    //
    //   if (signatureBuffer.length !== expectedBuffer.length ||
    //       !crypto.timingSafeEqual(signatureBuffer, expectedBuffer)) {
    //     console.warn(`[${new Date().toISOString()}] Invalid Plivo signature received.`);
    //     return res.status(403).send('Invalid signature');
    //   }
    //   console.log(`[${new Date().toISOString()}] Plivo signature validated successfully.`);
    // } catch (e) {
    //    console.error(`[${new Date().toISOString()}] Error during Plivo signature validation: ${e.message}`);
    //    return res.status(403).send('Signature validation error');
    // }
    
    // --- Proceed with request processing only after successful validation ---
  • Input Sanitization: Although we only reply with the received text here, if you use the input text for database storage, API calls, or dynamic responses, sanitize it to prevent injection attacks (e.g., use libraries like DOMPurify if rendering as HTML, or proper SQL parameterization via your ORM).

  • Rate Limiting: If the endpoint could be abused, implement rate limiting (e.g., using nextjs-rate-limiter or Vercel's built-in features) based on source IP or other identifiers (though source IP might be Plivo's IPs).

  • HTTPS: Always use HTTPS for your Message URL (ngrok provides this locally, and platforms like Vercel provide it automatically).


Handling Special SMS Cases and Edge Scenarios

  • STOP/HELP Keywords: Plivo (like most providers) typically handles standard opt-out keywords (STOP, UNSUBSCRIBE, CANCEL) automatically at the platform level for Toll-Free and Long Code numbers in supported regions (like the US/Canada). They manage the opt-out list. You generally don't need to implement specific logic for these basic keywords unless you have custom opt-out requirements or operate in regions with different regulations. You should still implement HELP keyword responses providing users with instructions.
  • Character Limits & Encoding: SMS messages have character limits (160 GSM-7 characters, fewer for UCS-2). Long messages are automatically segmented by Plivo but billed as multiple messages. Be mindful of reply length. Non-GSM characters (like some emojis) use UCS-2 encoding, reducing the limit per segment significantly.
  • Concatenated Messages: Plivo handles receiving long messages split into segments; you receive the full text in the webhook.
  • International Formatting: Ensure you handle phone numbers in E.164 format (+countrycode...).

Performance Optimization for Plivo Webhooks

For this simple webhook, performance is usually not a major concern unless calling slow external services.

  • Keep Webhooks Fast: Aim for webhook responses under 2-3 seconds. Plivo may time out if your endpoint is too slow. Offload long-running tasks (complex processing, slow API calls, database writes that aren't critical for the immediate reply) to background jobs (e.g., using BullMQ, Vercel Functions with longer timeouts, or external queueing systems).
  • Caching: If replies involve fetching data that doesn't change often, cache it (e.g., using Redis, Vercel Data Cache, or in-memory cache for small datasets).
  • Next.js API Route Performance: API routes are serverless functions; cold starts can introduce latency. Keep function size minimal.

Monitoring and Analytics for SMS Applications

  • Health Checks: Create a separate, simple API route (e.g., /api/health) that returns 200 OK for basic uptime monitoring.
  • Performance Metrics: Use your deployment platform's monitoring (e.g., Vercel Analytics) or integrated observability platforms (Datadog, New Relic) to track function duration, invocation counts, and error rates.
  • Error Tracking: Integrate services like Sentry or Bugsnag to capture and alert on runtime errors in your API route.
  • Logging Platform: As mentioned, centralize logs for easier searching and analysis. Create dashboards showing message volume (inbound/outbound inferred from logs), error rates, and webhook latency. Set up alerts for high error rates or sustained high latency.

Troubleshooting Common Plivo Webhook Issues

  • Ngrok Issues:
    • Ensure ngrok is running and targeting the correct localhost port.
    • Check that the ngrok URL in the Plivo Message URL setting is correct (HTTPS, includes /api/plivo/inbound-sms).
    • Free ngrok URLs change each time you restart it; update Plivo accordingly. Consider a paid ngrok plan or alternative solutions (like deployment previews) for stable URLs during development.
  • Plivo Configuration Errors:
    • Double-check the Plivo number is linked to the correct XML Application.
    • Verify the Message URL method is set to POST.
    • Confirm PLIVO_AUTH_ID and PLIVO_AUTH_TOKEN are correct in .env.local (and in production environment variables).
  • Code Errors:
    • Check the Next.js development server console (or production function logs) for errors.
    • Ensure correct extraction of From, To, Text from req.body. Case sensitivity matters (From vs from).
    • Verify the XML generated is valid Plivo XML (check console logs).
    • Check Plivo signature validation logic carefully if implemented.
  • Trial Account Limitations: Plivo trial accounts have specific restrictions that affect testing:
    • Incoming Webhooks Work Normally: Trial accounts can receive incoming SMS and trigger webhooks, allowing you to fully test your endpoint logic.
    • Outbound SMS Restrictions: Sending outgoing SMS (including automated replies) is limited to verified/sandboxed phone numbers only. You must verify each destination number in your Plivo console before the trial account can send messages to it. This applies to replies sent via XML responses or API calls.
    • No Alphanumeric Sender IDs: Trial accounts cannot use alphanumeric sender IDs.
    • Default Rate Limits: Trial accounts have a default outbound limit of 5 messages per second (MPS) for SMS and 0.25 MPS for MMS.
    • Account Never Expires: Plivo trial accounts do not expire. You can upgrade at any time to remove restrictions and send messages to any valid phone number.
    • One Test Number Provided: Trial signup includes one phone number for testing purposes.
    • To send messages to arbitrary numbers (not just verified ones), you need to upgrade your Plivo account. Check the current trial limitations at Plivo Support.
  • STOP Keyword Handling: If a user texts STOP, Plivo will likely block future messages from your Plivo number to them at the platform level. You usually won't receive the STOP message via webhook, as Plivo handles it. If messages suddenly stop delivering to a specific user, they may have opted out.
  • Firewall Issues: Ensure your deployment environment allows incoming traffic from Plivo's IP ranges if you have strict firewall rules (though serverless platforms like Vercel typically handle this).
  • XML Response Issues: Ensure Content-Type is exactly application/xml. Malformed XML will cause Plivo to fail processing the reply.

How to Deploy Next.js Plivo App to Production

Deploying a Next.js application is straightforward, especially with platforms like Vercel.

Deploying to Vercel (Example):

  1. Push to Git: Ensure your code is committed and pushed to a Git repository (GitHub, GitLab, Bitbucket). Remember your .gitignore prevents .env.local from being pushed.
  2. Import Project in Vercel:
    • Log in to your Vercel account.
    • Click ""Add New..."" -> ""Project"".
    • Import the Git repository containing your project.
  3. Configure Project:
    • Vercel usually detects Next.js automatically.
    • Crucially: Configure Environment Variables. Go to the project's Settings -> Environment Variables. Add PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN, and PLIVO_PHONE_NUMBER with their production values. Ensure they are available to all environments (Production, Preview, Development).
  4. Deploy: Click ""Deploy"". Vercel will build and deploy your application.
  5. Get Production URL: Once deployed, Vercel provides a production URL (e.g., your-app-name.vercel.app).
  6. Update Plivo Message URL: Go back to your Plivo Application settings (Messaging -> Applications -> XML) and update the Message URL to your production API route URL (e.g., https://your-app-name.vercel.app/api/plivo/inbound-sms). Save the changes.

CI/CD:

  • Platforms like Vercel automatically set up CI/CD. Pushing to your main branch triggers a production deployment; pushes to other branches can create preview deployments.

Frequently Asked Questions (FAQ)

How do I validate Plivo webhook signatures in Next.js?

Plivo's V3 signature validation uses the X-Plivo-Signature-V3 and X-Plivo-Signature-V3-Nonce headers. Concatenate your full webhook URL with the nonce value, compute HMAC-SHA256 using your PLIVO_AUTH_TOKEN as the secret key, and base64-encode the result. Compare this with the signature header using Node.js's crypto.timingSafeEqual() for timing-safe comparison. See Section 7 for complete implementation code.

What Node.js version do I need for Plivo and Next.js?

Next.js requires Node.js v18.17.0 minimum. For production deployments in 2024-2025, use Node.js 20 (Active LTS until April 2026) or Node.js 22 (LTS until April 2027). The Plivo Node.js SDK (version 4.x+) uses CommonJS modules and is compatible with Node.js 5.5 and higher. Node.js 18 reaches end-of-life in April 2025.

Can I send SMS replies with a Plivo trial account?

Yes, but with restrictions. Plivo trial accounts can receive incoming SMS and trigger webhooks normally, but outbound SMS (including automated replies) can only be sent to verified/sandboxed phone numbers. You must verify each destination number in your Plivo console before sending messages. Trial accounts also have a default 5 messages per second (MPS) rate limit and cannot use alphanumeric sender IDs. Trial accounts never expire, and you can upgrade anytime to remove these restrictions.

How fast must my Plivo webhook respond?

Aim for webhook responses under 2-3 seconds. Plivo may time out if your endpoint takes too long. Keep webhook logic fast by offloading long-running tasks (database writes, external API calls, complex processing) to background jobs using queues like BullMQ or serverless functions with longer timeouts. Return the XML response to Plivo immediately.

What XML format does Plivo expect for SMS replies?

Plivo expects an XML response with Content-Type: application/xml. Use the Plivo Node.js SDK's plivo.Response() object and response.addMessage() method to generate valid XML. The src parameter must be your Plivo number (the To number from the incoming webhook), and dst must be the sender's number (the From number). Return HTTP status 200 with the XML body.

Do I need to handle STOP keywords in my webhook?

No, Plivo handles standard opt-out keywords (STOP, UNSUBSCRIBE, CANCEL) automatically at the platform level for Toll-Free and Long Code numbers in supported regions like the US/Canada. Plivo manages the opt-out list, and you typically won't receive STOP messages via webhook. However, you should implement HELP keyword responses to provide users with instructions.

How do I test Plivo webhooks on localhost?

Use ngrok to expose your local development server to the internet. Run ngrok http 3000 (or your Next.js port), copy the HTTPS forwarding URL, and append your API route path (e.g., https://xxxxxxxx.ngrok.io/api/plivo/inbound-sms). Paste this full URL into your Plivo Application's Message URL field in the Plivo console. Free ngrok URLs change each restart, so update Plivo after restarting ngrok.

Can I use ES module imports with Plivo Node.js SDK?

No, the Plivo Node.js SDK (version 4.x+) uses traditional CommonJS modules. You must use const plivo = require('plivo') syntax, not ES module import statements. Next.js API routes support both CommonJS and ES modules, but the Plivo SDK itself requires CommonJS require() syntax.


Testing and Verification Checklist

Manual Verification:

  1. Local: Follow the ngrok testing steps outlined in Section 4.
  2. Production: After deploying and updating the Plivo Message URL:
    • Send an SMS from your personal phone (or a verified Sandbox Number if using a trial account) to the Plivo number.
    • Verify you receive the automated SMS reply (subject to trial account limitations).
    • Check Vercel function logs (Realtime Logs section in the dashboard) for your console.log output and any errors.
    • Check Plivo Message Logs in the Plivo console to confirm message statuses (incoming received, outgoing sent/delivered/failed).

Automated Testing (Conceptual):

  • Unit Tests (Jest): Mock the req and res objects, and the plivo SDK. Test the API route handler function directly:
    • Verify it correctly extracts parameters from a mock req.body.
    • Verify it calls plivo.Response and addMessage with the correct arguments.
    • Verify it sets the correct Content-Type header and status code on the mock res.
    • Test edge cases (missing parameters, different request methods, signature validation failures).
  • Integration Tests: Use a testing library (like supertest) to make actual HTTP requests to your running development server's API endpoint (or potentially a dedicated test deployment). Mock Plivo's request signature if needed. Verify the HTTP response status, headers, and XML body.

Verification Checklist:

  • Project initializes correctly (create-next-app).
  • plivo SDK is installed.
  • .env.local created and populated (NOT committed).
  • API route (/api/plivo/inbound-sms) exists and contains the correct logic.
  • Plivo Auth ID/Token/Number environment variables are set correctly (local and production).
  • Plivo Application created with POST method and correct Message URL (ngrok/preview for local, deployed URL for production).
  • Plivo Number is linked to the correct Plivo Application.
  • Local Test: ngrok running (or alternative), SMS sent to Plivo number results in console logs AND potentially a reply SMS (check trial limits).
  • Production Test: App deployed, SMS sent to Plivo number results in production logs AND potentially a reply SMS (check trial limits).
  • Plivo signature validation implemented and tested (Recommended).
  • Error handling and logging are adequate for the application's needs.

This guide provides a solid foundation for building two-way SMS interactions using Plivo and Next.js. You can expand upon this by adding database integration, more sophisticated response logic, state management for conversations, and robust error handling and monitoring suitable for your specific production needs. Remember to consult the official Plivo API documentation for further details and advanced features.

Frequently Asked Questions

How to set up two-way SMS with Plivo and Next.js?

Start by creating a Next.js project, installing the Plivo Node.js SDK, and setting up environment variables for your Plivo Auth ID, Auth Token, and Plivo phone number in a `.env.local` file. Then, create an API route in `pages/api` to handle incoming webhooks.

What is the purpose of ngrok in Plivo Next.js integration?

Ngrok creates a temporary public URL that tunnels requests to your local development server. This is essential for Plivo to send webhooks to your Next.js application during development, as Plivo needs a publicly accessible URL.

Why does Plivo need a webhook URL for SMS?

Plivo uses webhooks to notify your application when events like receiving an SMS message occur. Your webhook URL is the endpoint Plivo sends an HTTP POST request to with message details. Your application then processes this request and generates an appropriate response.

When should I validate the Plivo signature in my Next.js webhook?

Signature validation should be performed at the very beginning of your webhook handler logic, before you process any of the request data. This prevents malicious actors from sending fake requests to your webhook endpoint and helps ensure security.

Can I use a trial Plivo account to build a two-way SMS app?

Yes, a free Plivo trial account is sufficient for initial development and testing. Keep in mind that trial accounts often have limitations on sending outgoing SMS messages to numbers that aren't specifically verified as Sandbox Numbers by Plivo.

How to handle incoming SMS messages in a Next.js app with Plivo?

Create an API route in `pages/api` (e.g., `/api/plivo/inbound-sms`) that serves as your webhook endpoint. Inside the handler, extract the `From`, `To`, and `Text` parameters from `req.body`. Then use the Plivo SDK to generate an XML response with an SMS reply and return it to Plivo.

What is a Plivo application, and why do I need one for Next.js SMS?

A Plivo application acts as a configuration hub for managing webhook URLs, methods (e.g. POST), and other settings for your Plivo integration. You link a Plivo phone number to your app to route incoming messages and define how Plivo interacts with your application.

How to respond to an incoming SMS message with Plivo in my Next.js API route?

Use the `plivo.Response` object from the Plivo SDK to create an XML response. Add a `<Message>` element using `response.addMessage()`, specifying your Plivo number as the `src` (sender) and the original sender's number as the `dst` (destination). Set the message body (reply text), generate the XML, and send it with a 200 OK status code.

What environment variables are essential for two-way SMS with Plivo and Next.js?

You need `PLIVO_AUTH_ID`, `PLIVO_AUTH_TOKEN`, and `PLIVO_PHONE_NUMBER`. Store these securely in a `.env.local` file for development and configure equivalent environment variables in your production deployment environment. Never commit `.env.local` to version control.

How to test my Plivo SMS integration locally with Next.js?

Use `ngrok` to expose your local development server. Start your Next.js dev server, then in a separate terminal, run `ngrok http 3000` (or your app's port). Copy the HTTPS `ngrok` URL and paste it as the Message URL in your Plivo application settings, including the `/api/plivo/inbound-sms` path. Send a test SMS to your Plivo number.

Why am I not receiving SMS replies from my Plivo Next.js application?

Several factors could be at play: trial account limitations, incorrect Ngrok URL in Plivo, errors in the API route handler, or issues with Plivo's configuration. Check your Next.js server logs, Ngrok logs, Plivo message logs, and verify environment variables.

How to log Plivo inbound SMS messages and errors effectively?

For development, `console.log` is sufficient. In production, use a dedicated logging library like `pino` or `winston` integrated with a logging service (e.g., Datadog, Sentry). Log timestamps, Message UUIDs, phone numbers, status codes, and error details.

What security considerations are important when implementing two-way SMS with Plivo?

Prioritize Plivo signature validation to verify request authenticity, protect environment variables containing your Plivo credentials, and sanitize any user input (SMS text) used in your application logic or database to avoid potential injection vulnerabilities.

When developing locally with ngrok and Plivo, the URL keeps changing. How can I avoid this?

Free ngrok URLs change on restart. Use a paid ngrok plan for a static URL, or consider preview deployments with platforms like Vercel or Netlify, as they offer more stable URLs during development.

How to handle the STOP keyword and opt-outs in Plivo two-way SMS?

Plivo often handles basic opt-out keywords (STOP, UNSUBSCRIBE) automatically. Focus on adding clear instructions for users via a HELP keyword and ensure your application logic manages custom opt-out flows or requirements if necessary, according to the country-specific regulation.