code examples

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

Send and Receive SMS and WhatsApp Messages with Node.js and Vonage Messages API

Learn how to build a Node.js Express application to send and receive SMS and WhatsApp messages using the Vonage Messages API. Complete guide with webhooks, security, and deployment.

Send and Receive SMS and WhatsApp Messages with Node.js and Vonage Messages API

Build a Node.js application using Express to send and receive SMS and WhatsApp messages via the Vonage Messages API. This guide covers project setup, configuration, webhook handling, security implementation, and deployment preparation.

By the end of this tutorial, you will have a functional application capable of:

  • Sending SMS messages programmatically.
  • Sending WhatsApp messages using the Vonage WhatsApp Sandbox for testing.
  • Receiving incoming SMS and WhatsApp messages via webhooks.
  • Verifying incoming webhook requests for security.
  • Handling basic errors and logging essential information.

This enables your application to communicate with users via SMS and WhatsApp for notifications, alerts, two-factor authentication (2FA), customer support, and more – all through a unified API interface.

Technologies used

  • Node.js: JavaScript runtime environment for building server-side applications.
  • Express: Minimal and flexible Node.js web application framework.
  • Vonage Messages API: Unified API for sending and receiving messages across multiple channels (SMS, MMS, WhatsApp, Facebook Messenger, Viber).
  • Vonage Node SDK: Simplifies interaction with Vonage APIs in Node.js. This guide uses these SDK modules:
    • @vonage/server-sdk: Core client initialization and authentication.
    • @vonage/messages: Constructing and sending messages via the Messages API.
    • @vonage/jwt: Verifying signatures of incoming webhooks.
  • ngrok: Tool to expose local development servers to the internet for webhook testing.
  • dotenv: Module to load environment variables from a .env file.

Prerequisites

Before you start, ensure you have:

  1. Vonage API Account: Sign up for a free Vonage API account. You'll receive free credit to start building.
  2. Node.js: Install Node.js version 18 or higher from nodejs.org.
  3. ngrok: Install ngrok and create a free account at ngrok.com.
  4. WhatsApp Account: Personal WhatsApp account on your smartphone for testing the WhatsApp Sandbox integration.
  5. Vonage Phone Number: Purchase a virtual phone number from your Vonage dashboard to send and receive SMS. Navigate to Numbers > Buy numbers.

System architecture

Your application interacts with the Vonage platform in two ways:

  1. Sending Messages: Your Node.js app uses the Vonage SDK to call the Vonage Messages API, specifying the channel (SMS or WhatsApp), recipient, sender ID (your Vonage number or WhatsApp Sandbox number), and message content. Vonage delivers the message via the respective network.
  2. Receiving Messages: When a user sends a message to your Vonage number (SMS) or the WhatsApp Sandbox number, Vonage sends an HTTP POST request (webhook) containing the message details to a preconfigured URL endpoint hosted by your Node.js application. During development, ngrok forwards these requests from the public internet to your local machine.
mermaid
graph TD
    subgraph Your Application
        NodeApp[Node.js/Express App]
        EnvFile[.env File] --> NodeApp
    end

    subgraph Vonage Platform
        MessagesAPI[Messages API]
        VonageNumber[Vonage SMS Number]
        WhatsAppSandbox[WhatsApp Sandbox]
        VonageApp[Vonage Application Config]
        Webhooks[Webhook Service]
    end

    subgraph External Networks
        SMSNetwork[SMS Network]
        WhatsAppNetwork[WhatsApp Network]
    end

    UserSMS[User (SMS)]
    UserWhatsApp[User (WhatsApp)]

    NodeApp -- Send Request (SDK) --> MessagesAPI
    MessagesAPI -- Deliver SMS --> SMSNetwork --> UserSMS
    MessagesAPI -- Deliver WhatsApp --> WhatsAppNetwork --> UserWhatsApp

    UserSMS -- Sends SMS --> SMSNetwork -- Inbound SMS --> VonageNumber
    UserWhatsApp -- Sends WhatsApp --> WhatsAppNetwork -- Inbound WhatsApp --> WhatsAppSandbox

    VonageNumber -- Associated with --> VonageApp
    WhatsAppSandbox -- Triggers Webhook --> Webhooks
    VonageApp -- Webhook URLs --> Webhooks

    Webhooks -- POST Request --> Ngrok[ngrok Tunnel] --> NodeApp

    style UserSMS fill:#f9f,stroke:#333,stroke-width:2px
    style UserWhatsApp fill:#9cf,stroke:#333,stroke-width:2px

Note: If this Mermaid diagram doesn't render in your Markdown processor, generate a static image as a fallback and include appropriate alt text.

1. Setting up the project

Initialize your Node.js project and install the necessary dependencies.

  1. Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.

    bash
    mkdir vonage-node-messaging
    cd vonage-node-messaging
  2. Initialize Node.js Project: Initialize a new Node.js project using npm. The -y flag accepts default settings.

    bash
    npm init -y

    This creates a package.json file.

  3. Install Dependencies: Install Express for the web server, the Vonage SDKs for API interaction, and dotenv for managing environment variables.

    bash
    npm install express @vonage/server-sdk @vonage/messages @vonage/jwt dotenv
  4. Create Project Structure: Create a simple structure for your source code.

    bash
    mkdir src
    touch src/server.js
    touch .env
    touch .gitignore
  5. Configure .gitignore: Add node_modules and .env to your .gitignore file to prevent committing them to version control. Never commit sensitive credentials.

    Code
    node_modules/
    .env
  6. Set Up Environment Variables (.env): Open the .env file and add the following variables. You'll fill these in during the Vonage setup section.

    dotenv
    # .env
    
    # Vonage API Credentials (found on Vonage Dashboard homepage)
    VONAGE_API_KEY=YOUR_API_KEY
    VONAGE_API_SECRET=YOUR_API_SECRET
    
    # Vonage Application Details (generated when creating a Vonage Application)
    VONAGE_APPLICATION_ID=YOUR_APPLICATION_ID
    VONAGE_PRIVATE_KEY=./private.key # Path to your downloaded private key file (for local dev)
    
    # Vonage Numbers (your purchased Vonage number and the WhatsApp Sandbox number)
    VONAGE_SMS_FROM_NUMBER=YOUR_VONAGE_SMS_NUMBER
    # Verify the Sandbox number from your Vonage Dashboard (Messages API Sandbox page)
    VONAGE_WHATSAPP_SANDBOX_NUMBER=14157386102
    
    # Webhook Security (found in Vonage Dashboard > Settings)
    VONAGE_SIGNATURE_SECRET=YOUR_SIGNATURE_SECRET
    
    # Server Configuration
    PORT=8000 # Port for the Express server
    • VONAGE_API_KEY, VONAGE_API_SECRET: Your main account credentials.
    • VONAGE_APPLICATION_ID: Identifies your specific Vonage application configuration.
    • VONAGE_PRIVATE_KEY: Path to the private key file used for authenticating requests related to your Vonage application (required for Messages API). For deployment, handle this differently (see Section 8).
    • VONAGE_SMS_FROM_NUMBER: Your purchased Vonage number in E.164 format (e.g., 12015550123).
    • VONAGE_WHATSAPP_SANDBOX_NUMBER: The number provided by Vonage for the WhatsApp Sandbox. Verify this on your dashboard.
    • VONAGE_SIGNATURE_SECRET: Used to verify that incoming webhooks originated from Vonage.
    • PORT: The local port your Express server listens on.

2. Vonage account and application setup

Configure your Vonage account and create a Vonage Application to handle messaging.

  1. Log in to Vonage Dashboard: Access your Vonage API Dashboard.
  2. Find API Key and Secret: On the dashboard homepage, locate your API key and API secret. Copy these values into the corresponding VONAGE_API_KEY and VONAGE_API_SECRET variables in your .env file.
  3. Create a Vonage Application:
    • Navigate to Applications > Create a new application.
    • Give your application a name (e.g., "Node Messaging App").
    • Click Generate public and private key. Important: A private.key file will download. Save this file in your project's root directory (same level as your .env file). Ensure the VONAGE_PRIVATE_KEY path in your .env file points to this file (e.g., ./private.key).
    • Enable Messages under Capabilities. Configure the webhook URLs later after starting ngrok. Leave the Inbound URL and Status URL fields blank for now.
    • Click Generate new application.
    • On the next screen, copy the Application ID and paste it into the VONAGE_APPLICATION_ID variable in your .env file.
  4. Link Your Vonage SMS Number:
    • Go back to your Application's settings page.
    • Scroll down to Link virtual numbers.
    • Find the Vonage phone number you purchased earlier and click Link. This associates incoming SMS messages on this number with your application's webhooks.
    • Copy this number in E.164 format (e.g., 12015550123) into the VONAGE_SMS_FROM_NUMBER variable in your .env file.
  5. Find Signature Secret:
    • Navigate to Settings in the main dashboard menu.
    • Under API settings, find your Signature secret. Copy this value and paste it into the VONAGE_SIGNATURE_SECRET variable in your .env file.
    • Important: Ensure Default SMS Setting is set to Use the Messages API. Save changes if needed.
  6. Set Up WhatsApp Sandbox:
    • Navigate to Messages API Sandbox in the sidebar (under Build & Manage).
    • You'll see QR codes and instructions to link your personal WhatsApp number to the sandbox. Scan the QR code with your phone's WhatsApp app or send the specified message to the Vonage Sandbox number (+14157386102).
    • Follow the prompts in WhatsApp to allowlist your number for testing.
    • Verify the VONAGE_WHATSAPP_SANDBOX_NUMBER in your .env file matches the number shown on the Sandbox page.
    • Add webhook URLs to the Sandbox configuration later.

3. Setting up the Express server

Now let's write the basic Express server code in src/server.js.

javascript
// src/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 fs = require('fs'); // Needed for reading private key file locally

// ---- Initialize Express App ----
const app = express();
// Middleware to parse JSON bodies & URL-encoded data
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// ---- Environment Variables Check ----
const requiredEnv = [
  'VONAGE_API_KEY', 'VONAGE_API_SECRET', 'VONAGE_APPLICATION_ID',
  'VONAGE_PRIVATE_KEY', 'VONAGE_SMS_FROM_NUMBER',
  'VONAGE_WHATSAPP_SANDBOX_NUMBER', 'VONAGE_SIGNATURE_SECRET', 'PORT'
];
const missingEnv = requiredEnv.filter(key => !process.env[key]);
if (missingEnv.length > 0) {
  console.error(`Error: Missing required environment variables: ${missingEnv.join(', ')}`);
  console.error('Please check your .env file or environment configuration.');
  process.exit(1); // Exit if critical variables are missing
}

const PORT = process.env.PORT || 8000;
const VONAGE_SIGNATURE_SECRET = process.env.VONAGE_SIGNATURE_SECRET;

// ---- Initialize Vonage Client ----
// Determine private key source (file path for local dev, env var for production)
let privateKeyValue;
if (process.env.VONAGE_PRIVATE_KEY_CONTENT) {
    // Option 1: Read directly from an env var containing the key content
    privateKeyValue = process.env.VONAGE_PRIVATE_KEY_CONTENT.replace(/\\n/g, '\n'); // Handle potential escaped newlines
} else if (fs.existsSync(process.env.VONAGE_PRIVATE_KEY)) {
    // Option 2: Read from file path specified in .env (for local dev)
    privateKeyValue = fs.readFileSync(process.env.VONAGE_PRIVATE_KEY);
} else {
    console.error('Error: VONAGE_PRIVATE_KEY path is invalid or VONAGE_PRIVATE_KEY_CONTENT env var is not set.');
    process.exit(1);
}

const vonage = new Vonage({
  apiKey: process.env.VONAGE_API_KEY, // Optional for this setup, but good practice
  apiSecret: process.env.VONAGE_API_SECRET, // Optional for this setup
  applicationId: process.env.VONAGE_APPLICATION_ID,
  privateKey: privateKeyValue // Use the loaded key content
}, {
  // Set apiHost for WhatsApp Sandbox testing
  // Remove or change this for production WhatsApp
  apiHost: 'https://messages-sandbox.nexmo.com'
});

console.log('Vonage client initialized.');

// ---- Webhook Security Middleware ----
const verifyVonageSignature = (req, res, next) => {
  try {
    const authHeader = req.headers.authorization;
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      console.warn('Webhook Error: Missing or invalid Authorization header.');
      return res.status(401).send('Unauthorized: Missing or invalid token');
    }
    const token = authHeader.split(' ')[1];

    // It's recommended to check `req.body` directly if possible,
    // but Express middleware might have already parsed it.
    // The SDK's verifySignature expects the raw body or the parsed object.
    if (verifySignature(token, VONAGE_SIGNATURE_SECRET, req.body)) {
      console.log('Webhook Signature Verified Successfully.');
      next(); // Signature is valid, proceed to the route handler
    } else {
      console.warn('Webhook Error: Invalid signature.');
      res.status(401).send('Unauthorized: Invalid signature');
    }
  } catch (error) {
    console.error('Webhook Signature Verification Error:', error);
    res.status(500).send('Internal Server Error during signature verification');
  }
};

// ---- Define Routes (We will add these later) ----
// Placeholder for sending messages API endpoint
app.post('/api/send-message', (req, res) => {
  res.status(501).send({ message: 'Send message endpoint not implemented yet.' });
});

// Placeholder for inbound message webhook
app.post('/webhooks/inbound', verifyVonageSignature, (req, res) => {
  console.log('Received Inbound Webhook (Stub)');
  res.status(200).send('OK'); // Always respond with 200 OK
});

// Placeholder for message status webhook
app.post('/webhooks/status', verifyVonageSignature, (req, res) => {
  console.log('Received Status Webhook (Stub)');
  res.status(200).send('OK'); // Always respond with 200 OK
});

// ---- Basic Error Handler ----
app.use((err, req, res, next) => {
  console.error("Unhandled Error:", err.stack || err);
  res.status(500).send('Something broke!');
});

// ---- Start Server ----
app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
  console.log('Ensure ngrok is running and webhooks are configured correctly.');
});

Explanation:

  1. Load Env Vars: require('dotenv').config() loads variables from .env.
  2. Initialize Express: Sets up the Express application and middleware for parsing request bodies.
  3. Env Var Check: Verifies that all necessary environment variables are present.
  4. Initialize Vonage Client: Creates a Vonage instance. It now attempts to read the private key content either directly from an environment variable VONAGE_PRIVATE_KEY_CONTENT (for deployment) or from the file path specified in VONAGE_PRIVATE_KEY (for local development). The apiHost is set specifically for the WhatsApp Sandbox. Remove or change this for production WhatsApp.
  5. Webhook Security Middleware (verifyVonageSignature): Intercepts webhook requests, extracts the JWT token, verifies it using verifySignature, and passes control or sends an error response. Crucially, webhooks must be secured.
  6. Route Placeholders: Defines basic routes, applying verifyVonageSignature only to webhook routes.
  7. Basic Error Handler: A simple catch-all middleware.
  8. Start Server: Starts the Express server.

4. Sending messages

Let's implement the functions to send SMS and WhatsApp messages.

  1. Add Sending Functions: Add these functions inside src/server.js before the route definitions.
javascript
// src/server.js
// ... (Keep existing code above)

// ---- Message Sending Functions ----

/**
 * Sends an SMS message using the Vonage Messages API.
 * @param {string} toNumber - The recipient's phone number (E.164 format).
 * @param {string} text - The message content.
 * @returns {Promise<string>} - The message UUID on success.
 * @throws {Error} - If sending fails.
 */
const sendSmsMessage = async (toNumber, text) => {
  const fromNumber = process.env.VONAGE_SMS_FROM_NUMBER;
  console.log(`Attempting to send SMS from ${fromNumber} to ${toNumber}`);
  try {
    const response = await vonage.messages.send({
      message_type: "text",
      text: text,
      to: toNumber,
      from: fromNumber,
      channel: "sms"
    });
    // Assuming SDK maps message_uuid to messageUuid based on code comments/context
    console.log(`SMS sent successfully to ${toNumber}. Message UUID: ${response.messageUuid}`);
    return response.messageUuid;
  } catch (error) {
    console.error(`Error sending SMS to ${toNumber}:`, error.response ? error.response.data : error.message);
    // Rethrow or handle specific errors (e.g., invalid number, insufficient funds)
    throw new Error(`Failed to send SMS: ${error.message}`);
  }
};

/**
 * Sends a WhatsApp message using the Vonage Messages API (Sandbox).
 * @param {string} toNumber - The recipient's WhatsApp number (E.164 format).
 * @param {string} text - The message content.
 * @returns {Promise<string>} - The message UUID on success.
 * @throws {Error} - If sending fails.
 */
const sendWhatsAppMessage = async (toNumber, text) => {
  // For Sandbox, 'from' is the Sandbox number. For production, it's your provisioned WhatsApp number.
  const fromNumber = process.env.VONAGE_WHATSAPP_SANDBOX_NUMBER;
  console.log(`Attempting to send WhatsApp message from ${fromNumber} to ${toNumber}`);
  try {
    // Use the WhatsAppText message constructor for WhatsApp
    const response = await vonage.messages.send(
      new WhatsAppText({
        text: text,
        to: toNumber,
        from: fromNumber, // Using Sandbox number here
      })
    );
    // Assuming SDK maps message_uuid to messageUuid based on code comments/context
    console.log(`WhatsApp message sent successfully to ${toNumber}. Message UUID: ${response.messageUuid}`);
    return response.messageUuid;
  } catch (error) {
    console.error(`Error sending WhatsApp message to ${toNumber}:`, error.response ? error.response.data : error.message);
    // Handle specific WhatsApp errors (e.g., user not allowlisted in sandbox, template required for production)
    throw new Error(`Failed to send WhatsApp message: ${error.message}`);
  }
};

// ---- Webhook Security Middleware (Keep existing code) ----
// const verifyVonageSignature = ...

// ---- Define Routes ----
// Update the /api/send-message endpoint
app.post('/api/send-message', async (req, res) => {
  const { to, type, text } = req.body;

  // Basic Input Validation
  if (!to || !type || !text) {
    return res.status(400).send({ error: 'Missing required fields: to, type, text' });
  }
  if (type !== 'sms' && type !== 'whatsapp') {
    return res.status(400).send({ error: 'Invalid type. Must be "sms" or "whatsapp"' });
  }
  // Add more robust phone number validation if needed (e.g., using a library)

  try {
    let messageUuid;
    if (type === 'sms') {
      messageUuid = await sendSmsMessage(to, text);
    } else { // type === 'whatsapp'
      // Ensure the target number is allowlisted in the Sandbox for testing
      console.warn(`Sending WhatsApp to ${to}. Ensure this number is allowlisted in the Sandbox.`);
      messageUuid = await sendWhatsAppMessage(to, text);
    }
    res.status(200).send({ message: `${type.toUpperCase()} sent successfully`, messageUuid });
  } catch (error) {
    console.error(`Failed to send message via API endpoint: ${error.message}`);
    // Provide a more user-friendly error message
    res.status(500).send({ error: `Failed to send ${type.toUpperCase()} message.`, details: error.message });
  }
});

// ---- Webhook Routes (Keep existing code) ----
// app.post('/webhooks/inbound', verifyVonageSignature, ...
// app.post('/webhooks/status', verifyVonageSignature, ...

// ---- Basic Error Handler (Keep existing code) ----
// app.use((err, req, res, next) => ...

// ---- Start Server (Keep existing code) ----
// app.listen(PORT, () => ...

Explanation:

  • sendSmsMessage: Sends SMS using vonage.messages.send with channel: "sms".
  • sendWhatsAppMessage: Sends WhatsApp using the WhatsAppText constructor and the Sandbox number.
  • API Endpoint (/api/send-message): Handles POST requests to send messages, validates input, and calls the appropriate sending function.

5. Receiving messages (webhooks)

To receive messages, we need to expose our local server to the internet using ngrok and configure Vonage to send webhooks to that public URL.

  1. Start ngrok: Open a new terminal window and start ngrok, pointing it to the port your Express server is listening on (e.g., 8000).

    bash
    ngrok http 8000

    Copy the https:// Forwarding URL (e.g., https://<unique-subdomain>.ngrok-free.app). This is your public webhook base URL.

  2. Configure Vonage Webhooks:

    • Application Webhooks (for SMS):
      • Go back to your Vonage Application settings in the dashboard.
      • Under Messages capabilities, paste your ngrok Forwarding URL into the Inbound URL field, appending /webhooks/inbound.
      • Paste your ngrok Forwarding URL into the Status URL field, appending /webhooks/status.
      • Click Save changes.
    • WhatsApp Sandbox Webhooks:
      • Navigate to the Messages API Sandbox page in the dashboard.
      • Click on the Webhooks tab.
      • Paste your ngrok URL + /webhooks/inbound into the Inbound URL field.
      • Paste your ngrok URL + /webhooks/status into the Status URL field.
      • Click Save webhooks.
  3. Implement Webhook Handlers: Update the placeholder webhook routes in src/server.js to actually process the incoming data.

javascript
// src/server.js
// ... (Keep existing code for initialization, sending functions, middleware)

// ---- Define Routes ----
// app.post('/api/send-message', ... (Keep existing code)

// ---- Webhook Routes ----

// Inbound Message Webhook
app.post('/webhooks/inbound', verifyVonageSignature, (req, res) => {
  console.log('--- INBOUND WEBHOOK RECEIVED ---');
  console.log('Timestamp:', new Date().toISOString());
  // Log the full payload as structured JSON
  console.log('Request Body:', JSON.stringify(req.body, null, 2));

  const { channel, message_type, from, to, text, message_uuid } = req.body;

  // Process based on channel
  if (channel === 'sms') {
    console.log(`Received SMS from ${from} to ${to}`);
    if (message_type === 'text' && text) {
      console.log(`Message Text: "${text}"`);
      // --- Add your SMS processing logic here ---
      // Example: Look up user, trigger reply, store message
      // await processIncomingSms(from, text);
    }
  } else if (channel === 'whatsapp') {
    console.log(`Received WhatsApp message from ${from} to ${to}`);
    if (message_type === 'text' && text) {
      console.log(`Message Text: "${text}"`);
      // --- Add your WhatsApp processing logic here ---
      // Example: Handle commands, interact with chatbot logic
      // await processIncomingWhatsApp(from, text);
    }
    // Handle other WhatsApp message types (image, audio, location, etc.) if needed
    // else { console.log(`Received WhatsApp message of type: ${message_type}`); }

  } else {
    console.log(`Received message on unhandled channel: ${channel}`);
  }

  // CRITICAL: Always respond with 200 OK quickly to acknowledge receipt.
  res.status(200).send('OK');
});

// Message Status Webhook
app.post('/webhooks/status', verifyVonageSignature, (req, res) => {
  console.log('--- STATUS WEBHOOK RECEIVED ---');
  console.log('Timestamp:', new Date().toISOString());
  // Log the full payload as structured JSON
  console.log('Request Body:', JSON.stringify(req.body, null, 2));

  const { message_uuid, status, timestamp, error, usage } = req.body;

  console.log(`Status update for Message UUID: ${message_uuid}`);
  console.log(`Status: ${status} at ${timestamp}`);

  if (error) {
    console.error(`Error Code: ${error.code}, Reason: ${error.reason}`);
    // --- Add error handling logic here ---
    // Example: Log to error tracking service, notify admin, attempt retry if applicable
    // await handleFailedMessage(message_uuid, error);
  }

  if (usage) {
    console.log(`Message Cost: ${usage.price} ${usage.currency}`);
    // --- Add usage tracking logic here ---
  }

  // Potential statuses: submitted, delivered, rejected, undeliverable, read (WhatsApp)
  switch (status) {
      case 'delivered':
          console.log('Message successfully delivered.');
          break;
      case 'read':
          console.log('WhatsApp message read by recipient.');
          break;
      case 'rejected':
      case 'undeliverable':
          console.warn(`Message failed with status: ${status}`);
          // Consider adding specific handling for failed messages
          break;
      default:
          console.log(`Received status: ${status}`);
  }

  // CRITICAL: Always respond with 200 OK quickly.
  res.status(200).send('OK');
});

// ---- Basic Error Handler (Keep existing code) ----
// app.use((err, req, res, next) => ...

// ---- Start Server (Keep existing code) ----
// app.listen(PORT, () => ...

Explanation:

  • Inbound Webhook (/webhooks/inbound): Logs the full incoming payload using JSON.stringify for better structure. Differentiates processing based on channel. Responds with 200 OK promptly.
  • Status Webhook (/webhooks/status): Logs the status update payload using JSON.stringify. Provides details on message delivery, errors, and usage. Responds with 200 OK promptly.

6. Error handling and logging refinement

While we have basic try...catch and logging, let's refine it slightly. For production, consider dedicated logging libraries (pino, winston) and error tracking services (Sentry, Rollbar).

  • Structured Logging: Using JSON.stringify in the webhook handlers (as implemented above) is a step towards structured logging, making logs easier to parse. You can enhance this further by adopting a standard log format across your application.
    javascript
    // Example structured log (more detailed)
    console.log(JSON.stringify({
      level: 'info',
      timestamp: new Date().toISOString(),
      message: 'Received inbound SMS',
      details: { from: from, to: to, messageId: message_uuid }
    }));
    
    // Example structured error log
    console.error(JSON.stringify({
        level: 'error',
        timestamp: new Date().toISOString(),
        message: 'Failed to send SMS',
        error: { message: error.message, stack: error.stack }, // Include stack trace
        context: { recipient: toNumber, function: 'sendSmsMessage' }
    }));
  • Specific Error Handling: Catch specific Vonage API errors if possible by inspecting error.response.data or error.message for details provided by Vonage.
  • Webhook Retries: Remember Vonage retries webhooks if they don't receive a 200 OK. Your webhook handler should be idempotent (safe to run multiple times with the same input) or store processed message_uuids to prevent duplicate actions.
  • Retry Mechanisms (Sending): For transient network errors when sending messages, consider implementing a simple retry strategy with exponential backoff (e.g., using libraries like async-retry).

7. Verification and testing

Now, let's test the complete flow.

  1. Ensure Services are Running:

    • Your Node.js server (node src/server.js in one terminal).
    • ngrok (ngrok http <PORT> in another terminal).
  2. Test Sending SMS:

    • Use curl or Postman. Replace <YOUR_PERSONAL_PHONE_NUMBER> (E.164 format).
    bash
    curl -X POST http://localhost:8000/api/send-message \
         -H "Content-Type: application/json" \
         -d '{
               "to": "<YOUR_PERSONAL_PHONE_NUMBER>",
               "type": "sms",
               "text": "Hello from Vonage Node App! (SMS)"
             }'
    • Check: SMS received, server logs success, status webhooks arrive.
  3. Test Sending WhatsApp:

    • Ensure your number is allowlisted in the Sandbox. Replace <YOUR_PERSONAL_WHATSAPP_NUMBER> (E.164 format).
    bash
    curl -X POST http://localhost:8000/api/send-message \
         -H "Content-Type: application/json" \
         -d '{
               "to": "<YOUR_PERSONAL_WHATSAPP_NUMBER>",
               "type": "whatsapp",
               "text": "Hello from Vonage Node App! (WhatsApp Sandbox)"
             }'
    • Check: WhatsApp message received, server logs success, status webhooks arrive.
  4. Test Receiving SMS:

    • From your personal phone, send an SMS to your VONAGE_SMS_FROM_NUMBER.
    • Check: ngrok shows POST /webhooks/inbound, server logs signature verification, inbound webhook details (channel: sms), and message text.
  5. Test Receiving WhatsApp:

    • From your personal WhatsApp, send a message to the VONAGE_WHATSAPP_SANDBOX_NUMBER (+14157386102).
    • Check: ngrok shows POST /webhooks/inbound, server logs signature verification, inbound webhook details (channel: whatsapp), and message text.

8. Deployment and CI/CD considerations

Deploying this application requires replacing ngrok with a permanent public URL and managing environment variables securely.

  1. Choose a Hosting Platform: Options include Heroku, Vercel, AWS, Google Cloud, Azure, etc.
  2. Environment Variables: Configure your environment variables (VONAGE_API_KEY, VONAGE_API_SECRET, etc.) directly on the hosting platform. Do not commit your .env file.
    • Private Key Handling: Instead of using a file path for VONAGE_PRIVATE_KEY, set the content of your private.key file as an environment variable (e.g., VONAGE_PRIVATE_KEY_CONTENT). Many platforms support multi-line environment variables. Ensure your src/server.js reads this variable, as shown in the updated initialization code in Section 3. You might need to handle newline characters (\n) correctly (e.g., replace literal \n with actual newlines if your platform escapes them).
    • Example Code Modification (already done in Section 3): The Vonage client initialization in src/server.js now checks for process.env.VONAGE_PRIVATE_KEY_CONTENT first before falling back to the file path.
  3. Public URL: Your hosting platform will provide a public URL (e.g., https://your-app-name.herokuapp.com).
  4. Update Vonage Webhooks: Replace the ngrok URLs in your Vonage Application and Messages API Sandbox settings with your permanent public URL (e.g., https://your-app-name.herokuapp.com/webhooks/inbound and /status).
  5. Procfile (for Heroku/similar): You might need a Procfile:
    Procfile
    web: node src/server.js
  6. CI/CD Pipeline: Set up a pipeline (GitHub Actions, GitLab CI, etc.) for automated testing, building, and deployment, managing environment variables securely.

9. Troubleshooting and caveats

  • Invalid Credentials: Double-check API keys, secret, Application ID, and private key content/path.
  • Webhook Failures (401 Unauthorized): Incorrect VONAGE_SIGNATURE_SECRET or issues with the verifySignature implementation.
  • Webhook Failures (No Response / Timeouts): Server not responding 200 OK quickly, or the server crashed. Check server logs. Vonage expects a response within a few seconds.
  • WhatsApp Sandbox Limitations: Only works with allowlisted numbers. Message templates are required for production WhatsApp outside the 24-hour customer care window. The apiHost needs to be changed for production WhatsApp.
  • Rate Limits: Be aware of Vonage API rate limits. Implement appropriate error handling and backoff strategies.
  • Number Formatting: Ensure all phone numbers are in E.164 format (e.g., +12015550123).
  • SMS Sender ID: SMS sender ID behavior varies by country. Your Vonage number might be overwritten in some regions.
  • Idempotency: Ensure webhook handlers are idempotent to handle potential retries from Vonage safely.

Frequently Asked Questions

How to send SMS messages with Node.js and Vonage?

Use the Vonage Messages API and Node.js SDK. The `sendSmsMessage` function handles sending SMS messages via the API, specifying the recipient's number, your Vonage number, and the message content. Ensure your Vonage number is linked to your application in the Vonage dashboard.

How to receive WhatsApp messages in Node.js app?

Set up webhooks to receive incoming WhatsApp messages. Configure the inbound webhook URL in your Vonage application settings and WhatsApp Sandbox settings, pointing to your public ngrok URL (during development) or your deployed application URL, appended with '/webhooks/inbound'. Your server must respond with a 200 OK status to acknowledge receipt.

What is Vonage Messages API used for?

The Vonage Messages API is a unified interface for sending and receiving messages across multiple channels, including SMS, MMS, WhatsApp, Facebook Messenger, and Viber. It simplifies the process of integrating messaging into your applications.

Why does Vonage use webhooks for incoming messages?

Vonage uses webhooks to deliver incoming messages to your application in real-time. When a message is sent to your Vonage number or WhatsApp Sandbox number, Vonage sends an HTTP POST request to a pre-configured URL on your server, allowing your application to process the message immediately.

When should I use the WhatsApp Sandbox?

Use the WhatsApp Sandbox for testing your WhatsApp integration during development. It allows you to send and receive messages with allowlisted numbers without incurring charges. Remember that for production, you need a Vonage-approved WhatsApp Business Account linked to a verified WhatsApp Business Profile.

Can I send WhatsApp messages without allowlisting numbers?

Not in the WhatsApp Sandbox. You must allowlist the recipient's WhatsApp number in the Vonage dashboard for testing. In production, users will need to initiate the conversation with you or opt-in to messaging from your business, or you'll need approved pre-registered message templates.

What are the prerequisites for this Node.js tutorial?

You need a Vonage API account, Node.js (version 18 or higher recommended), ngrok for local development, a personal WhatsApp account, and a purchased Vonage phone number for sending/receiving SMS.

How to verify Vonage webhook signatures in Node.js?

Use the `@vonage/jwt` library, specifically the `verifySignature` function. This function verifies the JWT signature included in incoming webhook requests, ensuring they are authentic and originated from Vonage.

What is the Vonage Application ID used for?

The Vonage Application ID uniquely identifies your application's configuration within the Vonage platform. It connects your code to your Vonage account and its associated numbers, webhooks, and other settings.

How to handle inbound SMS messages with Node.js and Vonage?

The '/webhooks/inbound' route in your Node.js server handles incoming SMS messages. Parse the 'channel', 'message_type', 'from', 'to', and 'text' properties from the request body (`req.body`) to access the sender, content, and other details. Then add your specific business logic to process the message data.

How to secure Vonage webhooks to prevent unauthorized access?

Secure your webhooks by verifying the signature of incoming requests. This requires setting a 'Signature Secret' in your Vonage settings, and using a middleware function in your Node.js app to validate the signature, ensuring the webhook request actually originated from Vonage.

How to set up environment variables for a Vonage application?

Create a .env file in your project's root directory and add your Vonage API credentials, application details, and other sensitive information. Use the dotenv library to load these variables into your Node.js application during development. For deployment, set these as environment variables on your hosting platform.

How to handle webhook errors with Vonage Messages API?

The status webhook delivers information about message delivery status and any errors encountered. Log error details like 'code' and 'reason' using structured logging for better analysis. Implement appropriate error handling, including retries or notifications based on specific error codes.

How to test Vonage SMS and WhatsApp integration locally?

Use ngrok to create a publicly accessible URL for your local server. Configure your Vonage webhooks to point to this ngrok URL. Then test sending messages using curl or Postman to your local server endpoint, and also send actual SMS and WhatsApp messages to trigger the inbound webhooks. Verify all logs on both server and ngrok.

How to deploy a Vonage messaging Node.js application?

Choose a hosting provider (Heroku, AWS, Google Cloud, etc.), replace ngrok URLs with your permanent domain, manage environment variables securely on the platform (including setting VONAGE_PRIVATE_KEY_CONTENT if using the updated private key handling), update Vonage webhooks to point to the deployed URL, and establish a CI/CD pipeline for automated deployments.