code examples

Sent logo
Sent TeamMay 3, 2025 / code examples / Node.js

Send and receive SMS & WhatsApp messages with Node.js, Express, and Vonage

Learn how to build a complete Node.js application with Express to send and receive SMS and WhatsApp messages using Vonage Messages API. Includes webhook handling, security, and production-ready code examples.

Send and receive SMS & WhatsApp messages with Node.js, Express, and Vonage

Meta Description: Learn how to build a complete Node.js application with Express to send and receive SMS and WhatsApp messages using Vonage Messages API. Includes webhook handling, security, and production-ready code examples.

This guide provides a comprehensive walkthrough for building a Node.js application using the Express framework to send and receive both SMS and WhatsApp messages via the Vonage Messages API. You'll implement multi-channel messaging capabilities for notifications, alerts, customer support, or two-factor authentication using a unified API approach.

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

  1. Sending outbound SMS messages programmatically
  2. Receiving inbound SMS messages via webhooks
  3. Sending outbound WhatsApp messages using the Vonage WhatsApp Sandbox
  4. Receiving inbound WhatsApp messages via webhooks and sending automated replies

This guide solves the common need for applications to integrate multi-channel messaging capabilities for notifications, alerts, customer support, or two-factor authentication, leveraging the unified Vonage Messages API.

Technologies Used:

  • Node.js: A JavaScript runtime environment for building server-side applications.
  • Express: A minimal and flexible Node.js web application framework.
  • Vonage Messages API: A unified API for sending and receiving messages across various channels (SMS, MMS, WhatsApp, Facebook Messenger, Viber).
  • Vonage Node.js SDK: Simplifies interaction with Vonage APIs in Node.js.
  • ngrok: A tool to expose local development servers to the internet for webhook testing.
  • dotenv: A module to load environment variables from a .env file.

How It Works:

  • Outbound Messaging: Your Node.js app makes API calls to Vonage, which then delivers the message via the appropriate network (SMS/WhatsApp).
  • Inbound Messaging (Webhooks): A user sends a message (SMS/WhatsApp) to your Vonage number/WhatsApp Sandbox number. Vonage receives it and forwards the message data to your application's configured webhook URLs via an HTTP POST request.
  • Security: JWT signature verification ensures webhook authenticity and protects against unauthorized requests.

Prerequisites:

  • A Vonage API account. Sign up here if you don't have one.
  • Node.js v22 LTS installed (Active LTS until October 2027). Download here.
  • An ngrok account (free tier is sufficient) and the ngrok CLI installed. Sign up and download here.
  • A phone number capable of sending/receiving SMS for testing.
  • A WhatsApp-enabled phone number for testing the WhatsApp integration.

Final Outcome:

You will have two main scripts:

  1. index.js: A standalone script to send an outbound SMS message.
  2. server.js: An Express server that listens for incoming SMS and WhatsApp messages via webhooks, logs them, and automatically replies to incoming WhatsApp messages.

1. Node.js Project Setup for Vonage Messaging

Let's start by creating our project directory and initializing it as a Node.js project.

1. Create Project Directory:

Open your terminal or command prompt and create a new directory for your project.

bash
mkdir vonage-node-messaging
cd vonage-node-messaging

2. Initialize Node.js Project:

Initialize the project using npm, which creates a package.json file.

bash
npm init -y

The -y flag accepts the default settings.

3. Install Dependencies:

Install the required npm packages for Vonage messaging, Express web framework, and environment configuration:

  • express: The web framework.
  • @vonage/server-sdk: The core Vonage SDK.
  • @vonage/messages: Specific helpers for the Messages API (like WhatsAppText).
  • @vonage/jwt: For verifying webhook signatures (important for security).
  • dotenv: To manage environment variables securely.

Install them using npm:

bash
npm install express @vonage/server-sdk @vonage/messages @vonage/jwt dotenv

4. Create Project Structure:

For simplicity, we'll keep our code in the root directory for this example. Create the necessary files:

bash
touch index.js server.js .env .gitignore
  • index.js: Will contain the code to send an SMS.
  • server.js: Will contain the Express server to handle incoming webhooks.
  • .env: Will store our API keys and other configuration secrets. Never commit this file to version control.
  • .gitignore: Specifies intentionally untracked files that Git should ignore.

5. Configure .gitignore:

Add node_modules and .env to your .gitignore file to prevent committing dependencies and secrets.

text
# .gitignore

node_modules/
.env

6. Set up Environment Variables (.env):

Open the .env file and add the following placeholders. We will fill these in during the Vonage configuration steps.

dotenv
# .env

# Vonage API Credentials (Found on Vonage Dashboard homepage)
VONAGE_API_KEY=
VONAGE_API_SECRET=

# Vonage Application Details (Generated when creating a Vonage Application)
VONAGE_APPLICATION_ID=
VONAGE_PRIVATE_KEY=./private.key # Path to your downloaded private key file

# Vonage Numbers (One purchased Vonage number for SMS, Sandbox number for WhatsApp)
VONAGE_SMS_FROM_NUMBER= # Your Vonage virtual number for sending SMS
VONAGE_WHATSAPP_NUMBER=14157386102 # Vonage Sandbox number (fixed)

# Webhook Signature Verification (Found in Vonage Dashboard Settings)
VONAGE_API_SIGNATURE_SECRET=

# Server Configuration
PORT=8000 # Port for the Express server to listen on

Explanation of Configuration Choices:

  • .env: Using environment variables keeps sensitive credentials out of the codebase, enhancing security and making configuration easier across different environments (development, production).
  • .gitignore: Essential for preventing accidental exposure of secrets (.env) and unnecessary bloating of the repository (node_modules).
  • Express: Chosen for its simplicity, extensive middleware ecosystem, and widespread adoption in the Node.js community, making it ideal for building webhook handlers.

2. Vonage API Configuration and WhatsApp Sandbox Setup

Before writing code, we need to configure Vonage by creating an Application, setting up the WhatsApp Sandbox, and obtaining the necessary credentials.

1. Obtain API Key and Secret:

  • Log in to your Vonage API Dashboard.
  • Your API key and API secret are displayed prominently on the homepage.
  • Copy these values and paste them into your .env file for VONAGE_API_KEY and VONAGE_API_SECRET.

2. Create a Vonage Application:

Vonage Applications act as containers for your communication settings, including webhook URLs and authentication keys.

  • Navigate to "Applications" in the left-hand menu, then click "Create a new application".
  • Give your application a name (e.g., "Node Messaging App").
  • Generate Public/Private Key Pair: Click the "Generate public and private key" button. This will automatically download a private.key file. Save this file in the root of your project directory (vonage-node-messaging). The public key is stored by Vonage. Update the VONAGE_PRIVATE_KEY path in your .env file if you save it elsewhere (the default ./private.key assumes it's in the root).
  • Enable Capabilities: Toggle on the "Messages" capability.
  • Configure Webhook URLs: For now, enter placeholder URLs. We will update these later with our ngrok URL.
    • Inbound URL: http://example.com/webhooks/inbound
    • Status URL: http://example.com/webhooks/status
  • Click "Generate new application".
  • On the next page, you'll see your Application ID. Copy this ID and paste it into your .env file for VONAGE_APPLICATION_ID.

3. Link a Vonage Number (for SMS):

To send and receive SMS, you need a Vonage virtual number linked to your application.

  • If you don't have a number, navigate to "Numbers" > "Buy numbers" and purchase an SMS-capable number in your desired country.
  • Go back to your Application settings ("Applications" > click your app name).
  • Scroll down to the "Link virtual numbers" section. Click "Link" next to the number you want to use for SMS.
  • Copy this Vonage number (including the country code, e.g., 12015550123) and paste it into your .env file for VONAGE_SMS_FROM_NUMBER.

4. Set up the Messages API WhatsApp Sandbox:

The Sandbox provides a testing environment without needing a full WhatsApp Business Account initially.

  • Navigate to "Messages API Sandbox" in the left-hand menu.
  • You'll see instructions to add the Vonage Sandbox number (+14157386102) to your WhatsApp contacts and send it a specific join message (e.g., "WhatsApp Sandbox"). Do this now from the phone number you want to use for testing. This "allowlists" your number.
  • The Sandbox number (14157386102) is pre-filled in the .env file for VONAGE_WHATSAPP_NUMBER.
  • Configure Sandbox Webhooks: Similar to the application, we need to set webhooks for the Sandbox. Enter placeholders for now:
    • Inbound URL: http://example.com/webhooks/inbound
    • Status URL: http://example.com/webhooks/status
  • Click "Save webhooks".

5. Obtain API Signature Secret (for Webhook Security):

Vonage signs incoming webhook requests with a JWT (JSON Web Token) using a secret key, allowing you to verify their authenticity.

  • Click your name in the top-right corner of the dashboard, then "Settings".
  • Under "API settings", find the "Default SMS Setting". While this guide explicitly uses the Messages API for sending (making this setting less critical for the sending code shown), ensuring it's set to "Messages API" aligns your account settings with the API being used.
  • Scroll down to the "Signing secret" section. You may need to click "Generate new secret" if one doesn't exist.
  • Copy the Signature Secret.
  • Paste this value into your .env file for VONAGE_API_SIGNATURE_SECRET.

6. Start ngrok:

To receive webhooks on your local machine, we need to expose your local server to the internet using ngrok.

  • Open a new terminal window (keep it running).
  • Start ngrok, telling it to forward traffic to the port your Express server will run on (defined as 8000 in our .env).
bash
ngrok http 8000
  • ngrok will display a public "Forwarding" URL (e.g., https://<unique-subdomain>.ngrok-free.app). Copy this HTTPS URL.

7. Update Webhook URLs in Vonage:

Now, replace the placeholder URLs with your actual ngrok URL.

  • Application Webhooks:
    • Go back to "Applications" > your application > "Edit".
    • Update the Messages capability URLs:
      • Inbound URL: <your-ngrok-https-url>/webhooks/inbound
      • Status URL: <your-ngrok-https-url>/webhooks/status
    • Click "Save changes".
  • Sandbox Webhooks:
    • Go back to "Messages API Sandbox".
    • Update the webhook URLs:
      • Inbound URL: <your-ngrok-https-url>/webhooks/inbound
      • Status URL: <your-ngrok-https-url>/webhooks/status
    • Click "Save webhooks".

You have now fully configured Vonage and linked it to your local development environment via ngrok.


3. Implementing SMS and WhatsApp Message Handlers

Now let's write the Node.js code to send and receive messages.

Part 1: Sending an SMS (index.js)

This script demonstrates sending a single outbound SMS message using the credentials and Vonage number configured.

javascript
// index.js
require('dotenv').config();
const { Vonage } = require('@vonage/server-sdk');

// --- Configuration ---
// Ensure these environment variables are set in your .env file
const VONAGE_API_KEY = process.env.VONAGE_API_KEY;
const VONAGE_API_SECRET = process.env.VONAGE_API_SECRET;
const VONAGE_APPLICATION_ID = process.env.VONAGE_APPLICATION_ID;
const VONAGE_PRIVATE_KEY = process.env.VONAGE_PRIVATE_KEY;
const VONAGE_SMS_FROM_NUMBER = process.env.VONAGE_SMS_FROM_NUMBER;

// --- IMPORTANT: Replace with the recipient's phone number ---
const TO_NUMBER = 'REPLACE_WITH_RECIPIENT_PHONE_NUMBER'; // e.g., '14155550100' (use E.164 format)
// ---

// --- Input Validation ---
if (!VONAGE_API_KEY || !VONAGE_API_SECRET || !VONAGE_APPLICATION_ID || !VONAGE_PRIVATE_KEY || !VONAGE_SMS_FROM_NUMBER) {
  console.error('Error: Missing required Vonage credentials in .env file.');
  process.exit(1); // Exit if configuration is incomplete
}
if (!TO_NUMBER || TO_NUMBER === 'REPLACE_WITH_RECIPIENT_PHONE_NUMBER') {
    console.error('Error: Please replace TO_NUMBER with a valid recipient phone number in index.js');
    process.exit(1);
}
// Basic format check - allows digits, optionally preceded by '+', up to 15 digits total.
// Note: This is a basic check and doesn't fully validate E.164 format structure.
if (!/^\+?\d{1,15}$/.test(TO_NUMBER)) {
    console.error(`Error: Invalid TO_NUMBER format: ${TO_NUMBER}. Use E.164 format (e.g., 14155550100 or +14155550100).`);
    process.exit(1);
}
// Same basic check for the sender number
if (!/^\+?\d{1,15}$/.test(VONAGE_SMS_FROM_NUMBER)) {
    console.error(`Error: Invalid VONAGE_SMS_FROM_NUMBER format: ${VONAGE_SMS_FROM_NUMBER}. Use E.164 format (e.g., 12015550123 or +12015550123).`);
    process.exit(1);
}


// --- Initialize Vonage Client ---
// Using Application ID and Private Key for authentication is recommended for Messages API
const vonage = new Vonage({
  apiKey: VONAGE_API_KEY,
  apiSecret: VONAGE_API_SECRET, // Needed for fallback or other APIs, though Messages prefers JWT
  applicationId: VONAGE_APPLICATION_ID,
  privateKey: VONAGE_PRIVATE_KEY,
});

// --- Send SMS Message ---
const text = 'Hello from Vonage and Node.js!'; // Your message content

async function sendSms() {
  // Remove leading '+' if present for the API call, as per SDK examples for `to`/`from`
  const recipientNumber = TO_NUMBER.replace('+', '');
  const senderNumber = VONAGE_SMS_FROM_NUMBER.replace('+', '');

  console.log(`Attempting to send SMS from ${senderNumber} to ${recipientNumber}...`);
  try {
    const response = await vonage.messages.send({
      message_type: 'text',
      text: text,
      to: recipientNumber,
      from: senderNumber,
      channel: 'sms',
    });
    console.log(`SMS submitted successfully with Message UUID: ${response.messageUuid}`);
    console.log('Note: Successful submission means Vonage accepted the request. Check status webhooks or message logs for delivery status.');
  } catch (error) {
    console.error('Error sending SMS:');
    // Log detailed error information if available
    if (error.response && error.response.data) {
        console.error(`Status: ${error.response.status}`);
        console.error(`Headers: ${JSON.stringify(error.response.headers)}`);
        console.error(`Data: ${JSON.stringify(error.response.data)}`);
    } else {
        console.error(error);
    }
  }
}

// --- Execute Sending Function ---
sendSms();

Explanation (index.js):

  1. require('dotenv').config();: Loads environment variables from the .env file into process.env.
  2. Configuration Loading & Validation: Reads the necessary Vonage credentials and numbers from process.env and performs basic checks to ensure they are present and TO_NUMBER is set. It also performs a simple regex check for phone number format. Note that this regex (/^\+?\d{1,15}$/) is a very basic check; it ensures the string contains only digits (optionally preceded by a +) and is within the typical length limits, but it doesn't rigorously enforce the full E.164 standard (e.g., specific country code rules). The error message guides the user towards the correct format.
  3. Vonage Client Initialization: Creates an instance of the Vonage client using the Application ID and Private Key for JWT authentication, which is the preferred method for the Messages API. API Key/Secret are included for potential fallback or use with other Vonage APIs.
  4. vonage.messages.send({...}): This is the core function call.
    • message_type: 'text': Specifies a plain text message.
    • text: The content of the message.
    • to: The recipient's phone number (loaded from the TO_NUMBER constant, with leading + removed for the API call). Crucially, replace the placeholder value.
    • from: Your Vonage virtual number (loaded from .env, with leading + removed).
    • channel: 'sms': Explicitly tells the Messages API to use the SMS channel.
  5. Async/Await & Error Handling: The send function returns a Promise, so we use async/await for cleaner handling. A try...catch block captures potential errors during the API call (e.g., network issues, invalid credentials, insufficient funds) and logs detailed information if available from the error response.
  6. Execution: Calls the sendSms() function to trigger the process.

Part 2: Receiving Messages & Replying (WhatsApp) (server.js)

This script sets up an Express server to listen for incoming webhooks from Vonage for both SMS and WhatsApp messages. It includes JWT verification for security and demonstrates automatically replying to incoming WhatsApp messages.

javascript
// server.js
require('dotenv').config();
const express = require('express');
const { Vonage } = require('@vonage/server-sdk');
const { WhatsAppText } = require('@vonage/messages');
const { verifySignature } = require('@vonage/jwt');

// --- Configuration ---
const PORT = process.env.PORT || 8000;
const VONAGE_API_KEY = process.env.VONAGE_API_KEY;
const VONAGE_API_SECRET = process.env.VONAGE_API_SECRET;
const VONAGE_APPLICATION_ID = process.env.VONAGE_APPLICATION_ID;
const VONAGE_PRIVATE_KEY = process.env.VONAGE_PRIVATE_KEY;
const VONAGE_API_SIGNATURE_SECRET = process.env.VONAGE_API_SIGNATURE_SECRET;
const VONAGE_WHATSAPP_NUMBER = process.env.VONAGE_WHATSAPP_NUMBER; // Sandbox number

// --- Input Validation ---
if (!VONAGE_API_KEY || !VONAGE_API_SECRET || !VONAGE_APPLICATION_ID || !VONAGE_PRIVATE_KEY || !VONAGE_API_SIGNATURE_SECRET || !VONAGE_WHATSAPP_NUMBER) {
  console.error('Error: Missing required Vonage credentials or configuration in .env file for the server.');
  process.exit(1);
}

// --- Initialize Express App ---
const app = express();
// Use express.json() for parsing application/json
app.use(express.json());
// Use express.urlencoded() for parsing application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }));

// --- Initialize Vonage Client ---
// Use Sandbox host for WhatsApp Sandbox testing
const vonage = new Vonage(
  {
    apiKey: VONAGE_API_KEY,
    apiSecret: VONAGE_API_SECRET,
    applicationId: VONAGE_APPLICATION_ID,
    privateKey: VONAGE_PRIVATE_KEY,
  },
  {
    // IMPORTANT: Use the sandbox API host for WhatsApp Sandbox messages
    apiHost: 'https://messages-sandbox.nexmo.com',
  }
);

// --- Security Middleware: Verify Vonage Signature ---
// This function acts as middleware to verify the JWT signature on incoming requests
const verifyVonageSignature = (req, res, next) => {
  try {
    // Extract token from 'Authorization: Bearer <token>' header
    const authHeader = req.headers.authorization;
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
        console.warn('Webhook received without valid Bearer token.');
        return res.status(401).send('Unauthorized: Missing Bearer token');
    }
    const token = authHeader.split(' ')[1];

    // Verify the signature using the secret from .env
    if (!verifySignature(token, VONAGE_API_SIGNATURE_SECRET)) {
      console.error('Webhook received with invalid signature.');
      return res.status(401).send('Unauthorized: Invalid signature');
    }

    console.log('Webhook signature verified successfully.');
    // If signature is valid, proceed to the next middleware/route handler
    next();
  } catch (error) {
    console.error('Error during signature verification:', error);
    res.status(500).send('Internal Server Error during signature verification');
  }
};

// --- Function to Send WhatsApp Reply ---
const sendWhatsAppReply = async (recipientNumber, messageText) => {
  console.log(`Attempting to send WhatsApp reply to ${recipientNumber}...`);
  try {
    const response = await vonage.messages.send(
      new WhatsAppText({
        text: messageText,
        to: recipientNumber, // The number that sent the inbound message
        from: VONAGE_WHATSAPP_NUMBER, // Your Vonage WhatsApp Sandbox number
      })
    );
    console.log(`WhatsApp reply submitted successfully with Message UUID: ${response.messageUuid}`);
  } catch (error) {
    console.error('Error sending WhatsApp reply:');
     if (error.response && error.response.data) {
        console.error(`Status: ${error.response.status}`);
        console.error(`Headers: ${JSON.stringify(error.response.headers)}`);
        console.error(`Data: ${JSON.stringify(error.response.data)}`);
    } else {
        console.error(error);
    }
  }
};

// --- Webhook Endpoints ---

// Endpoint for INBOUND messages (SMS & WhatsApp)
// Apply signature verification middleware FIRST
app.post('/webhooks/inbound', verifyVonageSignature, async (req, res) => {
  console.log('--- Inbound Message Received ---');
  console.log('Timestamp:', new Date().toISOString());
  console.log('Request Body:', JSON.stringify(req.body, null, 2));

  const { channel, from: senderNumberObj, message_type, text } = req.body;
  const senderNumber = senderNumberObj?.number; // Extract number from the 'from' object

  if (!senderNumber) {
      console.error('Could not extract sender number from inbound webhook:', req.body);
      // Still send 200 OK to prevent retries, but log the error
      return res.status(200).send('OK');
  }

  console.log(`Channel: ${channel}, From: ${senderNumber}, Type: ${message_type}`);

  // Handle based on channel
  if (channel === 'whatsapp') {
    console.log('Received a WhatsApp message.');
    // Automatically reply to incoming WhatsApp messages
    const replyText = `Hi there! You sent: "${text}". This is an automated reply from the Vonage Node.js server.`;
    // Ensure the senderNumber is correctly formatted if needed (it should be E.164 from Vonage)
    await sendWhatsAppReply(senderNumber, replyText);

  } else if (channel === 'sms') {
    console.log('Received an SMS message.');
    // Add specific SMS handling logic here if needed (e.g., store in DB, trigger workflow)
    // For this example, we just log it.

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

  // IMPORTANT: Always respond with 200 OK to acknowledge receipt
  // Otherwise Vonage will retry sending the webhook
  res.status(200).send('OK');
});


// Endpoint for message STATUS updates (Delivery Receipts etc.)
// Apply signature verification middleware FIRST
app.post('/webhooks/status', verifyVonageSignature, (req, res) => {
  console.log('--- Status Update Received ---');
  console.log('Timestamp:', new Date().toISOString());
  console.log('Request Body:', JSON.stringify(req.body, null, 2));

  // Process the status update (e.g., update message status in your database)
  // Example statuses: submitted, delivered, rejected, undeliverable

  // Acknowledge receipt
  res.status(200).send('OK');
});

// --- Basic Health Check Endpoint ---
app.get('/health', (req, res) => {
    res.status(200).send('Server is healthy');
});


// --- Start Server ---
app.listen(PORT, () => {
  console.log(`Server listening on port ${PORT}`);
  console.log(`ngrok should be forwarding to http://localhost:${PORT}`);
  console.log('Ensure your Vonage Application and Sandbox webhooks point to your ngrok URL + /webhooks/inbound and /webhooks/status');
});

Explanation (server.js):

  1. Dependencies & Config: Loads dotenv, express, Vonage SDK components, and configuration variables. Includes validation.
  2. Express Setup: Initializes the Express app and configures middleware (express.json(), express.urlencoded()) to parse incoming request bodies.
  3. Vonage Client Initialization: Creates the Vonage client instance. Crucially, it specifies { apiHost: 'https://messages-sandbox.nexmo.com' } in the options. This directs API calls (specifically the sendWhatsAppReply) to the correct Sandbox endpoint. Important Limitation: Because this apiHost is hardcoded, this specific vonage client instance cannot be used to send production WhatsApp messages (which use the standard messages.nexmo.com host) or potentially SMS messages directly from the server without modification. For a production application sending multiple message types from the server, you might need separate client instances or logic to determine the correct apiHost based on the message type or environment.
  4. verifyVonageSignature Middleware:
    • This function is designed to run before the main route handlers for /webhooks/inbound and /webhooks/status.
    • It extracts the JWT token from the Authorization: Bearer <token> header sent by Vonage.
    • It uses verifySignature(token, VONAGE_API_SIGNATURE_SECRET) from @vonage/jwt to check if the token's signature is valid using the secret stored in .env.
    • If the signature is invalid or the header is missing/malformed, it sends a 401 Unauthorized response and stops processing.
    • If valid, it calls next() to pass control to the actual webhook handler (app.post(...)). This is a critical security step to ensure webhooks genuinely originate from Vonage.
  5. sendWhatsAppReply Function:
    • An async function dedicated to sending replies via WhatsApp.
    • Uses vonage.messages.send() but wraps the message details in new WhatsAppText({...}) provided by @vonage/messages for convenience.
    • Sends the reply from the Sandbox number to the original sender (recipientNumber).
    • Includes error handling specific to sending the reply.
  6. /webhooks/inbound Endpoint:
    • Handles POST requests to this path (where Vonage sends incoming messages).
    • Crucially applies the verifyVonageSignature middleware first.
    • Logs the entire request body for debugging.
    • Extracts key information: channel, from (sender number is extracted from the from object, e.g., req.body.from.number), message_type, text. Includes a basic check to ensure the sender number was extracted.
    • Includes logic to differentiate between whatsapp and sms channels.
    • If it's a WhatsApp message, it calls sendWhatsAppReply to send an automated response.
    • If it's SMS, it just logs it (you would add custom logic here).
    • Responds with res.status(200).send('OK');. This is vital. Vonage expects a 2xx response to confirm receipt; otherwise, it will retry sending the webhook, potentially causing duplicate processing.
  7. /webhooks/status Endpoint:
    • Handles POST requests for status updates (e.g., delivery confirmations).
    • Also applies the verifyVonageSignature middleware.
    • Logs the status update body. In a real application, you'd parse this to update the status of sent messages in your database.
    • Responds with 200 OK.
  8. /health Endpoint: A simple GET endpoint useful for basic monitoring or load balancers to check if the server is running.
  9. app.listen: Starts the Express server, making it listen for connections on the configured PORT.

4. Testing SMS and WhatsApp Integration with Webhooks

Let's test both sending and receiving capabilities.

Ensure ngrok is running and your Vonage webhook URLs are correctly updated.

Test 1: Sending an Outbound SMS

  1. Edit index.js: Open index.js and replace 'REPLACE_WITH_RECIPIENT_PHONE_NUMBER' with your actual mobile phone number in E.164 format (e.g., 14155550100 or +14155550100). Save the file.

  2. Run the Script: In your terminal (the one not running ngrok), execute:

    bash
    node index.js
  3. Check Output: You should see console output indicating the SMS was submitted successfully, including a Message UUID.

  4. Check Your Phone: You should receive the SMS message ("Hello from Vonage and Node.js!") on the phone number you specified. Delivery might take a few seconds.

Test 2: Receiving Messages and WhatsApp Reply

  1. Start the Server: In your terminal (the one not running ngrok, if you stopped it after sending SMS), start the Express server:

    bash
    node server.js
  2. Check Server Logs: You should see "Server listening on port 8000...".

  3. Receive an SMS:

    • From your mobile phone, send an SMS message to the Vonage virtual number you linked to your application (VONAGE_SMS_FROM_NUMBER in .env).
    • Observe Server Logs: You should see the "--- Inbound Message Received ---" log, detailing the SMS message (channel: 'sms'), your phone number, and the message text. The signature verification log should also appear.
    • Observe ngrok Console: You should see POST /webhooks/inbound 200 OK requests in the ngrok terminal window.
  4. Receive a WhatsApp Message & Trigger Reply:

    • Ensure you have already joined the Vonage Sandbox from your WhatsApp number (Step 4 in Vonage Configuration).
    • From your WhatsApp-enabled phone, send any message to the Vonage Sandbox WhatsApp number (+14157386102).
    • Observe Server Logs:
      • You'll see the "--- Inbound Message Received ---" log (channel: 'whatsapp').
      • You'll see the "Attempting to send WhatsApp reply..." log.
      • You'll see the "WhatsApp reply submitted successfully..." log.
    • Check Your Phone (WhatsApp): You should receive an automated reply from the Sandbox number like: "Hi there! You sent: "[Your Message]". This is an automated reply...".
    • Observe ngrok Console: You should see POST /webhooks/inbound 200 OK requests.
  5. Check Status Webhooks (Optional but Recommended):

    • After sending the SMS (index.js) and the WhatsApp reply (server.js), you should eventually see "--- Status Update Received ---" logs in your server.js console as Vonage sends delivery confirmations (or failures) to the /webhooks/status endpoint. This might take a few seconds to minutes depending on the network.

5. Error Handling and Logging Best Practices

Our current implementation includes basic error handling and logging:

  • API Call Errors (index.js, server.js): try...catch blocks wrap vonage.messages.send. Errors are logged to the console, including details from the error response if available (error.response.data).
  • Configuration Errors (index.js, server.js): Basic checks for missing environment variables prevent the scripts from running without necessary config, exiting gracefully (process.exit(1)).
  • Webhook Signature Errors (server.js): The verifyVonageSignature middleware explicitly handles invalid or missing signatures, logging warnings/errors and returning 401 Unauthorized.
  • Logging (server.js): console.log is used to track incoming messages, status updates, signature verification results, and reply attempts. Timestamps are added for context.
  • Webhook Acknowledgement (server.js): Sending res.status(200).send('OK') prevents Vonage's retry mechanism from flooding the server if processing takes time or fails silently.

Production Considerations:

While the current setup is functional for demonstration, a production environment requires more robustness:

  • Structured Logging: Use a dedicated logging library (e.g., Winston, Pino) for structured logging (JSON format), different log levels (info, warn, error), and configurable outputs (console, file, external services).
  • Centralized Logging: Ship logs to a centralized logging platform (e.g., Datadog, ELK stack, Logtail) for easier searching, analysis, and alerting.
  • Error Tracking: Integrate an error tracking service (e.g., Sentry, Bugsnag) to capture, aggregate, and alert on unhandled exceptions.
  • More Granular Error Handling: Implement more specific error handling based on Vonage API error codes or types (e.g., differentiate between invalid number format, insufficient funds, throttling).
  • Retry Mechanisms (for Sending): For critical outbound messages, implement a retry strategy (e.g., using libraries like async-retry) with exponential backoff if the initial vonage.messages.send call fails due to temporary network issues or API unavailability. Be cautious not to retry errors that are clearly permanent (like invalid numbers).

6. Security Features for Production Deployment

Security is paramount, especially when handling communication and API keys.

  • Environment Variables (.env): Keeping API keys, secrets, and application IDs out of source code (and using .gitignore to exclude .env) is the most fundamental security practice.
  • Webhook Signature Verification (server.js): The verifyVonageSignature middleware is crucial. It ensures that incoming requests to your webhook endpoints genuinely originate from Vonage and haven't been tampered with or spoofed. Never disable this in production.
  • HTTPS for Webhooks: Using ngrok provides an HTTPS URL during development. In production, ensure your webhook endpoint is served over HTTPS (using a reverse proxy like Nginx or Caddy with SSL certificates, or platform-provided HTTPS like Heroku, Vercel, AWS Load Balancer). This encrypts the data in transit between Vonage and your server.
  • Input Validation: While basic checks are included (e.g., for TO_NUMBER format), robust applications should validate all inputs more thoroughly, especially data coming from external sources (like webhook payloads or user input used in replies) to prevent injection attacks or unexpected behavior.
  • Rate Limiting: Consider implementing rate limiting on your webhook endpoints (using middleware like express-rate-limit) to prevent abuse or denial-of-service attacks.
  • Least Privilege: Ensure the Vonage API key/secret or Application ID/Private Key used have only the necessary permissions required for the application's functionality.

Frequently Asked Questions

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

Use the Vonage Messages API with the Node.js SDK. After setting up a Vonage application and linking a number, initialize the Vonage client in your Node.js script. Then use `vonage.messages.send()` with the recipient's number, your Vonage number, and the message text. Ensure your API key and secret are stored securely in environment variables.

What is the Vonage Messages API?

The Vonage Messages API is a unified platform for sending and receiving messages across multiple channels like SMS, WhatsApp, MMS, and more. It simplifies multi-channel messaging integration within applications and allows developers to manage all channels through a single interface.

Why does Vonage use webhooks for inbound messages?

Vonage uses webhooks to deliver inbound messages to your application in real-time. When a user sends a message to your Vonage number, Vonage forwards it to a pre-configured URL on your server via an HTTP POST request, ensuring your application receives messages instantly without constantly polling.

When should I use the Vonage WhatsApp Sandbox?

Use the Vonage WhatsApp Sandbox for testing and development of your WhatsApp integrations before going live. It allows you to test sending and receiving WhatsApp messages without needing a full WhatsApp Business Account initially, simply by joining the Sandbox and allowlisting your testing number.

Can I send WhatsApp messages from my Node.js app?

Yes, you can send WhatsApp messages using Node.js, Express, and the Vonage Messages API. The Vonage Node.js SDK simplifies interaction with the API, and you can use `new WhatsAppText({...})` with `vonage.messages.send()` within your Express server. Ensure you point to the Sandbox API host (`https://messages-sandbox.nexmo.com`) when initializing the Vonage client specifically for sending Sandbox messages.

How to receive SMS messages in Node.js with Vonage?

Set up a webhook URL in your Vonage application settings and configure your Express server to listen for POST requests at that endpoint. Use `express.json()` middleware to parse incoming data. Vonage will send an HTTP POST request to your webhook URL whenever an SMS is received by your Vonage number.

How to secure Vonage webhooks in Node.js?

Secure your Vonage webhooks using JWT signature verification. The Vonage Node.js SDK provides `verifySignature` to verify the signature of incoming webhook requests. Use a middleware function in Express to verify signatures, ensuring all webhook requests are authentic and originate from Vonage, blocking unauthorized access to sensitive data.

What is ngrok used for with Vonage webhooks?

ngrok creates a secure tunnel that exposes your locally running Express server to the internet. This allows Vonage to deliver webhooks to your development environment during testing. ngrok provides an HTTPS URL that you configure as your webhook endpoint in your Vonage application settings.

How to set up Vonage API credentials in Node.js?

Store your Vonage API Key, API Secret, Application ID, Private Key, and Signature Secret in a `.env` file. Use the `dotenv` package in Node.js to load these environment variables into `process.env`. Never commit the `.env` file to version control.

How to reply to WhatsApp messages automatically with Node.js?

Within your inbound webhook handler, use `vonage.messages.send()` combined with `new WhatsAppText({...})` to send a reply. Extract the sender's number and craft the reply message dynamically. Set the 'from' number to your Vonage Sandbox number. This allows your application to engage in two-way conversations with users.

What is a Vonage Application ID?

A Vonage Application ID is a unique identifier assigned to each application you create within the Vonage platform. It's essential for authentication and associating your application with Vonage services, including configuring webhooks and managing API access.

What are the prerequisites for using the Vonage Messages API?

You need a Vonage API account, Node.js and npm installed, ngrok for local testing, and a Vonage virtual number for sending SMS. An ngrok account is needed to expose your localhost during development. For WhatsApp, you need to enable the WhatsApp Sandbox and join it with your WhatsApp-enabled phone number.