code examples
code examples
Vonage SMS Delivery Status & Webhooks with Node.js Express: Complete Guide
Learn how to send SMS messages, receive inbound texts, and track delivery status using Vonage Messages API with Node.js and Express. Includes webhook implementation and real-time status tracking.
Node.js & Express Guide: Vonage Messages API for SMS Sending, Receiving & Delivery Status
This comprehensive guide shows you how to build a production-ready Node.js application using Express to send SMS messages, receive inbound SMS, and track delivery status in real-time using Vonage Messages API webhooks.
Learn how to implement SMS delivery tracking and webhooks in Node.js for:
- Sending SMS messages programmatically: Trigger SMS messages to specified recipients via an API endpoint using the Vonage Messages API.
- Receiving inbound SMS messages: Handle incoming texts sent to your Vonage virtual number using webhook callbacks.
- Tracking SMS delivery status in real-time: Monitor delivery receipts (DLR) and receive instant status updates (delivered, failed, rejected) via webhook notifications.
This tutorial solves the common need for applications to reliably communicate with users via SMS and track message delivery confirmation. Perfect for building two-factor authentication (2FA), notification systems, and SMS marketing campaigns. Note: Test the code snippets rigorously in your environment, especially error handling and security implementations.
Technologies Used
- Node.js: A JavaScript runtime environment for server-side development. Requires Node.js 14+ or later.
- Express: A minimal and flexible Node.js web application framework.
- Vonage Messages API: A unified API for sending and receiving messages across various channels, including SMS. This guide uses it for comprehensive features, JWT-based webhook security, and webhook capabilities for inbound messages and status updates.
@vonage/server-sdk: The official Vonage Node.js SDK (v3.x or later) for interacting with Vonage APIs.dotenv: A module to load environment variables from a.envfile.jsonwebtoken: (Conceptually needed for JWT verification, though implementation details are deferred to Vonage docs).- ngrok: A tool to expose local development servers to the internet for testing webhooks during development.
System Architecture: How SMS Webhooks Work
The SMS delivery tracking system involves several components interacting:
- Your Application (Node.js/Express): Hosts the API endpoint for sending SMS and the webhook endpoints for receiving inbound messages and delivery status callbacks.
- Vonage Platform: Provides the Messages API, virtual numbers, and handles the routing of SMS messages and webhook events.
- User's Mobile Device: Sends and receives SMS messages.
- ngrok (Development): Tunnels requests from a public URL to your local development server. For production, use a stable public IP/domain with SSL (see Production Deployment section).
Prerequisites
Before starting, ensure you have the following:
- Vonage API Account: Sign up for free at Vonage API Dashboard. You'll get free credit to start.
- Vonage API Key and Secret: Find these at the top of your Vonage API Dashboard. (Required for SDK setup, though Application ID/Private Key is primary auth for Messages API calls).
- Vonage Virtual Number: Purchase an SMS-capable number from the dashboard under "Numbers" > "Buy Numbers".
- Node.js and npm (or yarn): Installed on your system. Download from nodejs.org. Requires Node.js 14 or later.
- ngrok: Installed and authenticated. Download from ngrok.com. A free account is sufficient for development. Note that free ngrok URLs are temporary. Paid tiers offer stable domains.
- Basic understanding of Node.js, Express, and APIs.
1. Setting up the Project
Initialize your Node.js project 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-messages-guide cd vonage-messages-guide -
Initialize Node.js Project: This creates a
package.jsonfile to manage dependencies and project metadata.bashnpm init -y(Use
yarn init -yif you prefer Yarn) -
Install Dependencies: Install
expressfor the web server,@vonage/server-sdkto interact with the Vonage API, anddotenvto manage environment variables. Also installjsonwebtokenfor webhook security.bashnpm install express @vonage/server-sdk dotenv jsonwebtoken(Use
yarn add express @vonage/server-sdk dotenv jsonwebtokenfor Yarn) -
Create Project Structure: Create the necessary files and folders.
bashtouch server.js .env .gitignore vonageClient.js mkdir logs # Optional: For file loggingserver.js: Main application file for the Express server and route handlers..env: Stores sensitive credentials and configuration (API keys, etc.). Never commit this file to version control..gitignore: Specifies intentionally untracked files that Git should ignore (like.envandnode_modules).vonageClient.js: Module to initialize and export the Vonage SDK client.logs/: Directory for log files if using file transport for logging.
-
Configure
.gitignore: Add the following lines to your.gitignorefile to prevent sensitive information and unnecessary files from being committed:text# Dependencies node_modules # Environment variables .env # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Optional Editor directories .idea .vscode *.suo *.ntvs* *.njsproj *.sln # Private Key file private.key *.pem -
Set up Environment Variables (
.env): Open the.envfile and add the following variables. Fill in the values in the next steps.dotenv# Vonage Credentials VONAGE_API_KEY=YOUR_VONAGE_API_KEY VONAGE_API_SECRET=YOUR_VONAGE_API_SECRET VONAGE_APPLICATION_ID=YOUR_VONAGE_APPLICATION_ID VONAGE_PRIVATE_KEY_PATH=./private.key # Path relative to project root VONAGE_NUMBER=YOUR_VONAGE_VIRTUAL_NUMBER # Your purchased Vonage number # Application Config PORT=3000 # Port your local server will run on BASE_WEBHOOK_URL=YOUR_NGROK_FORWARDING_URL # We'll get this from ngrok laterVONAGE_API_KEY,VONAGE_API_SECRET: Found on your Vonage dashboard. Used by the SDK, though Application ID/Private Key is primary for Messages API.VONAGE_APPLICATION_ID,VONAGE_PRIVATE_KEY_PATH: Obtained when creating a Vonage Application (next section).VONAGE_NUMBER: Your purchased Vonage virtual phone number in E.164 format (e.g.,14155552671for US,447700900000for UK).PORT: The local port your Express application will listen on.BASE_WEBHOOK_URL: The public URL provided by ngrok (or your production server URL).
-
Basic Express Server (
server.js): Set up a minimal Express server to ensure everything works.javascript// server.js require('dotenv').config(); // Load .env variables into process.env const express = require('express'); const app = express(); // Middleware to parse JSON bodies // Vonage Messages API webhooks typically use JSON app.use(express.json()); // Include urlencoded parser for flexibility, though JSON is primary for Messages API app.use(express.urlencoded({ extended: true })); const PORT = process.env.PORT || 3000; // Simple root route for testing app.get('/', (req, res) => { res.send('Vonage Messages API Guide App is running!'); }); // Health check endpoint app.get('/health', (req, res) => { res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString() }); }); // Start the server app.listen(PORT, () => { console.log(`Server listening at http://localhost:${PORT}`); });Run this with
node server.js. You should see the confirmation message in your console. Accessinghttp://localhost:3000in your browser should show the test message.
2. Vonage Account and Application Setup
Configure your Vonage account and create a Vonage Application to handle Messages API interactions.
-
Retrieve API Key and Secret:
- Log in to your Vonage API Dashboard.
- Your API key and API secret are displayed prominently at the top.
- Copy these values into your
.envfile forVONAGE_API_KEYandVONAGE_API_SECRET.
-
Purchase a Vonage Number:
- Navigate to Numbers > Buy numbers.
- Search for a number with SMS capabilities in your desired country.
- Purchase the number.
- Copy the purchased number (in E.164 format, e.g.,
12015550123) into your.envfile forVONAGE_NUMBER.
-
Set Default SMS API to "Messages API":
- This is crucial for consistency. Navigate to Settings in the dashboard.
- Scroll down to SMS settings.
- Ensure that Default SMS Setting is set to Messages API. If it's set to "SMS API", toggle it to "Messages API".
- Click Save changes.
- Why? While linking a number to a Messages-enabled Application should force Messages API usage for that app, this account-wide setting ensures consistent Messages API behavior (and webhook formats) for numbers not linked to this specific application or if other authentication methods are ever used inadvertently within your account. It aligns your account's default behavior with this guide's focus.
-
Create a Vonage Application: Vonage Applications act as containers for configuration, security credentials (like the private key), and associated numbers, specifically for APIs like Messages.
- Navigate to Applications > Create a new application.
- Give your application a descriptive Name (e.g., "Node Messages Guide App").
- Click Generate public and private key. This will automatically download the
private.keyfile. Save this file securely in the root of your project directory (matchingVONAGE_PRIVATE_KEY_PATHin your.env). Crucially, addprivate.keyor*.pemto your.gitignorefile. Vonage stores the public key; you keep the private key to authenticate SDK requests using JWT. - Enable the Messages capability by toggling it on.
- Enter webhook endpoints using your base URL variable (update these with the real ngrok URL later):
- Inbound URL:
${BASE_WEBHOOK_URL}/webhooks/inbound(Method:POST) - Status URL:
${BASE_WEBHOOK_URL}/webhooks/status(Method:POST)
- Inbound URL:
- Scroll down to Link virtual numbers and link the Vonage number you purchased earlier to this application. Select the number and click Link.
- Click Generate new application.
- You will be redirected to the application's details page. Copy the Application ID.
- Paste the Application ID into your
.envfile forVONAGE_APPLICATION_ID.
3. Setting up SMS Webhooks with ngrok (Development)
Webhooks enable real-time SMS delivery tracking by allowing Vonage to send instant notifications (inbound messages, delivery status updates) to your application. Since your application runs locally during development, use ngrok to create a secure tunnel from a public URL to your localhost.
Important: ngrok is excellent for development and testing, but not suitable for production. Production environments require a stable, publicly accessible IP address or domain name with a valid SSL/TLS certificate. See the Production Deployment section for more details.
-
Run ngrok: Open a new terminal window (keep your Node.js server running or be ready to restart it). Run ngrok, telling it to forward traffic to the port your Express app listens on (defined in
.env, default is 3000).bashngrok http 3000 -
Get the Forwarding URL: ngrok will display session information, including a public
ForwardingURL (usually ending in.ngrok.ioor.ngrok-free.app). It will look something likehttps://<random-string>.ngrok-free.app. Always use thehttpsversion.Session Status online Account Your Name (Plan: Free/Paid) Version x.x.x Region United States (us-cal-1) Web Interface http://127.0.0.1:4040 Forwarding https://<random-string>.ngrok-free.app -> http://localhost:3000 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00Copy this
https://...Forwarding URL. Note that free accounts generate a new random URL each time ngrok starts. Paid ngrok plans offer stable subdomains.Tip: Access the ngrok web interface at
http://127.0.0.1:4040to inspect all incoming requests, view headers (including JWT tokens), and debug webhook issues. -
Update
.env: Paste the copied ngrok Forwarding URL into your.envfile for theBASE_WEBHOOK_URLvariable.dotenv# .env (partial) BASE_WEBHOOK_URL=https://<random-string>.ngrok-free.app -
Update Vonage Application Webhooks:
- Go back to your Vonage Application settings in the dashboard (Applications > click your application name).
- Click Edit.
- Replace the placeholder URLs with your actual ngrok URLs:
- Inbound URL:
https://<random-string>.ngrok-free.app/webhooks/inbound - Status URL:
https://<random-string>.ngrok-free.app/webhooks/status
- Inbound URL:
- Click Save changes.
Why? When Vonage needs to send an inbound message or a status update related to this application, it sends an HTTP POST request to these public ngrok URLs, which ngrok forwards to your locally running Express application's
/webhooks/inboundand/webhooks/statusroutes. -
Restart Your Node.js Server: If your server is running, stop it (
Ctrl+C) and restart it (node server.js) to ensure it picks up the updated.envconfiguration.
4. Implementing SMS Sending
Create the Vonage SDK client and add an endpoint to send SMS messages using the Messages API.
-
Configure Vonage SDK Client (
vonageClient.js): This module initializes the Vonage client using credentials from the.envfile. Using Application ID and Private Key is the recommended authentication method for the Messages API as it uses JWTs.javascript// vonageClient.js require('dotenv').config(); const { Vonage } = require('@vonage/server-sdk'); const { readFileSync } = require('fs'); // To read the private key file // Read the private key from the file path specified in .env let privateKeyValue; try { privateKeyValue = readFileSync(process.env.VONAGE_PRIVATE_KEY_PATH); } catch (error) { console.error(`Error reading private key from path: ${process.env.VONAGE_PRIVATE_KEY_PATH}`, error); process.exit(1); // Exit if private key cannot be loaded } const vonage = new Vonage({ apiKey: process.env.VONAGE_API_KEY, // Still required by SDK constructor apiSecret: process.env.VONAGE_API_SECRET, // Still required by SDK constructor applicationId: process.env.VONAGE_APPLICATION_ID, privateKey: privateKeyValue // Use the key content read from the file }, { // Optional: Set custom options here // apiHost: 'https://messages-sandbox.nexmo.com' // Example: Use the sandbox }); module.exports = vonage;Why use Application ID/Private Key? This method uses JWTs generated by the SDK for authenticating API requests, which is more secure than sending API Key/Secret with each request for server-to-server communication via the Messages API.
-
Create Send SMS Endpoint (
server.js): Add a new route to yourserver.jsfile to handle sending SMS messages. Make it aPOSTrequest that accepts the recipient number and message text in the request body.javascript// server.js (add these parts) const vonage = require('./vonageClient'); // Import the initialized client // Keep existing setup: require('dotenv').config(), express, app, middleware... app.get('/', (req, res) => { // Keep existing root route res.send('Vonage Messages API Guide App is running!'); }); app.get('/health', (req, res) => { // Keep health check route res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString() }); }); // --- New Route for Sending SMS --- app.post('/send-sms', async (req, res) => { // Basic input validation const { to, text } = req.body; if (!to || !text) { return res.status(400).json({ error: 'Missing "to" or "text" in request body.' }); } // Consider adding phone number validation using libphonenumber-js in production const fromNumber = process.env.VONAGE_NUMBER; if (!fromNumber) { console.error('VONAGE_NUMBER is not set in .env'); return res.status(500).json({ error: 'Server configuration error: Missing Vonage number.' }); } console.log(`Attempting to send SMS via Messages API from ${fromNumber} to ${to}`); try { // Use the vonage.messages.send() method for the Messages API const resp = await vonage.messages.send({ channel: 'sms', message_type: 'text', to: to, // Recipient phone number (E.164 format recommended) from: fromNumber, // Your Vonage virtual number linked to the Application text: text // The message content (max 160 chars for single SMS) }); console.log('Message submission accepted by Vonage:', resp); // The resp object contains the message_uuid res.status(200).json({ success: true, message_uuid: resp.message_uuid }); } catch (err) { console.error('Error sending SMS via Messages API:', err.response ? JSON.stringify(err.response.data, null, 2) : err.message); // Provide more context if available from the Vonage error response const errorMessage = err.response?.data?.title || err.message || 'Failed to send SMS'; const errorDetail = err.response?.data?.detail || JSON.stringify(err.response?.data); // Include full detail if possible res.status(err.response?.status || 500).json({ success: false, error: errorMessage, detail: errorDetail }); } }); // --- End of New Route --- // ... (Add webhook routes later) // Start the server (keep existing) const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server listening at http://localhost:${PORT}`); });Note: SMS messages longer than 160 characters automatically concatenate into multiple segments. Each segment is billed separately. Consider truncating or paginating long messages.
-
Test Sending SMS: Make sure your Node.js server and ngrok are running. Use
curlor a tool like Postman/Insomnia to send a POST request to your/send-smsendpoint. Replace<your-ngrok-url>with your actual ngrok Forwarding URL and<recipient-phone-number>with a real phone number (in E.164 format, e.g.,14155552671).bashcurl -X POST <your-ngrok-url>/send-sms \ -H "Content-Type: application/json" \ -d '{ "to": "<recipient-phone-number>", "text": "Hello from Vonage Messages API and Node.js!" }'You should receive an SMS on the recipient phone shortly. Your server console will log the success response from Vonage, including the
message_uuid, which is crucial for tracking. The API call should return a JSON response like{"success":true,"message_uuid":"<some-uuid>"}. If errors occur, check the console logs for details from thecatchblock.
Common Errors:
- 401 Unauthorized: Check your Application ID and private key path in
.env. - 403 Forbidden: Ensure your number is linked to the Vonage Application.
- 422 Unprocessable Entity: Verify phone numbers are in E.164 format and the "to" number is valid.
5. Implementing Inbound SMS Webhook (Receive SMS)
This webhook endpoint handles incoming SMS messages sent to your Vonage virtual number. When someone texts your number, Vonage forwards the message to your webhook URL via HTTP POST request.
-
Create Inbound Webhook Route (
server.js): Add aPOSTroute handler for the/webhooks/inboundpath you configured in your Vonage Application.javascript// server.js (add this route) // ... (existing code: imports, middleware, /send-sms route) // --- New Route for Inbound SMS --- app.post('/webhooks/inbound', (req, res) => { console.log('--- Inbound SMS Webhook Received ---'); console.log('Request Body:', JSON.stringify(req.body, null, 2)); // Log headers for debugging signature verification later console.log('Request Headers:', JSON.stringify(req.headers, null, 2)); // --- Security Check Placeholder --- // IMPORTANT: Implement JWT verification here for production! // See Security Considerations section and Vonage documentation. // verifyVonageSignature(req); // This function needs proper implementation // Process the inbound message (assuming signature verification passed or is deferred) const message = req.body; // Check structure for typical Messages API inbound SMS if (message.from?.type === 'sms' && message.message?.content?.type === 'text') { console.log(`From: ${message.from.number}`); console.log(`To: ${message.to.number}`); // Your Vonage number console.log(`Text: ${message.message.content.text}`); console.log(`Message UUID: ${message.message_uuid}`); console.log(`Timestamp: ${message.timestamp}`); // --- Add your business logic here --- // Example: Store the message, trigger a reply, etc. // e.g., replyToSms(message.from.number, 'Thanks for your message!'); } else { console.warn('Received webhook does not appear to be a standard inbound SMS via Messages API.'); } // Vonage needs a 200 OK response quickly to acknowledge receipt. // Do complex processing asynchronously if needed. res.status(200).end(); }); // --- End of Inbound Route --- // ... (Add status webhook route next) // Start the server (keep existing) // ... -
Understanding Webhook Signature Verification (JWT): The Messages API secures webhooks using JWT (JSON Web Tokens) attached to the
Authorizationheader (typicallyBearer <token>). Verifying this signature is critical for production security to ensure requests genuinely come from Vonage.JWT Verification Steps:
- Extract the JWT from the
Authorization: Bearer <token>header - Decode the JWT to get the payload and claims
- Fetch Vonage's public key (available via Vonage API or dashboard)
- Verify the signature using
jsonwebtoken.verify(token, publicKey, { algorithms: ['RS256'] }) - Validate the claims (e.g.,
iat,exp,aud)
Refer to the official Vonage Developer Documentation on "Signed Webhooks" or "Webhook Security" specifically for the Messages API for the authoritative and up-to-date implementation guide.
- Extract the JWT from the
-
Test Inbound SMS:
- Ensure your Node.js server and ngrok are running.
- Using your mobile phone, send an SMS message to your Vonage virtual number (
VONAGE_NUMBER). - Watch your server console. You should see the "--- Inbound SMS Webhook Received ---" log message, followed by the parsed request body and headers. Verify the message content and sender number are logged correctly.
- Check the ngrok web interface (
http://127.0.0.1:4040) to inspect the incoming POST request to/webhooks/inbound, including theAuthorizationheader containing the JWT.
6. Implementing Delivery Status Webhook (Track SMS Delivery)
This webhook receives real-time delivery receipt (DLR) updates about the delivery status of outbound messages you sent using the Messages API. Track whether your SMS was delivered, failed, or rejected by the carrier.
- Create Status Webhook Route (
server.js): Add aPOSTroute handler for the/webhooks/statuspath.javascript// server.js (add this route) // ... (existing code: imports, middleware, /send-sms, /webhooks/inbound) // --- New Route for Delivery Status Updates --- app.post('/webhooks/status', (req, res) => { console.log('--- Delivery Status Webhook Received ---'); console.log('Request Body:', JSON.stringify(req.body, null, 2)); // Log headers for debugging signature verification later console.log('Request Headers:', JSON.stringify(req.headers, null, 2)); // --- Security Check Placeholder --- // IMPORTANT: Implement JWT verification here for production! // Use the same logic/mechanism as for the inbound webhook. // See Security Considerations section and Vonage documentation. // verifyVonageSignature(req); // This function needs proper implementation // Process the status update (assuming signature verification passed or is deferred) const statusUpdate = req.body; // Key fields in a Messages API status update if (statusUpdate.message_uuid && statusUpdate.status) { console.log(`Status for message ${statusUpdate.message_uuid}: ${statusUpdate.status}`); console.log(`Timestamp: ${statusUpdate.timestamp}`); console.log(`Recipient: ${statusUpdate.to?.number}`); if (statusUpdate.error) { console.error(`Error Code: ${statusUpdate.error.code}`); console.error(`Error Reason: ${statusUpdate.error.reason}`); } if (statusUpdate.usage) { console.log(`Usage: Price ${statusUpdate.usage.price} ${statusUpdate.usage.currency}`); } // --- Add your business logic here --- // Example: Update the status of the message in your database using message_uuid // updateMessageStatusInDB(statusUpdate.message_uuid, statusUpdate.status, statusUpdate.error, statusUpdate.timestamp); } else { console.warn('Received webhook does not appear to be a valid Messages API status update.'); } // Vonage needs a 200 OK response. res.status(200).end(); }); // --- End of Status Route --- // Start the server (keep existing) // ...
SMS Delivery Status Codes Explained:
Understanding these delivery status codes helps you track SMS delivery success:
| Status | Description | Final State |
|---|---|---|
submitted | Vonage accepted the message request | No |
delivered | Recipient's carrier confirmed delivery to the handset | Yes |
rejected | Vonage rejected before sending (e.g., invalid number, insufficient funds) | Yes |
undeliverable | Carrier could not deliver (e.g., phone off, invalid number, blocked) | Yes |
failed | General delivery failure | Yes |
Note: You may receive multiple webhook callbacks per message as the status changes. Final states (delivered, rejected, undeliverable, failed) indicate no further status updates will arrive for that message.
- Test Status Updates:
- Ensure your server and ngrok are running.
- Send an SMS using the
/send-smsendpoint (Step 4). Note themessage_uuidreturned. - Watch your server console. You should receive one or more status updates for that
message_uuidvia the/webhooks/statusendpoint. - Status updates typically arrive within seconds to minutes, depending on carrier processing times.
- Check the ngrok web interface (
http://127.0.0.1:4040) to inspect the incoming POST requests to/webhooks/status, including the JWT in theAuthorizationheader.
7. Error Handling and Logging
Robust error handling and clear logging are essential for debugging and maintaining the application.
-
Error Handling:
- API Calls: Wrap the
vonage.messages.sendcall in atry...catchblock. Log detailed error information fromerr.response.dataif available, as it contains specific Vonage error details. Return appropriate HTTP status codes (e.g., 400 for bad input, 500 for server errors, Vonage status codes if meaningful). - Webhooks: Ensure webhook handlers always return a
200 OKstatus to Vonage quickly, even if internal processing fails. Log errors encountered during webhook processing for later debugging. Usetry...catchwithin the webhook handlers for your business logic to prevent crashes from stopping theres.status(200).end()call. Handle asynchronous operations carefully. - Configuration: Check for the existence and validity of necessary environment variables (like
VONAGE_NUMBER,VONAGE_APPLICATION_ID,VONAGE_PRIVATE_KEY_PATH) at startup. ThevonageClient.jsexample includes validation that exits the process if the private key fails to load.
- API Calls: Wrap the
-
Logging:
- Use
console.log,console.warn, andconsole.errorconsistently for basic logging. - Log key events: server start, incoming API requests, outgoing API calls, successful operations, errors (with stack traces or detailed Vonage responses), and full webhook payloads/headers (especially during development).
- Include unique identifiers like
message_uuidin logs to trace the lifecycle of a message across sending, status updates, and potential inbound replies. - For production: Use a structured logging library like Winston or Pino. These libraries offer:
- Different log levels (debug, info, warn, error, critical).
- Structured formatting (JSON is common for log aggregation systems).
- Multiple transports (writing to console, files, databases, external logging services like Datadog, Loggly, etc.).
javascript// Example using Winston (requires npm install winston) const winston = require('winston'); const path = require('path'); const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', // Control log level via env var format: winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), // Log stack traces winston.format.splat(), winston.format.json() // Log as JSON ), defaultMeta: { service: 'vonage-sms-app' }, // Add service name to logs transports: [ // Write errors to error.log new winston.transports.File({ filename: path.join('logs', 'error.log'), level: 'error' }), // Write all logs (info and above) to combined.log new winston.transports.File({ filename: path.join('logs', 'combined.log') }) ], }); // If not in production, also log to the console with a simpler format if (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple() ) })); } // Usage example (replace console.log/error): // logger.info('Server started on port %d', PORT); // logger.error('Failed to send SMS:', err); // logger.warn('Webhook received with unexpected format:', req.body); // logger.debug('Detailed webhook body:', req.body); // Only logs if level is 'debug' - Use
Best Practices:
- Redact PII (personally identifiable information) like phone numbers and message content from production logs.
- Use correlation IDs to trace requests across distributed systems.
- Configure log rotation to prevent disk space issues.
- Integrate with monitoring services (Sentry, New Relic) for real-time error tracking.
8. Security Considerations
Security is paramount when handling SMS and webhooks. Implement these measures for production:
-
Webhook JWT Verification (Critical):
- Implement JWT signature verification for all webhook endpoints (
/webhooks/inboundand/webhooks/status). - Extract the JWT from the
Authorization: Bearer <token>header. - Verify the signature using Vonage's public key and the
jsonwebtokenlibrary. - Validate JWT claims including expiration (
exp), issued-at (iat), and audience (aud). - Reject requests with invalid or expired tokens.
- Refer to Vonage's official documentation for implementation details.
- Implement JWT signature verification for all webhook endpoints (
-
Private Key Security:
- Never commit
private.keyor.pemfiles to version control. Add them to.gitignore. - In production, store private keys in secure secret management systems (AWS Secrets Manager, HashiCorp Vault, etc.).
- Use environment variables or secure configuration management to inject keys at runtime.
- Implement key rotation procedures and monitor for compromised keys.
- Never commit
-
Environment Variables:
- Never hardcode credentials in source code.
- Use different
.envfiles for development, staging, and production environments. - Restrict access to environment variables in production systems.
-
Input Validation:
- Validate all user inputs, especially phone numbers. Use libraries like
libphonenumber-jsfor robust phone number validation. - Sanitize message content to prevent injection attacks.
- Implement rate limiting on the
/send-smsendpoint to prevent abuse (use libraries likeexpress-rate-limit).
- Validate all user inputs, especially phone numbers. Use libraries like
-
HTTPS/TLS:
- Always use HTTPS for production webhooks. ngrok provides HTTPS, but production requires valid SSL/TLS certificates (Let's Encrypt, commercial CAs).
- Ensure your server enforces TLS 1.2 or higher.
-
Error Messages:
- Avoid exposing sensitive information in error messages returned to clients.
- Log detailed errors server-side but return generic messages to API consumers.
9. Production Deployment
Deploying to production requires additional considerations beyond ngrok:
Infrastructure Requirements:
- Stable Public URL: Use a cloud provider (AWS, Google Cloud, Azure, Heroku, DigitalOcean) or VPS with a static IP address or domain name.
- SSL/TLS Certificate: Obtain a valid SSL certificate (free from Let's Encrypt or commercial CAs).
- Process Manager: Use PM2, systemd, or Docker to keep your Node.js application running and restart on crashes.
- Reverse Proxy: Use Nginx or Apache as a reverse proxy for SSL termination, load balancing, and security.
Production Checklist:
- Deploy your application to a production server.
- Configure environment variables securely (use secret management tools).
- Update Vonage Application webhook URLs to point to your production domain (e.g.,
https://yourdomain.com/webhooks/inbound). - Implement JWT webhook verification.
- Set up monitoring and alerting (New Relic, Datadog, Sentry).
- Configure log aggregation (ELK stack, Splunk, CloudWatch).
- Implement rate limiting and DDoS protection (Cloudflare, AWS WAF).
- Set up automated backups for any databases or persistent storage.
- Test thoroughly in a staging environment before production deployment.
- Implement graceful shutdown handling to prevent message loss during deployments.
Scaling Considerations:
- Use message queues (Bull, RabbitMQ, AWS SQS) for asynchronous webhook processing.
- Implement horizontal scaling with load balancers.
- Monitor Vonage API rate limits and implement backoff/retry logic.
- Cache frequently accessed data (Redis, Memcached).
- Use database connection pooling for efficient resource usage.
10. Additional Resources
- Official Vonage Documentation: https://developer.vonage.com/messages/overview
- Vonage Messages API Reference: https://developer.vonage.com/api/messages-olympus
- Vonage Node.js SDK: https://github.com/Vonage/vonage-node-sdk
- Webhook Security (JWT Verification): https://developer.vonage.com/messages/concepts/signed-webhooks
- E.164 Phone Number Format: https://en.wikipedia.org/wiki/E.164
- Express.js Documentation: https://expressjs.com/
- ngrok Documentation: https://ngrok.com/docs
- Winston Logging: https://github.com/winstonjs/winston
Conclusion: Building SMS Delivery Tracking Systems
You have successfully built a complete SMS delivery tracking application using Node.js, Express, and the Vonage Messages API with full webhook support:
- Send SMS messages programmatically via API
- Receive inbound SMS via webhook callbacks
- Track SMS delivery status and delivery receipts (DLR) in real-time
This foundation enables you to build sophisticated SMS-based applications including two-factor authentication, notifications, customer support systems, and marketing campaigns.
Next Steps:
- Implement JWT webhook verification for production security
- Add database persistence for messages and status tracking
- Implement rate limiting and input validation
- Set up production deployment with monitoring and logging
- Explore advanced Vonage features like MMS, WhatsApp, and conversation API
Frequently Asked Questions
How to send SMS with Vonage Messages API?
Use the Vonage Messages API's `vonage.messages.send()` method. Provide the recipient's number, your Vonage virtual number, and the message text in the request body. Ensure your Vonage application is set up correctly with the necessary credentials and linked virtual number as described in the guide's setup steps.
What is the Vonage Messages API?
The Vonage Messages API is a unified API for sending and receiving messages across various channels, including SMS. It offers comprehensive features for sending SMS programmatically, receiving inbound SMS to your virtual number, and tracking message delivery status through webhooks.
Why use Vonage Application ID/Private Key for authentication?
Using Application ID and Private Key with the Vonage Messages API enhances security by using JWT (JSON Web Tokens) for authentication. This approach is preferred over sending API Key/Secret with each request, as it reduces the risk of credential exposure.
How to receive inbound SMS messages with Node.js?
Set up a webhook endpoint in your Node.js/Express application (e.g., `/webhooks/inbound`) and configure this URL in your Vonage Application settings. Vonage will send an HTTP POST request to this endpoint whenever an SMS is sent to your linked virtual number.
How to set up ngrok for Vonage webhooks?
Run `ngrok http <your-port>` (e.g., `ngrok http 3000`) in a separate terminal. Copy the HTTPS forwarding URL provided by ngrok. Update your `.env` file's `BASE_WEBHOOK_URL` with this URL and configure your Vonage Application's inbound and status URLs using this base URL.
What is the purpose of JWT verification for webhooks?
JWT verification ensures that incoming webhook requests genuinely originate from Vonage, preventing unauthorized access or malicious actors from spoofing webhook events. It is crucial for production security.
How to track SMS delivery status with Vonage?
Configure a status webhook URL (e.g., `/webhooks/status`) in your Vonage application settings. Vonage will send POST requests to this endpoint with real-time delivery status updates (e.g., 'submitted', 'delivered', 'failed') for each message sent via the Messages API.
What are the prerequisites for using Vonage Messages API?
You'll need a Vonage API account, API Key and Secret, a purchased Vonage virtual number, Node.js and npm installed, ngrok for development, and a basic understanding of Node.js, Express, and APIs. The guide details how to acquire and configure each of these elements.
Vonage Messages API webhook security best practices?
Implement JWT verification for all webhook endpoints to ensure requests are from Vonage. Regularly check and update your private key. Refer to Vonage's official documentation for the most accurate and up-to-date security recommendations.
When should I use the Messages API instead of the SMS API?
The Messages API is Vonage's recommended approach for modern SMS integrations. It offers a unified API for various channels, including SMS, provides JWT-based webhook security, and has webhooks for inbound messages and delivery statuses. The SMS API is being superseded by the Messages API. For new projects, always use the Messages API.
What are the Vonage supported SMS number formats?
The guide recommends E.164 format for phone numbers (e.g., +14155552671). While the Messages API may accept other formats, using E.164 promotes consistency and avoids potential issues.
How to handle Vonage Messages API errors in Node.js?
Use `try...catch` blocks around API calls and within webhook handlers. Log detailed error information from `err.response.data` when available. Return appropriate HTTP status codes to indicate errors, distinguishing between bad user input (4xx errors) and server-side or Vonage issues (5xx errors).