code examples
code examples
Implementing SMS Delivery Status Webhooks with Node.js
A guide on setting up a Node.js/Express application using the Vonage Messages API to receive and process SMS delivery status updates via webhooks.
Tracking the delivery status of your SMS messages is crucial for building reliable communication workflows. Knowing whether a message reached the recipient's handset – or why it failed – enables you to build smarter retry logic, provide accurate user feedback, and gain valuable insights into message deliverability.
This guide provides a step-by-step walkthrough for building a Node.js application using the Express framework to receive and process SMS delivery status updates from the Vonage Messages API via webhooks. We will cover project setup, Vonage configuration, implementing the webhook handler, sending test messages, handling errors, and preparing for production deployment.
Project Goal: To create a robust Node.js service that can:
- Send SMS messages using the Vonage Messages API.
- Receive real-time delivery status updates for those messages via a secure webhook endpoint.
- Log or store these status updates for analysis or further action.
Technologies Used:
- Node.js: A JavaScript runtime environment for building server-side applications.
- Express: A minimal and flexible Node.js web application framework used to create the webhook endpoint.
- Vonage Messages API: A multi-channel API for sending and receiving messages, including SMS. We'll use it for sending SMS and receiving status updates.
@vonage/server-sdk: The official Vonage Node.js SDK for interacting with the API.- ngrok: A tool to expose local servers to the internet, essential for testing webhooks during development.
dotenv: A module to load environment variables from a.envfile.jsonwebtoken: (Optional, for Security) A library to verify JWT signatures on incoming webhooks.
Prerequisites:
- A Vonage API account. Sign up here if you don't have one.
- Node.js and npm (or yarn) installed locally.
- A publicly accessible Vonage virtual phone number capable of sending SMS. You can purchase one from the Vonage API Dashboard.
- ngrok installed and authenticated. Download it here.
System Architecture
The flow involves two main interactions with the Vonage platform:
- Sending SMS: Your Node.js application uses the Vonage SDK to make an API request to Vonage, instructing it to send an SMS from your Vonage number to the recipient.
- Receiving Status Updates: When the status of the sent SMS changes (e.g., submitted, delivered, failed), the Vonage platform sends an HTTP POST request containing the status details to a pre-configured
Status URL(your webhook endpoint). Your Express application listens at this URL, receives the data, and processes it.
1. Setting Up the Project
Let's create the project structure and install the necessary dependencies.
-
Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.
bashmkdir vonage-sms-status-app cd vonage-sms-status-app -
Initialize Node.js Project: Initialize the project using npm or yarn. This creates a
package.jsonfile.bashnpm init -y # or # yarn init -y -
Install Dependencies: Install Express for the web server, the Vonage SDK, and
dotenvfor managing environment variables.bashnpm install express @vonage/server-sdk dotenv # or # yarn add express @vonage/server-sdk dotenv -
Create Project Structure: Set up a basic source directory and necessary files.
bashmkdir src touch src/index.js touch .env touch .gitignore -
Configure
.gitignore: Prevent sensitive files and build artifacts from being committed to version control. Add the following to your.gitignorefile:text# .gitignore node_modules .env private.key npm-debug.log* yarn-debug.log* yarn-error.log* -
Set Up Environment Variables (
.env): Create a.envfile in the project root to store your Vonage credentials and configuration. Never commit this file to Git.dotenv# .env # Vonage API Credentials (Found in Vonage Dashboard > API Settings) # Note: API Key/Secret might be used for other Vonage APIs, but Messages API primarily uses App ID + Private Key VONAGE_API_KEY=YOUR_VONAGE_API_KEY VONAGE_API_SECRET=YOUR_VONAGE_API_SECRET # Vonage Application Credentials (Created in Vonage Dashboard > Your Applications) VONAGE_APPLICATION_ID=YOUR_VONAGE_APPLICATION_ID # IMPORTANT: Provide the *path* to your downloaded private key file. # This path is relative to the current working directory where the Node process starts. VONAGE_PRIVATE_KEY_PATH=./private.key # Your Vonage Virtual Number (Must be linked to the Application ID) VONAGE_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER # Server Port PORT=3000 # Optional: For JWT Signature Verification (Store securely, NOT directly in .env for production) # Example structure - store the actual key securely, e.g., in secrets manager or env var # VONAGE_PUBLIC_KEY_STRING=""-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----""VONAGE_API_KEY,VONAGE_API_SECRET: Found at the top of your Vonage API Dashboard. While present, the Messages API primarily uses JWT authentication (Application ID + Private Key).VONAGE_APPLICATION_ID: Obtained after creating a Vonage Application (see next section).VONAGE_PRIVATE_KEY_PATH: The file path to theprivate.keyfile downloaded when creating the Vonage Application. Place this file in your project root or specify the correct path relative to where you run thenodecommand. Ensure this file is readable by the Node.js process.VONAGE_NUMBER: Your purchased Vonage virtual number, formatted with the country code (e.g.,14155550100).PORT: The port your Express server will listen on (defaulting to 3000).
2. Configuring Vonage
To send messages and receive status updates using the Messages API, you need to create a Vonage Application and configure it correctly.
-
Navigate to Vonage Applications: Log in to the Vonage API Dashboard and navigate to ""Your applications"" > ""Create a new application"".
-
Create the Application:
- Name: Give your application a descriptive name (e.g., ""SMS Status Webhook App"").
- Generate Public and Private Key: Click this button. Your browser will download a
private.keyfile. Save this file securely in your project directory (or another location referenced viaVONAGE_PRIVATE_KEY_PATHin your.envfile). Make sure this file is included in your.gitignore. Vonage stores the public key associated with this application. - Application ID: Note the generated Application ID. Add it to your
.envfile asVONAGE_APPLICATION_ID.
-
Enable Capabilities:
- Scroll down to the ""Capabilities"" section.
- Toggle Messages to enable it. This reveals fields for Inbound and Status URLs.
-
Configure Webhook URLs:
- Status URL: This is where Vonage will send delivery status updates. For now, enter a placeholder like
https://example.com/webhooks/status. We will update this later with our ngrok URL during testing. Ensure the method is set to POST. - Inbound URL: If you also wanted to receive SMS messages (not just status updates), you would configure this URL. We can leave it blank for this guide or use a placeholder like
https://example.com/webhooks/inbound. Ensure the method is set to POST.
- Status URL: This is where Vonage will send delivery status updates. For now, enter a placeholder like
-
Link Your Vonage Number:
- Scroll down to ""Link virtual numbers"".
- Find the Vonage number you want to use for sending SMS (the one specified in your
.envfile) and click ""Link"".
-
Save Changes: Click ""Save changes"" at the bottom of the page.
-
(Optional but Recommended) Ensure Messages API is Default: While the application settings usually suffice, you can double-check your account-level SMS settings. Go to ""Account"" > ""Settings"". Scroll to ""API settings"" > ""SMS settings"". Ensure the default API for sending SMS is set to ""Messages API"". This avoids potential conflicts if you previously used the older SMS API settings.
3. Implementing the Express Server (Webhook Handler)
Now, let's write the code for the Express server that will listen for incoming status webhooks from Vonage.
Edit src/index.js:
// src/index.js
require('dotenv').config(); // Load environment variables from .env file (searches relative to CWD)
const express = require('express');
const { Vonage } = require('@vonage/server-sdk');
const fs = require('fs'); // Needed to read the private key file
// --- Basic Server Setup ---
const app = express();
// Vonage sends webhooks with application/json content type
app.use(express.json());
// Optional: If you need to handle URL-encoded data (not typical for Vonage webhooks)
app.use(express.urlencoded({ extended: true }));
const port = process.env.PORT || 3000;
// --- Vonage Client Initialization ---
// Read the private key from the file path specified in .env
// Ensure the path is correct relative to where the node process starts.
let privateKey;
try {
privateKey = fs.readFileSync(process.env.VONAGE_PRIVATE_KEY_PATH);
} catch (err) {
console.error(""Error reading private key file:"", err);
process.exit(1); // Exit if key is essential and missing
}
// Initialize the Vonage SDK for the Messages API using Application ID and Private Key (JWT Auth)
// Note: Other Vonage APIs might use API Key/Secret authentication instead.
const vonage = new Vonage({
apiKey: process.env.VONAGE_API_KEY, // Optional for Messages API, but good practice to include
apiSecret: process.env.VONAGE_API_SECRET, // Optional for Messages API
applicationId: process.env.VONAGE_APPLICATION_ID,
privateKey: privateKey // Use the key content read from the file
});
// --- Webhook Endpoint for Status Updates ---
// This path MUST match the path configured in the Vonage Application's Status URL
app.post('/webhooks/status', (req, res) => {
const statusData = req.body;
console.log('--- Vonage Status Webhook Received ---');
console.log('Timestamp:', statusData.timestamp);
console.log('Message UUID:', statusData.message_uuid);
console.log('Status:', statusData.status);
console.log('To:', statusData.to);
console.log('From:', statusData.from);
if (statusData.error) {
console.error('Error Code:', statusData.error.code);
console.error('Error Reason:', statusData.error.reason);
}
// Store or process the status update here (e.g., update a database)
// For this example, we just log it.
// See Section 7 for persistence ideas.
// IMPORTANT: Respond to Vonage with a 200 OK status code
// This acknowledges receipt of the webhook. Failure to do so
// will cause Vonage to retry sending the webhook.
res.status(200).send('OK');
// Alternatively: res.sendStatus(200);
});
// --- Basic Health Check Endpoint ---
app.get('/_health', (req, res) => {
res.status(200).json({ status: 'ok', timestamp: new Date().toISOString() });
});
// --- Start the Server ---
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
console.log(`Webhook endpoint available at /webhooks/status (POST)`);
});Explanation:
require('dotenv').config(): Loads variables from your.envfile intoprocess.env. It looks for.envstarting from the current working directory (CWD) of the Node.js process.express(): Creates an Express application instance.app.use(express.json()): Adds middleware to parse incoming requests with JSON payloads (which Vonage uses for webhooks).- Reading
privateKey: We usefs.readFileSyncto read the private key content from the path specified inVONAGE_PRIVATE_KEY_PATH. Error handling is added in case the file doesn't exist or isn't readable. vonage = new Vonage(...): Initializes the Vonage SDK client. For the Messages API,applicationIdand the content of theprivateKeyare essential for JWT authentication. We pass the actual key content, not the file path, to the SDK.app.post('/webhooks/status', ...): Defines the route handler for POST requests to/webhooks/status.- It logs the key fields from the incoming
req.body(the webhook payload). Common fields includemessage_uuid,status(submitted,delivered,rejected,undeliverable,failed),timestamp,to,from, and an optionalerrorobject if the status isfailedorrejected. - Crucially, it sends back a
200 OKstatus usingres.status(200).send('OK');. This tells Vonage you've successfully received the webhook. Without this, Vonage will retry sending the webhook according to its retry schedule, potentially causing duplicate processing.
- It logs the key fields from the incoming
app.get('/_health', ...): A simple endpoint to check if the server is running.app.listen(...): Starts the Express server on the specified port.
4. Sending an SMS to Trigger Status Updates
To test our webhook, we need to send an SMS message using the Vonage Messages API. The status updates for this message will then be sent to our webhook.
You can add a simple function to src/index.js or create a separate script. Let's create a separate script for clarity.
Create src/send-test-sms.js:
// src/send-test-sms.js
require('dotenv').config();
const { Vonage } = require('@vonage/server-sdk');
const fs = require('fs');
// Read the private key content
let privateKey;
try {
privateKey = fs.readFileSync(process.env.VONAGE_PRIVATE_KEY_PATH);
} catch (err) {
console.error(""Error reading private key file:"", err);
process.exit(1);
}
// Initialize Vonage SDK (same as in index.js)
const vonage = new Vonage({
apiKey: process.env.VONAGE_API_KEY,
apiSecret: process.env.VONAGE_API_SECRET,
applicationId: process.env.VONAGE_APPLICATION_ID,
privateKey: privateKey
});
// Replace with a valid recipient phone number
const RECIPIENT_NUMBER = ""REPLACE_WITH_RECIPIENT_PHONE_NUMBER""; // e.g., 14155550101
async function sendSms() {
const fromNumber = process.env.VONAGE_NUMBER;
const toNumber = RECIPIENT_NUMBER;
const text = `Hello from Vonage! Testing status webhook. [${new Date().toLocaleTimeString()}]`;
if (!toNumber || toNumber === ""REPLACE_WITH_RECIPIENT_PHONE_NUMBER"") { // Basic check
console.error('Error: Please replace RECIPIENT_NUMBER in src/send-test-sms.js');
process.exit(1);
}
if (!fromNumber) {
console.error('Error: VONAGE_NUMBER not found in .env file.');
process.exit(1);
}
console.log(`Attempting to send SMS from ${fromNumber} to ${toNumber}`);
try {
const resp = await vonage.messages.send({
message_type: ""text"",
text: text,
to: toNumber,
from: fromNumber,
channel: ""sms""
});
console.log('SMS Submitted Successfully!');
console.log('Message UUID:', resp.message_uuid);
} catch (err) {
console.error('Error sending SMS:');
// Log specific Vonage error details if available
if (err.response && err.response.data) {
console.error(JSON.stringify(err.response.data, null, 2));
} else {
console.error(err);
}
}
}
sendSms();Explanation:
- Initializes the Vonage client similarly to
index.js, including reading the private key file content. - Defines the
RECIPIENT_NUMBER. Remember to replace the placeholder with a real phone number you can check. - Uses
vonage.messages.send()to send the SMS.message_type: ""text"": Specifies a plain text message.text: The content of the SMS.to: The recipient's phone number.from: Your Vonage virtual number (from.env).channel: ""sms"": Specifies the SMS channel.
- Logs the
message_uuidupon successful submission or logs the error details if the API call fails.
Before running: Edit src/send-test-sms.js and replace ""REPLACE_WITH_RECIPIENT_PHONE_NUMBER"" with a valid E.164 formatted phone number.
5. Running Locally with ngrok
To allow Vonage's servers to reach your local development machine, you need to use ngrok.
-
Start ngrok: Open a new terminal window (keep the one for the server running separately). Run
ngrok, telling it to forward traffic to the port your Express app is listening on (e.g., 3000).bashngrok http 3000 -
Copy the ngrok URL: ngrok will display output similar to this:
ngrok by @inconshreveable (Ctrl+C to quit) Session Status online Account Your Name (Plan: Free) Version x.x.x Region United States (us-cal-1) Web Interface http://127.0.0.1:4040 Forwarding http://xxxxxxxx.ngrok.io -> http://localhost:3000 Forwarding https://xxxxxxxx.ngrok.io -> http://localhost:3000 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00Copy the
httpsForwarding URL (e.g.,https://xxxxxxxx.ngrok.io). Using HTTPS is strongly recommended. -
Update Vonage Status URL:
- Go back to your application settings in the Vonage API Dashboard (""Your applications"" > Select your app).
- Scroll to ""Capabilities"" > ""Messages"".
- Paste the copied
httpsngrok URL into the Status URL field, appending your webhook path:https://xxxxxxxx.ngrok.io/webhooks/status. - Ensure the method is POST.
- Click Save changes.
Note: ngrok provides a temporary public URL suitable for development. For production, you will need to deploy your application to a server with a permanent public IP address or domain name and configure a valid SSL/TLS certificate. See Section 10.
6. Verification and Testing
Now, let's test the end-to-end flow.
-
Start the Express Server: In your first terminal window, run:
bashnode src/index.jsYou should see
Server listening at http://localhost:3000. -
Send a Test SMS: In another terminal window (not the ngrok one), run the sending script:
bashnode src/send-test-sms.jsYou should see ""SMS Submitted Successfully!"" and a
message_uuid. -
Observe Webhook Receipt:
- Watch the terminal where your Express server (
src/index.js) is running. Within a few seconds to minutes (depending on carrier networks), you should start seeing logs like ""--- Vonage Status Webhook Received ---"" followed by the status details (submitted, then potentiallydeliveredorfailed). - Check the recipient phone number – it should receive the SMS message.
- Watch the terminal where your Express server (
-
Inspect with ngrok (Optional): Open the ngrok Web Interface URL (usually
http://127.0.0.1:4040) in your browser. You can inspect the incoming POST requests to/webhooks/status, view headers, the request body (payload), and your server's response (200 OK). This is invaluable for debugging.
Manual Verification Checklist:
- Express server starts without errors (especially private key reading).
-
send-test-sms.jsexecutes and logs amessage_uuid. - Recipient phone receives the SMS message.
- Express server logs show incoming requests to
/webhooks/status. - Logged status progresses (e.g., from
submittedtodelivered). - ngrok web interface shows POST requests to
/webhooks/statusreceiving a200 OKresponse.
7. Enhancements: Persistence and Data Storage
Logging status updates to the console is fine for testing, but in a production scenario, you'll want to store this information persistently, typically in a database.
Conceptual Database Schema:
A simple table to store message statuses might look like this (using PostgreSQL syntax):
CREATE TABLE message_statuses (
message_uuid VARCHAR(36) PRIMARY KEY, -- Vonage Message UUID
status VARCHAR(50) NOT NULL, -- e.g., 'submitted', 'delivered', 'failed'
recipient_number VARCHAR(20), -- To number
sender_number VARCHAR(20), -- From number (Your Vonage number)
-- Timestamp from Vonage webhook. Using TIMESTAMPTZ stores the timestamp
-- along with time zone information (typically converting to UTC for storage),
-- which is crucial for accurately recording event times from external systems
-- like Vonage, regardless of server or client time zones.
status_timestamp TIMESTAMPTZ NOT NULL,
error_code VARCHAR(50), -- Error code if status is 'failed'/'rejected'
error_reason TEXT, -- Error reason text
created_at TIMESTAMPTZ DEFAULT NOW(), -- When the record was first created (optional)
updated_at TIMESTAMPTZ DEFAULT NOW() -- When the record was last updated
);Implementation Steps (Conceptual):
- Choose a Database: Select a database (e.g., PostgreSQL, MySQL, MongoDB).
- Install Database Driver/ORM: Add the appropriate Node.js driver or ORM (e.g.,
pgfor PostgreSQL,mysql2for MySQL,mongoosefor MongoDB, or a higher-level ORM like Prisma or Sequelize).bash# Example for PostgreSQL with Prisma npm install @prisma/client npm install prisma --save-dev npx prisma init --datasource-provider postgresql # Define schema in prisma/schema.prisma based on the SQL above # npx prisma migrate dev --name init - Update Webhook Handler: Modify the
/webhooks/statushandler insrc/index.jsto:- Initialize your database client/ORM.
- Inside the handler, use the incoming
statusData(especiallymessage_uuid) to find or create a record in your database table (anUPSERToperation is often ideal). - Update the record with the latest
status,status_timestamp, and anyerrordetails. Convert the ISO 8601 timestamp string from Vonage into a Date object for storage. - Implement appropriate error handling for database operations. Crucially, still ensure a
200 OKis sent back to Vonage even if your database update fails, but log the database error internally for investigation. You might implement a separate retry mechanism for failed DB writes later.
// src/index.js - Conceptual Database Update in Webhook
// --- Assume Prisma Client is initialized as 'prisma' ---
// const { PrismaClient } = require('@prisma/client');
// const prisma = new PrismaClient();
app.post('/webhooks/status', async (req, res) => { // Make handler async
const statusData = req.body;
console.log('--- Vonage Status Webhook Received ---');
console.log(JSON.stringify(statusData, null, 2)); // Log full payload
try {
// Example: Using Prisma to update the database
await prisma.messageStatus.upsert({
where: { message_uuid: statusData.message_uuid },
update: {
status: statusData.status,
status_timestamp: new Date(statusData.timestamp), // Convert ISO string to Date object
error_code: statusData.error?.code,
error_reason: statusData.error?.reason,
updated_at: new Date()
},
create: {
message_uuid: statusData.message_uuid,
status: statusData.status,
recipient_number: statusData.to,
sender_number: statusData.from,
status_timestamp: new Date(statusData.timestamp), // Convert ISO string to Date object
error_code: statusData.error?.code,
error_reason: statusData.error?.reason,
// created_at and updated_at might have defaults in the DB schema
}
});
console.log(`Database updated for message: ${statusData.message_uuid}`);
} catch (dbError) {
console.error(`Database error processing status for ${statusData.message_uuid}:`, dbError);
// Decide on internal error handling/retry strategy for DB errors
// BUT STILL ACKNOWLEDGE THE WEBHOOK TO VONAGE
} finally {
// ALWAYS send 200 OK back to Vonage unless there's a catastrophic server failure
// preventing even this response.
res.status(200).send('OK');
}
});8. Error Handling and Logging
Robust error handling and logging are essential for production.
- Webhook Handler:
- Wrap database/processing logic in
try...catchblocks. - Log errors comprehensively, including the message UUID and the full error stack.
- Always return
200 OKto Vonage unless your server is fundamentally unable to process any requests. Vonage has its own retry mechanism for non-2xxresponses; let it handle transient network issues. Focus your internal retries on downstream dependencies like your database if needed.
- Wrap database/processing logic in
- SMS Sending:
- Wrap
vonage.messages.send()intry...catch. - Log detailed errors, including potential response data from the Vonage API (
err.response.datain Axios-based errors from the SDK).
- Wrap
- Structured Logging: Use a dedicated logging library like Winston or Pino for structured logging (e.g., JSON format). This makes logs easier to parse and analyze in production.
// Example using Winston (conceptual setup)
const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info', // Control log level via env var
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json() // Use JSON format for structured logs
),
defaultMeta: { service: 'sms-status-service' },
transports: [
// In development, log to console with simple format
// In production, log to console (to be captured by container orchestrator)
// or configure file/remote transports
new winston.transports.Console({
format: process.env.NODE_ENV !== 'production'
? winston.format.simple()
: winston.format.json(), // Use simple format locally, JSON in prod
}),
],
});
// Replace console.log/console.error with logger.info/logger.error
// e.g., logger.info('Webhook received', { messageId: statusData.message_uuid, status: statusData.status });
// e.g., logger.error('Database update failed', { messageId: statusData.message_uuid, error: dbError.message, stack: dbError.stack });9. Security Considerations
Protecting your webhook endpoint and credentials is vital.
-
Environment Variables: Never hardcode credentials. Use
.envlocally and secure environment variable management (like platform secrets or a secrets manager) in your deployment environment. Ensure.envandprivate.keyare in.gitignore. -
Private Key Handling: Treat your
private.keyfile as highly sensitive. Ensure its permissions are restricted. In production deployments (especially containers), avoid copying the key file directly into the image; use secure methods like mounted volumes or secrets management tools (Docker secrets, Kubernetes secrets, cloud provider secrets managers). -
Webhook Signature Verification (Highly Recommended): The Vonage Messages API supports signing webhooks with JWT (JSON Web Tokens). This allows your application to verify that incoming requests genuinely originated from Vonage.
- Enable Signed Webhooks: In your Vonage Application settings under Messages > Webhook security, select ""Enable signed webhooks"" and choose ""JWT (recommended)"". Vonage will display the Public Key associated with your application.
- Store Public Key Securely: Obtain the Public Key string from the Vonage dashboard. Do not hardcode the public key directly in your source code. Store it securely, for example, as an environment variable or retrieve it from a secrets management service at runtime.
- Install Verification Library:
bash
npm install jsonwebtoken # or # yarn add jsonwebtoken - Implement Verification Middleware: Use the
jsonwebtokenlibrary and the Vonage public key to verify theAuthorization: Bearer <token>header sent with each webhook.
javascript// Conceptual JWT Verification Middleware const jwt = require('jsonwebtoken'); // Load the public key securely (e.g., from environment variable) // IMPORTANT: Avoid hardcoding this in production code. Load from secure source. const VONAGE_PUBLIC_KEY = process.env.VONAGE_PUBLIC_KEY_STRING; // Example format: ""-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"" if (!VONAGE_PUBLIC_KEY) { // Use your logger here console.warn(""VONAGE_PUBLIC_KEY environment variable not set. Skipping JWT verification. THIS IS INSECURE FOR PRODUCTION.""); } function verifyVonageSignature(req, res, next) { // Only verify if the public key was loaded if (!VONAGE_PUBLIC_KEY) { return next(); // Skip verification if key is missing (warning already logged) } const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { // Use your logger here console.warn('Missing or invalid Authorization header for JWT verification.'); return res.sendStatus(401); // Unauthorized } const token = authHeader.split(' ')[1]; try { // Verify the token using the application's public key // Ensure correct algorithm (RS256) which Vonage uses for Messages API JWT const decoded = jwt.verify(token, VONAGE_PUBLIC_KEY, { algorithms: ['RS256'] }); // Optional but Recommended: Check if decoded application_id matches your app ID if (decoded.application_id !== process.env.VONAGE_APPLICATION_ID) { // Use your logger here console.warn('JWT application_id mismatch', { jwtAppId: decoded.application_id, expectedAppId: process.env.VONAGE_APPLICATION_ID }); return res.sendStatus(401); // Or 403 Forbidden } // Attach decoded payload if needed for auditing, or just proceed req.vonage_jwt = decoded; // Example: make decoded token available // Use your logger here console.info('JWT signature verified successfully', { messageId: req.body?.message_uuid }); next(); // Signature is valid, proceed to the handler } catch (err) { // Use your logger here console.error('Invalid JWT signature', { error: err.message, tokenReceived: token ? 'yes' : 'no' }); return res.sendStatus(401); // Unauthorized - signature verification failed } } // Apply middleware *before* your route handler in src/index.js // Ensure logger is defined if using it within the middleware app.post('/webhooks/status', verifyVonageSignature, async (req, res) => { // ... existing handler logic ... }); -
HTTPS: Always use HTTPS for your webhook endpoint in production. Ensure your server has a valid SSL/TLS certificate configured.
-
Input Validation: Even with JWT verification, sanitize and validate expected fields within the webhook payload before using them (e.g., check data types, lengths, expected values for
status). -
Rate Limiting: Implement rate limiting on your webhook endpoint using middleware like
express-rate-limitto prevent abuse.
Frequently Asked Questions
how to track sms delivery status with vonage
Track SMS delivery status by setting up a webhook endpoint with the Vonage Messages API. Your Node.js application will receive real-time status updates (e.g., delivered, failed) via this endpoint, enabling you to implement custom logic based on these updates, such as retry mechanisms or user notifications. This guide provides a comprehensive walkthrough of the process using Express.js and the Vonage Server SDK.
what is a vonage status webhook
A Vonage status webhook is an HTTP endpoint you provide to the Vonage Messages API. When the status of an SMS message changes (e.g., sent, delivered, failed), Vonage sends an HTTP POST request to this URL with status details. This allows your application to react to delivery events in real-time.
why use vonage messages api for sms
The Vonage Messages API offers a multi-channel approach for sending and receiving various message types, including SMS. It provides features like delivery status updates via webhooks, allowing for robust error handling and improved communication workflows. The API simplifies sending SMS messages from your Node.js applications.
when to configure vonage status url
Configure the Vonage Status URL when creating or modifying a Vonage Application in the Vonage API Dashboard. This URL is essential for receiving real-time delivery receipts and handling potential message failures. It must point to a publicly accessible endpoint on your server where you'll process the incoming status updates.
how to create a vonage application for sms
Create a Vonage Application by logging into the Vonage API Dashboard, navigating to 'Your Applications,' and clicking 'Create a new application.' Provide a descriptive name, generate public and private keys (securely storing the private key), and enable the 'Messages' capability under the capabilities section. Link the application to your Vonage Virtual Number, allowing you to send and receive messages through the Vonage Messages API using JWT Authentication.
what is vonage private key path
The `VONAGE_PRIVATE_KEY_PATH` environment variable stores the *file path* to your downloaded `private.key` file, relative to where your Node.js process starts. This key is crucial for authenticating with the Vonage Messages API and should *never* be hardcoded or exposed in version control. The file's contents are used with your application ID for JWT authentication with the Vonage server SDK.
how to send test sms with vonage api
Send a test SMS using the Vonage Messages API by initializing the Vonage Node.js SDK with your credentials and calling `vonage.messages.send()`. Provide the recipient's number, your Vonage virtual number, and the message text. Ensure `RECIPIENT_NUMBER` in the `send-test-sms.js` example is replaced with a valid number.
what is ngrok used for with vonage webhooks
ngrok creates a temporary public URL that tunnels to your local development server. This allows Vonage to send webhook requests to your local machine during development, even though it's behind a firewall or NAT. Ngrok is essential for testing webhooks locally, ensuring they function correctly before production deployment.
how to handle vonage webhook errors
Handle Vonage webhook errors by implementing robust error handling within your webhook route handler using `try...catch` blocks, logging errors with details such as message UUID and error stack, and *always returning a 200 OK status to Vonage*. This prevents Vonage from repeatedly retrying the webhook and allows your application to manage any issues with downstream services, like database updates, separately.
why respond with 200 ok to vonage webhook
Responding with a 200 OK status to a Vonage webhook acknowledges successful receipt of the webhook data. Without a 200 OK response, Vonage assumes the webhook failed and will retry sending it, potentially leading to duplicate processing. This is crucial for reliable communication between your application and the Vonage Messages API.
what database to use for vonage sms statuses
You can choose a database suitable for your needs and resources, such as PostgreSQL, MySQL, MongoDB, or others, to persist your Vonage SMS delivery statuses. Consider data volume, query complexity, and ease of integration with Node.js when selecting a database. You may also consider an ORM such as Prisma or Sequelize for database interactions.
how to secure vonage webhook endpoint
Secure your Vonage webhook endpoint by using HTTPS, verifying JWT signatures, managing credentials securely (especially your private key and public key), validating input data, and implementing rate limiting to prevent abuse. Never hardcode credentials or expose sensitive information in your codebase.
can I verify vonage webhook signatures
Yes, you can verify Vonage webhook signatures using JWT (JSON Web Tokens). Enable signed webhooks in your Vonage application settings and store the public key securely to validate signatures. The example uses the `jsonwebtoken` library for verification, which helps ensure the integrity and authenticity of incoming webhook requests.
what is vonage application id
The Vonage Application ID is a unique identifier assigned to your Vonage application. It's used along with your private key for JWT authentication with various Vonage APIs, especially when interacting with the Messages API. This method is preferred over API Key/Secret authentication in modern Vonage APIs.