code examples
code examples
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:
- Sending outbound SMS messages programmatically
- Receiving inbound SMS messages via webhooks
- Sending outbound WhatsApp messages using the Vonage WhatsApp Sandbox
- 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
.envfile.
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
POSTrequest. - 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:
index.js: A standalone script to send an outbound SMS message.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.
mkdir vonage-node-messaging
cd vonage-node-messaging2. Initialize Node.js Project:
Initialize the project using npm, which creates a package.json file.
npm init -yThe -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 (likeWhatsAppText).@vonage/jwt: For verifying webhook signatures (important for security).dotenv: To manage environment variables securely.
Install them using npm:
npm install express @vonage/server-sdk @vonage/messages @vonage/jwt dotenv4. Create Project Structure:
For simplicity, we'll keep our code in the root directory for this example. Create the necessary files:
touch index.js server.js .env .gitignoreindex.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.
# .gitignore
node_modules/
.env6. Set up Environment Variables (.env):
Open the .env file and add the following placeholders. We will fill these in during the Vonage configuration steps.
# .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 onExplanation 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 keyandAPI secretare displayed prominently on the homepage. - Copy these values and paste them into your
.envfile forVONAGE_API_KEYandVONAGE_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.keyfile. Save this file in the root of your project directory (vonage-node-messaging). The public key is stored by Vonage. Update theVONAGE_PRIVATE_KEYpath in your.envfile if you save it elsewhere (the default./private.keyassumes 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
- Inbound URL:
- Click "Generate new application".
- On the next page, you'll see your Application ID. Copy this ID and paste it into your
.envfile forVONAGE_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.envfile forVONAGE_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.envfile forVONAGE_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
- Inbound URL:
- 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
.envfile forVONAGE_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
8000in our.env).
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
- Inbound URL:
- 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
- Inbound URL:
- 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.
// 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):
require('dotenv').config();: Loads environment variables from the.envfile intoprocess.env.- Configuration Loading & Validation: Reads the necessary Vonage credentials and numbers from
process.envand performs basic checks to ensure they are present andTO_NUMBERis 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. - 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.
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 theTO_NUMBERconstant, 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.
- Async/Await & Error Handling: The
sendfunction returns a Promise, so we useasync/awaitfor cleaner handling. Atry...catchblock 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. - 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.
// 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):
- Dependencies & Config: Loads
dotenv,express, Vonage SDK components, and configuration variables. Includes validation. - Express Setup: Initializes the Express app and configures middleware (
express.json(),express.urlencoded()) to parse incoming request bodies. - 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 thesendWhatsAppReply) to the correct Sandbox endpoint. Important Limitation: Because thisapiHostis hardcoded, this specificvonageclient instance cannot be used to send production WhatsApp messages (which use the standardmessages.nexmo.comhost) 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 correctapiHostbased on the message type or environment. verifyVonageSignatureMiddleware:- This function is designed to run before the main route handlers for
/webhooks/inboundand/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/jwtto 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 Unauthorizedresponse 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.
- This function is designed to run before the main route handlers for
sendWhatsAppReplyFunction:- An
asyncfunction dedicated to sending replies via WhatsApp. - Uses
vonage.messages.send()but wraps the message details innew WhatsAppText({...})provided by@vonage/messagesfor convenience. - Sends the reply
fromthe Sandbox numbertothe original sender (recipientNumber). - Includes error handling specific to sending the reply.
- An
/webhooks/inboundEndpoint:- Handles
POSTrequests to this path (where Vonage sends incoming messages). - Crucially applies the
verifyVonageSignaturemiddleware first. - Logs the entire request body for debugging.
- Extracts key information:
channel,from(sender number is extracted from thefromobject, 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
whatsappandsmschannels. - If it's a WhatsApp message, it calls
sendWhatsAppReplyto 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.
- Handles
/webhooks/statusEndpoint:- Handles
POSTrequests for status updates (e.g., delivery confirmations). - Also applies the
verifyVonageSignaturemiddleware. - 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.
- Handles
/healthEndpoint: A simpleGETendpoint useful for basic monitoring or load balancers to check if the server is running.app.listen: Starts the Express server, making it listen for connections on the configuredPORT.
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
-
Edit
index.js: Openindex.jsand replace'REPLACE_WITH_RECIPIENT_PHONE_NUMBER'with your actual mobile phone number in E.164 format (e.g.,14155550100or+14155550100). Save the file. -
Run the Script: In your terminal (the one not running ngrok), execute:
bashnode index.js -
Check Output: You should see console output indicating the SMS was submitted successfully, including a
Message UUID. -
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
-
Start the Server: In your terminal (the one not running ngrok, if you stopped it after sending SMS), start the Express server:
bashnode server.js -
Check Server Logs: You should see "Server listening on port 8000...".
-
Receive an SMS:
- From your mobile phone, send an SMS message to the Vonage virtual number you linked to your application (
VONAGE_SMS_FROM_NUMBERin.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 OKrequests in the ngrok terminal window.
- From your mobile phone, send an SMS message to the Vonage virtual number you linked to your application (
-
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.
- You'll see the "--- Inbound Message Received ---" 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 OKrequests.
-
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 yourserver.jsconsole as Vonage sends delivery confirmations (or failures) to the/webhooks/statusendpoint. This might take a few seconds to minutes depending on the network.
- After sending the SMS (
5. Error Handling and Logging Best Practices
Our current implementation includes basic error handling and logging:
- API Call Errors (
index.js,server.js):try...catchblocks wrapvonage.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): TheverifyVonageSignaturemiddleware explicitly handles invalid or missing signatures, logging warnings/errors and returning401 Unauthorized. - Logging (
server.js):console.logis used to track incoming messages, status updates, signature verification results, and reply attempts. Timestamps are added for context. - Webhook Acknowledgement (
server.js): Sendingres.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 initialvonage.messages.sendcall 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.gitignoreto exclude.env) is the most fundamental security practice. - Webhook Signature Verification (
server.js): TheverifyVonageSignaturemiddleware 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
ngrokprovides 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_NUMBERformat), 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.