code examples

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

Vonage WhatsApp API Node.js Integration: Complete Express.js Tutorial [2025]

Learn how to integrate WhatsApp and SMS messaging in Node.js with Vonage Messages API. Step-by-step Express.js tutorial with webhook handlers, JWT authentication, and production deployment guides for building scalable messaging applications.

This comprehensive guide teaches you how to build a production-ready WhatsApp and SMS integration using Node.js, Express.js, and the Vonage Messages API. Whether you're implementing customer notifications, two-factor authentication, or automated messaging workflows, this tutorial covers everything you need.

You'll learn how to send and receive WhatsApp messages with Node.js, implement secure webhook integration with Express.js, handle JWT authentication for API security, manage delivery status callbacks, and deploy your messaging application to production. Perfect for developers building customer communication platforms, chatbots, notification systems, or multi-channel messaging services.

Project Overview and Goals

What You'll Build:

You'll build an Express.js web application that:

  1. Sends outbound SMS messages via API calls or internal functions
  2. Sends outbound WhatsApp messages using the Vonage WhatsApp Sandbox for testing
  3. Receives inbound SMS messages via webhooks configured in the Vonage API Dashboard
  4. Receives inbound WhatsApp messages via the same webhook, handling replies
  5. Receives message status updates via a separate webhook for delivery confirmations

Problem You'll Solve:

This application provides a foundational backend service for integrating programmable SMS and WhatsApp messaging into larger systems, enabling customer communication, notifications, two-factor authentication triggers, or basic chatbots.

Technologies Used:

  • Node.js: A JavaScript runtime environment for building server-side applications. Chosen for its asynchronous nature, large ecosystem (npm), and popularity.

  • Express.js: A minimal and flexible Node.js web application framework. Provides robust features for web and mobile applications, ideal for creating webhook handlers and potentially a simple API.

  • Vonage Messages API: A unified API for sending and receiving messages across multiple channels (SMS, MMS, WhatsApp, Facebook Messenger, Viber). Chosen for its multi-channel support and developer-friendly SDKs.

  • Vonage Node.js SDK (@vonage/server-sdk, @vonage/messages, @vonage/jwt): Official libraries simplifying interaction with the Vonage APIs and handling security features like JWT verification.

  • dotenv: A zero-dependency module that loads environment variables from a .env file into process.env. Essential for managing credentials securely.

  • ngrok: A tool to expose local servers to the internet. Crucial for testing Vonage webhooks during development.

    Note: For production deployments, ngrok is replaced by a publicly accessible server with a static IP or domain name.

System Architecture:

text
+-------------+       +------------------------+       +----------------+       +-----------------+
| Your Client | ----> | Node.js/Express App  | ----> | Vonage API     | ----> | SMS/WhatsApp    |
| (Optional)  |       | (localhost or Server)  | <---- | (Messages API) | <---- | User's Phone    |
+-------------+       +---------+--------------+       +----------------+       +-----------------+
                          |       ^
                          |       | (Webhooks: Inbound Message / Status Update)
                          v       |
                      +---------+--------------+
                      | ngrok (Development)    |
                      +------------------------+

Prerequisites:

  • Node.js: Version 18 or higher installed. (Download Node.js)
  • npm: Node Package Manager (comes with Node.js).
  • Vonage API Account: Free signup available. (Sign up for Vonage)
  • Vonage API Key and Secret: Found on your Vonage API Dashboard homepage.
  • A Vonage Phone Number: Capable of sending SMS. You can purchase one in the Vonage API Dashboard under "Numbers".
  • ngrok: Installed and authenticated with a free account. (Download ngrok)
  • (Optional) WhatsApp Account: Needed on your phone to test WhatsApp integration with the Sandbox.

1. Set Up Your Node.js WhatsApp Project

Let's create the project directory, initialize Node.js, and install the necessary dependencies.

  1. Create Project Directory:

    Open your terminal or command prompt and create a new directory for your project, then navigate into it.

    bash
    mkdir vonage-messaging-app
    cd vonage-messaging-app
  2. Initialize Node.js Project:

    Initialize a new Node.js project using npm. The -y flag accepts the default settings.

    bash
    npm init -y

    This creates a package.json file, which tracks your project's dependencies and other metadata.

  3. Install Dependencies:

    Install Express, the Vonage SDK components, and dotenv.

    bash
    npm install express @vonage/server-sdk @vonage/messages @vonage/jwt dotenv
    • express: Web framework.
    • @vonage/server-sdk: Core Vonage SDK.
    • @vonage/messages: Specific SDK for the Messages API.
    • @vonage/jwt: For verifying webhook signatures.
    • dotenv: For loading environment variables.
  4. Project Structure:

    Your basic project structure should look like this:

    text
    vonage-messaging-app/
    ├── node_modules/
    ├── package.json
    ├── package-lock.json
    ├── .env                # Will be created in step 6
    ├── .gitignore          # Will be created in step 7
    ├── server.js           # Will be created in step 8
    └── private.key         # Will be downloaded from Vonage
  5. Configure Vonage Account Settings:

    Before writing code, ensure your Vonage account is set up correctly:

    • Set Default SMS API:

      • Go to your Vonage API Dashboard.
      • Navigate to Settings in the left-hand menu.
      • Under API keys > SMS settings, ensure "Which API do you want to use by default for sending SMS?" is set to Messages API.
      • Click Save changes. This ensures the correct webhook format is used.

      Why? Vonage has older SMS APIs. Setting Messages as default ensures consistency.

  6. Create .env File for Credentials:

    Create a file named .env in the root of your project directory. This file will store your sensitive credentials and configuration. Never commit this file to version control.

    dotenv
    # .env
    
    # Vonage API Credentials (Find on Vonage API Dashboard homepage)
    VONAGE_API_KEY=YOUR_API_KEY
    VONAGE_API_SECRET=YOUR_API_SECRET
    
    # Vonage Application Credentials (Generated in next step)
    VONAGE_APPLICATION_ID=YOUR_APPLICATION_ID
    VONAGE_PRIVATE_KEY=./private.key # Path to your downloaded private key file
    
    # Vonage Numbers (Find/Buy in Vonage API Dashboard -> Numbers)
    VONAGE_SMS_NUMBER=YOUR_VONAGE_SMS_NUMBER_IN_E164_FORMAT # E.164 format (e.g., 14155551212)
    VONAGE_WHATSAPP_NUMBER=YOUR_VONAGE_WHATSAPP_SANDBOX_NUMBER # e.g., 14157386102 (Sandbox Number)
    
    # Webhook Security (Find in Vonage API Dashboard -> Settings -> API secrets)
    VONAGE_API_SIGNATURE_SECRET=YOUR_SIGNATURE_SECRET
    
    # Your Phone Number for Testing (E.164 format)
    MY_PHONE_NUMBER=YOUR_PERSONAL_PHONE_NUMBER_IN_E164_FORMAT # E.164 format (e.g., 12015551212)
    
    # Server Configuration
    PORT=8000
    • Replace placeholders like YOUR_API_KEY with your actual values (except VONAGE_APPLICATION_ID and VONAGE_PRIVATE_KEY, which we'll get next).
    • Number Formatting: Enter phone numbers in E.164 format (country code + number, without leading + or 00).
  7. Create .gitignore File:

    To prevent accidentally committing sensitive information and unnecessary files, create a .gitignore file in the root directory:

    text
    # .gitignore
    
    node_modules
    .env
    private.key
    *.log
  8. Initialize server.js:

    Create a file named server.js in the root directory. This will contain our application logic. Add the initial setup code:

    javascript
    // server.js
    require('dotenv').config(); // Load environment variables from .env file
    
    const express = require('express');
    const { Vonage } = require('@vonage/server-sdk');
    const { WhatsAppText } = require('@vonage/messages');
    const { verifySignature } = require('@vonage/jwt');
    
    const app = express();
    app.use(express.json()); // Middleware to parse JSON request bodies
    app.use(express.urlencoded({ extended: true })); // Middleware to parse URL-encoded request bodies
    
    const PORT = process.env.PORT || 8000;
    
    // --- Vonage Client Initialization (Add later) ---
    
    // --- Webhook Endpoints (Add later) ---
    
    // --- Send Functions (Add later) ---
    
    // --- Start Server ---
    app.listen(PORT, () => {
      console.log(`Server is listening on port ${PORT}`);
      console.log(`Ensure ngrok is running and pointing to http://localhost:${PORT}`);
    });
    
    // Basic check endpoint
    app.get('/', (req, res) => {
      res.status(200).send('Vonage Messaging App is running!');
    });

2. Configure Vonage WhatsApp Sandbox and Application

Now, let's create a Vonage Application and set up the WhatsApp Sandbox. This application acts as a container for your settings and authentication.

  1. Create a Vonage Application:

    • Navigate to Applications in your Vonage API Dashboard.

    • Click + Create a new application.

    • Enter an Application name (e.g., "Node Express Messaging App").

    • Click Generate public and private key. This will automatically download a file named private.key. Save this file in the root of your project directory. The public key is stored securely by Vonage.

      Why? This key pair is used for authenticating API requests securely instead of just using the API secret for certain operations.

    • Important: Copy the Application ID generated on this page and paste it into your .env file for the VONAGE_APPLICATION_ID variable.

    • Scroll down to Capabilities.

    • Toggle Messages ON.

      • You'll see fields for Inbound URL and Status URL. We'll fill these in the next step after starting ngrok. Leave them blank for now or enter temporary placeholders like http://example.com/inbound.
    • Scroll down to Virtual numbers.

    • Click Link next to the Vonage phone number you purchased (the one you put in .env as VONAGE_SMS_NUMBER). This connects incoming messages on that number to this application's webhooks.

    • Click Generate new application.

  2. Start ngrok:

    Open a new terminal window/tab (keep the one for running the server separate). Run ngrok to expose the port your Express app will listen on (defined in .env, default is 8000).

    bash
    ngrok http 8000

    ngrok will display output similar to this:

    text
    Session Status                online
    Account                       Your Name (Plan: Free)
    Version                       x.y.z
    Region                        United States (us-cal-1)
    Latency                       --
    Web Interface                 http://127.0.0.1:4040
    Forwarding                    https://<YOUR_UNIQUE_ID>.ngrok-free.app -> http://localhost:8000
    
    Connections                   ttl     opn     rt1     rt5     p50     p90
                                  0       0       0.00    0.00    0.00    0.00
    • Copy the https://<YOUR_UNIQUE_ID>.ngrok-free.app Forwarding URL. This is your public URL for testing.
  3. Configure Webhook URLs in Vonage Application:

    • Go back to your Vonage Application settings (Vonage API Dashboard -> Click your application name).

    • Click Edit.

    • Under Capabilities > Messages:

      • Set Inbound URL to: YOUR_NGROK_FORWARDING_URL/webhooks/inbound (e.g., https://<YOUR_UNIQUE_ID>.ngrok-free.app/webhooks/inbound)
      • Set Status URL to: YOUR_NGROK_FORWARDING_URL/webhooks/status (e.g., https://<YOUR_UNIQUE_ID>.ngrok-free.app/webhooks/status)

      Why? Inbound URL receives incoming messages. Status URL receives delivery receipts/status updates for outbound messages.

    • Click Save changes.

  4. Set Up Vonage WhatsApp Sandbox:

    The Sandbox allows testing WhatsApp messaging without needing a full WhatsApp Business Account initially.

    • Navigate to Messages API Sandbox under Build & Manage in the Vonage API Dashboard.

    • Allowlist Your Number: Follow the instructions to send the specific WhatsApp message shown on the page (e.g., "Allowlist <keyword>") from your personal WhatsApp number to the Vonage Sandbox number provided (usually +14157386102). This confirms you consent to receive test messages. The Sandbox number is what you should have in your .env file as VONAGE_WHATSAPP_NUMBER.

    • Configure Sandbox Webhooks: In the Sandbox settings page, find the Webhooks section.

      • Set Inbound message URL to: YOUR_NGROK_FORWARDING_URL/webhooks/inbound
      • Set Message status URL to: YOUR_NGROK_FORWARDING_URL/webhooks/status

      Why? These tell the Sandbox where to send incoming WhatsApp messages and status updates, separate from the main application settings but typically pointing to the same place in simple apps.

    • Click Save webhooks.

  5. Get Signature Secret:

    • Go to Settings in the Vonage API Dashboard.

    • Under API secrets, find the Signing secret associated with your main API key. Click the eye icon to reveal it.

    • Copy this secret and paste it into your .env file for VONAGE_API_SIGNATURE_SECRET.

      Why? This secret is used to create a signature (JWT) for incoming webhooks, allowing your application to verify they genuinely came from Vonage.

3. Send SMS and WhatsApp Messages with Vonage Messages API

Now, let's add the code to initialize the Vonage client and functions to send SMS and WhatsApp messages.

  1. Initialize Vonage Client:

    Add the following code to your server.js file, below the initial require and app setup:

    javascript
    // server.js (Add this section)
    
    // --- Vonage Client Initialization ---
    const vonage = new Vonage(
      {
        apiKey: process.env.VONAGE_API_KEY,
        apiSecret: process.env.VONAGE_API_SECRET,
        applicationId: process.env.VONAGE_APPLICATION_ID,
        privateKey: process.env.VONAGE_PRIVATE_KEY, // Path from .env
      },
      {
        // Use sandbox host for WhatsApp testing if required by SDK version or specific needs
        // Check Vonage Node SDK documentation for latest recommendations
        // Often, the SDK handles the correct endpoint automatically based on message type
        // apiHost: 'https://messages-sandbox.nexmo.com' // Usually only needed for older SDKs or specific sandbox interactions
      }
    );
    • This uses the credentials loaded from your .env file to create an authenticated Vonage client instance.
    • The privateKey path is read directly from the environment variable.
    • The apiHost override is generally not needed for sending messages with recent SDK versions but is included for awareness if you encounter issues specifically with the sandbox.
  2. Implement sendSms Function:

    Add a function to handle sending SMS messages.

    javascript
    // server.js (Add this section)
    
    // --- Send Functions ---
    
    /**
     * Sends an SMS message using the Vonage Messages API.
     * @param {string} to - The recipient phone number (E.164 format, e.g., 12015551212).
     * @param {string} text - The message content.
     */
    async function sendSms(to, text) {
      console.log(`Attempting to send SMS to ${to}...`);
      try {
        const resp = await vonage.messages.send({
          message_type: 'text',
          text: text,
          to: to,
          from: process.env.VONAGE_SMS_NUMBER, // Your Vonage SMS number from .env
          channel: 'sms',
        });
        console.log(`SMS sent successfully to ${to}. Message UUID: ${resp.messageUuid}`);
        return resp; // Contains message_uuid
      } catch (err) {
        console.error(`Error sending SMS to ${to}:`, err?.response?.data || err.message);
        // Rethrow or handle specific errors as needed
        throw err;
      }
    }
    • Uses vonage.messages.send.
    • Specifies channel: 'sms' and message_type: 'text'.
    • Uses the VONAGE_SMS_NUMBER from .env as the sender ID.
    • Includes basic logging and error handling.
  3. Implement sendWhatsApp Function:

    Add a function to handle sending WhatsApp messages.

    javascript
    // server.js (Add this section)
    
    /**
     * Sends a WhatsApp message using the Vonage Messages API.
     * Uses the WhatsAppText class for simplicity.
     * @param {string} to - The recipient WhatsApp number (E.164 format, e.g., 12015551212).
     * @param {string} text - The message content.
     */
    async function sendWhatsApp(to, text) {
      console.log(`Attempting to send WhatsApp message to ${to}...`);
      try {
        // NOTE: For Sandbox, 'from' MUST be the Sandbox number.
        // For production WhatsApp Business API, 'from' is your provisioned WhatsApp number.
        const fromNumber = process.env.VONAGE_WHATSAPP_NUMBER;
    
        const resp = await vonage.messages.send(
          new WhatsAppText({
            text: text,
            to: to,
            from: fromNumber,
          })
        );
        console.log(`WhatsApp message sent successfully to ${to}. Message UUID: ${resp.messageUuid}`);
        return resp; // Contains message_uuid
      } catch (err) {
        console.error(`Error sending WhatsApp message to ${to}:`, err?.response?.data || err.message);
         // Log specific WhatsApp errors if available in err.response.data
        if (err.response?.data?.invalid_parameters) {
            console.error('Invalid Parameters:', err.response.data.invalid_parameters);
        }
        throw err;
      }
    }
    • Uses vonage.messages.send with a WhatsAppText object from @vonage/messages.
    • Uses the VONAGE_WHATSAPP_NUMBER (which is the Sandbox number in our setup) as the from number. This is required for the Sandbox.
    • Includes similar logging and error handling.
  4. (Optional) Add Test Endpoints:

    To easily trigger sending messages during development, you can add simple GET endpoints. Remember to remove or secure these properly before production.

    javascript
    // server.js (Add this section - for testing only)
    
    // --- Test Endpoints (Development Only - Secure or Remove for Production) ---
    app.get('/test/sms', async (req, res) => {
      const to = process.env.MY_PHONE_NUMBER; // Send to your own number from .env
      const text = `Hello from Vonage SMS! (${new Date().toLocaleTimeString()})`;
      if (!to) {
        return res.status(400).send('MY_PHONE_NUMBER environment variable not set.');
      }
      try {
        const result = await sendSms(to, text);
        res.status(200).json({ message: 'SMS test initiated.', result });
      } catch (error) {
        res.status(500).json({ message: 'Failed to send SMS test.', error: error.message });
      }
    });
    
    app.get('/test/whatsapp', async (req, res) => {
      const to = process.env.MY_PHONE_NUMBER; // Send to your own allowlisted number from .env
      const text = `Hello from Vonage WhatsApp Sandbox! (${new Date().toLocaleTimeString()})`;
       if (!to) {
        return res.status(400).send('MY_PHONE_NUMBER environment variable not set.');
      }
      try {
        // Ensure your number is allowlisted in the Sandbox first!
        const result = await sendWhatsApp(to, text);
        res.status(200).json({ message: 'WhatsApp test initiated.', result });
      } catch (error) {
        res.status(500).json({ message: 'Failed to send WhatsApp test.', error: error.message });
      }
    });
    • These endpoints read your personal phone number from .env and call the respective send functions.

4. Build Express.js Webhook Integration for Inbound Messages

Webhooks are HTTP callbacks triggered by Vonage when events occur (like receiving a message or a status update). Our Express app needs endpoints to listen for these callbacks.

  1. Implement JWT Verification Middleware:

    Create a middleware function to verify the signature of incoming webhooks. This ensures the request genuinely came from Vonage.

    javascript
    // server.js (Add this section)
    
    // --- Webhook Security Middleware ---
    const verifyVonageSignature = (req, res, next) => {
      try {
        // Get authorization header (should be "Bearer <JWT>")
        const authHeader = req.headers.authorization;
        if (!authHeader || !authHeader.startsWith('Bearer ')) {
           console.warn('Webhook received without Bearer token.');
           return res.status(401).send('Unauthorized: Missing Bearer token.');
        }
        const token = authHeader.split(' ')[1];
    
        // Verify the JWT using the signature secret from .env
        const decoded = verifySignature(token, process.env.VONAGE_API_SIGNATURE_SECRET);
    
        // Optional: Check claims like api_key or application_id if needed
        // if (decoded.api_key !== process.env.VONAGE_API_KEY) {
        //   console.warn('Webhook received with mismatched API key.');
        //   return res.status(401).send('Unauthorized: Invalid API Key');
        // }
    
        console.log('Webhook JWT verified successfully.');
        req.vonage_jwt = decoded; // Attach decoded payload if needed later
        next(); // Proceed to the next middleware/handler
    
      } catch (error) {
        console.error('Webhook JWT verification failed:', error.message);
        res.status(401).send('Unauthorized: Invalid signature.');
      }
    };
    • Extracts the JWT from the Authorization: Bearer <token> header.
    • Uses verifySignature from @vonage/jwt and your VONAGE_API_SIGNATURE_SECRET.
    • Sends a 401 Unauthorized response if verification fails.
    • Calls next() to pass control to the actual route handler if verification succeeds.
  2. Create Inbound Webhook Endpoint:

    This endpoint (/webhooks/inbound) will receive incoming SMS and WhatsApp messages.

    javascript
    // server.js (Add this section)
    
    // --- Webhook Endpoints ---
    
    // Endpoint to receive incoming messages (SMS & WhatsApp)
    // Vonage sends POST requests to this URL
    app.post('/webhooks/inbound', verifyVonageSignature, async (req, res) => {
      console.log('--- Inbound Webhook Received ---');
      console.log('Body:', JSON.stringify(req.body, null, 2)); // Log the full payload
    
      const message = req.body;
    
      // Basic sanity check for expected properties
      if (!message.from || !message.to || !message.channel) {
         console.warn('Received incomplete inbound message data.');
         // Still send 200 OK to prevent Vonage retries for malformed requests
         return res.status(200).end();
      }
    
      console.log(`Received message on channel: ${message.channel}`);
      console.log(`From: ${message.from.number || message.from.id}`); // Number format varies by channel
      console.log(`To: ${message.to.number || message.to.id}`);
    
      if (message.message_type === 'text') {
        console.log(`Text: ${message.text}`);
    
        // --- Example: Auto-reply logic ---
        if (message.channel === 'whatsapp') {
          try {
            // Simple reply to incoming WhatsApp
            const replyText = `Thanks for your WhatsApp message: "${message.text}"`;
            await sendWhatsApp(message.from.id, replyText); // Reply to the sender
            console.log(`Sent WhatsApp auto-reply to ${message.from.id}`);
          } catch (error) {
            console.error(`Failed to send WhatsApp auto-reply to ${message.from.id}`, error);
          }
        } else if (message.channel === 'sms') {
           // Optional: Implement SMS auto-reply logic here
           // const replyText = `Thanks for your SMS: "${message.text}"`;
           // await sendSms(message.from.number, replyText);
        }
        // --- End Example ---
    
      } else {
        console.log(`Received non-text message type: ${message.message_type}`);
        // Handle other message types (image, audio, location, etc.) if needed
      }
    
      // IMPORTANT: Always respond with 200 OK quickly to acknowledge receipt.
      // Vonage will retry if it doesn't get a 200 response.
      res.status(200).end();
    });
    • Uses the verifyVonageSignature middleware first.
    • Logs the entire incoming request body (req.body). The structure varies slightly depending on the channel (SMS vs. WhatsApp) and message type. Refer to Vonage Messages API Webhook Reference for details.
    • Includes a simple example of sending an auto-reply via WhatsApp.
    • Crucially, always sends a 200 OK response to Vonage to acknowledge receipt, even if processing fails later. This prevents unnecessary retries from Vonage.
  3. Create Status Webhook Endpoint:

    This endpoint (/webhooks/status) receives updates about the delivery status of messages you sent.

    javascript
    // server.js (Add this section)
    
    // Endpoint to receive message status updates
    // Vonage sends POST requests to this URL
    app.post('/webhooks/status', verifyVonageSignature, (req, res) => {
      console.log('--- Status Webhook Received ---');
      console.log('Body:', JSON.stringify(req.body, null, 2)); // Log the full payload
    
      const statusUpdate = req.body;
    
      // Example: Log key status information
      if (statusUpdate.message_uuid && statusUpdate.status) {
         console.log(`Status Update for ${statusUpdate.message_uuid}: ${statusUpdate.status}`);
         if(statusUpdate.error) {
           console.error(`Error (${statusUpdate.error.code}): ${statusUpdate.error.reason}`);
         }
         // You could update a database record here based on message_uuid and status
         // Statuses include: submitted, delivered, rejected, undeliverable
      } else {
         console.warn('Received incomplete status update data.');
      }
    
    
      // IMPORTANT: Always respond with 200 OK quickly.
      res.status(200).end();
    });
    • Also uses the verifyVonageSignature middleware.
    • Logs the status update payload. Key fields include message_uuid, status (submitted, delivered, rejected, undeliverable), timestamp, and potentially an error object. See Vonage Messages API Webhook Reference.
    • Again, always sends a 200 OK response.

5. Test Your WhatsApp and SMS Integration

Now you can run the application and test the integrations.

  1. Ensure ngrok is Running:

    Make sure the ngrok http 8000 command is still running in its separate terminal and that the correct ngrok Forwarding URL is configured in your Vonage Application and Sandbox settings.

  2. Start the Node.js Server:

    In the terminal window within your project directory, run:

    bash
    node server.js

    You should see:

    text
    Server is listening on port 8000
    Ensure ngrok is running and pointing to http://localhost:8000
  3. Test Sending SMS:

    • Open your web browser and navigate to http://localhost:8000/test/sms.
    • Check the terminal running node server.js. You should see logs indicating an attempt to send SMS and then success with a message_uuid.
    • Check your phone (the MY_PHONE_NUMBER specified in .env). You should receive the SMS message shortly.
    • Check the terminal again. You might see Status Webhook logs related to the SMS delivery (submitted, delivered).
  4. Test Sending WhatsApp:

    • Important: Ensure your MY_PHONE_NUMBER is allowlisted in the Vonage WhatsApp Sandbox.
    • Open your web browser and navigate to http://localhost:8000/test/whatsapp.
    • Check the node server.js terminal for logs indicating the attempt and success.
    • Check your WhatsApp on your phone. You should receive the message from the Vonage Sandbox number.
    • Check the terminal for Status Webhook logs related to the WhatsApp message.
  5. Test Receiving SMS:

    • Using your physical phone, send an SMS message to your VONAGE_SMS_NUMBER.
    • Check the node server.js terminal. You should see the "Inbound Webhook Received" logs, including the message details (channel: sms, your phone number, the text).
  6. Test Receiving WhatsApp (and Auto-Reply):

    • Using your physical phone (with the allowlisted WhatsApp number), send a WhatsApp message to the VONAGE_WHATSAPP_NUMBER (the Sandbox number).
    • Check the node server.js terminal. You should see:
      • "Inbound Webhook Received" logs (channel: whatsapp, your number, the text).
      • Logs indicating an attempt to send the WhatsApp auto-reply.
      • Logs indicating the auto-reply was sent successfully.
    • Check your WhatsApp. You should receive the auto-reply message from the Sandbox.
    • Check the terminal for Status Webhook logs related to the auto-reply message you just sent.

6. Handle Errors and Add Logging

The current implementation has basic console.log and console.error. For production:

  • Use a Dedicated Logger: Integrate a more robust logging library like winston or pino. This enables structured logging (e.g., JSON format), different log levels (debug, info, warn, error), and routing logs to files or external services.
  • Centralized Error Handling Middleware: Implement an Express error-handling middleware to catch unhandled errors, log them consistently, and return appropriate HTTP responses (e.g., generic 500 errors to the client while logging detailed stack traces).
  • Specific Vonage Error Codes: The err.response.data object in the catch blocks of the send functions often contains detailed error information from the Vonage API, including specific error codes and descriptions. Log these details and potentially handle specific, retryable errors (like network issues) differently from non-retryable ones (like invalid numbers). Refer to Vonage API Error Codes.
  • Webhook Reliability: While we send 200 OK immediately, implement background job queues (e.g., using BullMQ with Redis) for processing inbound messages or sending replies if the logic is complex or involves external dependencies. This prevents webhook timeouts and allows for retries on processing failures without blocking the Vonage retry mechanism.

7. Secure Your Messaging Application

  • Webhook Signature Verification: We've implemented JWT verification, which is crucial. Always keep your VONAGE_API_SIGNATURE_SECRET secure.
  • Environment Variables: Never commit .env files or private.key to source control. Use environment variables provided by your deployment platform in production.
  • Input Validation/Sanitization: If you process the content of incoming messages (message.text) for anything beyond simple logging or replies (e.g., saving to a database, using it in commands), sanitize it thoroughly to prevent injection attacks (XSS, SQLi, etc.). Libraries like express-validator can help.
  • Rate Limiting: If your application exposes APIs that trigger messages, implement rate limiting (e.g., using express-rate-limit) to prevent abuse and control costs.
  • Authentication/Authorization: If you build a frontend or API layer on top of this, ensure proper user authentication and authorization before allowing actions like sending messages.

8. Troubleshoot Common Issues

  • Ngrok URL Mismatch: Ensure the exact ngrok URL (including https://) is updated in both the Vonage Application settings and the WhatsApp Sandbox settings whenever you restart ngrok. A common mistake is forgetting one or using http instead of https.
  • Firewall Issues: If ngrok is running but you don't receive webhooks, a local firewall might be blocking ngrok or Node.js.
  • .env Not Loaded: Ensure require('dotenv').config(); is the very first line in server.js. Make sure the .env file is in the root directory where you run node server.js.
  • Incorrect Credentials: Double-check API Key, Secret, Application ID, Signature Secret, and Private Key Path in .env. Ensure the private.key file exists at the specified path.
  • Number Formatting: All phone numbers (to, from, MY_PHONE_NUMBER) should be in E.164 format without leading + or 00 (e.g., 14155551212).
  • WhatsApp Sandbox Limitations:
    • Only works with allowlisted numbers.
    • Uses the fixed Sandbox number (14157386102 or similar) as the from number.
    • Has time limits for conversations initiated by the business (requires user opt-in for production).
  • JWT Verification Errors: Usually caused by an incorrect VONAGE_API_SIGNATURE_SECRET in .env or issues with the system clock if the discrepancy is large.
  • Missing 200 OK: Forgetting to send res.status(200).end() in webhook handlers will cause Vonage to repeatedly retry sending the webhook, leading to duplicate processing and logs.
  • Vonage API Errors: Check the node server.js logs for specific errors returned by the Vonage API (e.g., insufficient funds, invalid recipient number, permission issues).

Frequently Asked Questions

How do I send WhatsApp messages with Vonage in Node.js?

Install the Vonage SDK (npm install @vonage/server-sdk @vonage/messages), authenticate with your API credentials and private key, then use vonage.messages.send(new WhatsAppText()) with your sandbox number as from and recipient number in E.164 format as to. Configure webhooks for delivery status and inbound messages through ngrok during development.

What is the Vonage WhatsApp Sandbox?

The Vonage WhatsApp Sandbox is a testing environment that lets you send and receive WhatsApp messages without a full WhatsApp Business Account. Users must allowlist their phone numbers by sending a specific message to the sandbox number (+14157386102). It's free but requires user opt-in and has conversation time limits.

How do I verify Vonage webhook signatures in Express.js?

Use the verifySignature function from @vonage/jwt to validate the Bearer token in the Authorization header against your Vonage API Signature Secret. This JWT verification ensures webhook requests genuinely originate from Vonage and haven't been tampered with, preventing unauthorized webhook abuse.

What is E.164 phone number format?

E.164 is the international phone numbering format: country code + national number without leading + or 00. Example: US number (415) 555-1212 becomes 14155551212. Vonage requires E.164 format for all phone numbers in API calls to ensure proper routing across global carriers.

How do I handle inbound WhatsApp messages with Vonage?

Create a POST endpoint (e.g., /webhooks/inbound) secured with JWT verification middleware. Parse req.body to extract message.text, message.from.id, and message.channel. Always respond with res.status(200).end() immediately to acknowledge receipt. Process messages asynchronously using background queues for complex logic.

Can I send SMS and WhatsApp from the same Vonage application?

Yes, the Vonage Messages API supports multi-channel messaging from a single application. Use different from numbers (Vonage SMS number for SMS, Sandbox number for WhatsApp) and specify channel: 'sms' or use WhatsAppText class. Configure webhooks once – they receive both SMS and WhatsApp inbound messages.

What Node.js version does Vonage SDK require?

Vonage SDK requires Node.js version 18 or higher for optimal compatibility with modern JavaScript features and security updates. Check your version with node --version. Update via nodejs.org or nvm. The SDK uses ES modules and async/await patterns requiring recent Node.js runtime features.

How do I deploy a Vonage webhook application to production?

Deploy to cloud platforms (AWS, Google Cloud, Heroku, Render) with HTTPS support. Use environment variables for credentials (never commit .env), implement pm2 process manager for reliability, configure production webhook URLs in Vonage Dashboard pointing to your public domain, and enable rate limiting with express-rate-limit to prevent abuse.

9. Deploy to Production

Deploying this application involves running the Node.js process on a server and ensuring it's publicly accessible for webhooks.

  • Choose a Platform: Options include cloud providers (AWS, Google Cloud, Azure), PaaS (Heroku, Render, Vercel - ensure long-running process support), or traditional VPS.
  • Environment Variables: Configure production environment variables securely on your chosen platform instead of using a .env file.
  • Process Manager: Use a process manager like pm2 to keep your Node.js application running reliably (handles crashes, restarts, logging).
  • HTTPS: Ensure your public server uses HTTPS for webhook URLs. Platforms often handle this, or use a reverse proxy like Nginx or Caddy with Let's Encrypt.
  • Update Webhook URLs: Update the Inbound and Status URLs in your Vonage Application and Sandbox settings to point to your permanent public server URL.
  • Firewall Rules: Ensure your server's firewall allows incoming traffic on the port your application listens on (or port 443 if using a reverse proxy).

Learn more about WhatsApp messaging and Node.js integrations:

Frequently Asked Questions

How to send SMS messages with Node.js and Vonage

Use the Vonage Messages API and Node.js SDK. The `sendSms` function handles sending text messages by specifying the recipient's number, message content, your Vonage SMS number, and the 'sms' channel. Ensure numbers are in E.164 format (e.g., 14155551212).

How to receive SMS messages with Vonage and Express

Set up an inbound webhook URL in your Vonage application settings and handle incoming messages in your Express app's `/webhooks/inbound` endpoint. Verify the webhook signature using `@vonage/jwt` to ensure security. Always respond with a 200 OK status, even if processing fails, to prevent Vonage retries.

How to send WhatsApp messages using Node.js and Vonage

Use the `sendWhatsApp` function, providing recipient and message text. The 'from' number must be your Sandbox number for testing or your provisioned WhatsApp Business number for production. This leverages the `WhatsAppText` class from `@vonage/messages`.

What is the Vonage Messages API?

The Vonage Messages API is a unified platform for sending and receiving messages across various channels, including SMS, MMS, WhatsApp, Viber, and Facebook Messenger. It offers a simplified way to integrate messaging into applications using a single API.

Why use ngrok for Vonage webhook development?

Ngrok creates a secure tunnel that exposes your locally running Express.js application to the internet, allowing Vonage to deliver webhooks to your development environment. This is essential for testing webhooks before deploying to a public server.

How to set up Vonage WhatsApp Sandbox

Allowlist your WhatsApp number by sending a specific message to the Sandbox number provided in the Vonage Dashboard. Then, configure your Sandbox webhooks (inbound and status) to point to your application's URLs (e.g., your ngrok URL during development).

What is a Vonage Application ID, and why is it necessary?

A Vonage Application ID is a unique identifier assigned to your application in the Vonage Dashboard. It's essential for associating your application with your Vonage account settings, such as API keys, webhooks, and linked phone numbers.

When should I replace ngrok with a public server?

Replace ngrok with a publicly accessible server with a static IP or domain name before deploying your application to production. Ngrok is a development tool and not suitable for production use due to its temporary URLs and security implications.

Can I receive WhatsApp messages without a business account?

Yes, using the Vonage WhatsApp Sandbox. It's designed for development and testing purposes. For production, a WhatsApp Business Account is required.

What Vonage SDKs do I need for a Node.js messaging application?

You need `@vonage/server-sdk` (core SDK), `@vonage/messages` (Messages API SDK), and `@vonage/jwt` (for verifying webhook signatures) to interact with the Vonage Messages API effectively in your Node.js project.

How to handle Vonage webhook security in Express

Use the `verifyVonageSignature` middleware to verify the JWT (JSON Web Token) signature attached to incoming webhooks. This confirms authenticity and prevents unauthorized requests from reaching your application logic.

Why does Vonage send message status updates?

Vonage sends message status updates via webhooks to inform your application about the delivery status of sent messages. These updates include statuses like 'submitted', 'delivered', 'rejected', or 'undeliverable', which are crucial for monitoring message delivery.

How to verify Vonage webhook signatures

Verify webhook signatures by extracting the JWT (JSON Web Token) from the Authorization header of the webhook request. Use the `@vonage/jwt` library with your `VONAGE_API_SIGNATURE_SECRET` to decode and verify the token, ensuring its origin and integrity.

What is the purpose of the .env file?

The `.env` file stores environment variables like API keys, secrets, and configuration settings. It's essential for managing sensitive credentials securely during development and should never be committed to version control.

When to use the apiHost override in the Vonage client initialization?

The `apiHost` override was typically needed with older versions of the Vonage Node.js SDK, particularly when interacting with the Sandbox. Check the official documentation; recent SDK versions mostly handle the correct endpoint automatically.