Frequently Asked Questions
Use the Vonage Messages API with the @vonage/server-sdk in your Node.js application. The sendSms function demonstrates how to send text messages by specifying the recipient, sender, and message content. Remember to set up your API key, secret, application ID, private key path, and phone numbers correctly in your .env file.
The Vonage Messages API is a unified interface for sending various types of messages, including SMS. It offers robust features and multi-channel capabilities, making it suitable for applications needing reliable messaging and status tracking.
Webhooks provide real-time delivery status updates, including 'delivered,' 'failed,' or 'rejected,' directly to your application. This approach eliminates the need for constant polling and allows for immediate responses to message statuses, enabling better reliability and logging.
ngrok is essential during local development to expose your local server and receive webhooks from Vonage. For production deployments, you must use your server's public HTTPS URL configured in your Vonage application settings.
Yes, by setting up a webhook endpoint (e.g., /webhooks/status) in your Node.js application. Vonage will send real-time status updates to this endpoint, which you can then process to track deliveries, failures, and other events.
First, install the @vonage/server-sdk package. Then, configure your Vonage account, create an application, and obtain the necessary credentials (API Key, API Secret, Application ID, and Private Key). Finally, initialize the Vonage client in your Node.js code using these credentials.
The private key, along with the application ID, is crucial for authenticating your Node.js application with the Vonage Messages API and is the standard method when an application context is needed. This ensures secure communication and prevents unauthorized access to your account.
Responding with a 2xx status code (like 200 OK) is mandatory to acknowledge successful receipt of the Vonage webhook. Failure to respond correctly will cause Vonage to retry the webhook, leading to potential duplicate processing.
Design your webhook handler to be idempotent using the message_uuid, ensuring it can process the same status update multiple times without causing issues. If processing is lengthy, respond with 200 OK immediately and process asynchronously.
Check the error code and reason in the webhook payload. Common reasons include an invalid recipient number, the recipient blocking the number, carrier-specific restrictions, or insufficient funds in your Vonage account.
Store the message UUID, sender and recipient numbers, content, timestamps, status updates, and any error codes. Consider the provided conceptual database schema as a starting point.
Consult the Vonage Messages API documentation. They might use HMAC-SHA256 with your API secret or a dedicated webhook signing secret. Check if the Vonage SDK offers helper functions for signature verification.
Long SMS are split, but share a message UUID. Status updates may be per part or as a whole. Your logic should accommodate this, potentially grouping status updates by message UUID.
Implement webhook signature verification to prevent unauthorized requests. Use HTTPS, IP whitelisting if possible, and input sanitization to minimize security risks.
Vonage retries webhook delivery if your endpoint doesn't return a 2xx HTTP status code within a short time frame, indicating that the message hasn't been processed successfully.
Developer guide: Sending SMS and receiving delivery status callbacks with Node.js, Express, and Vonage
Build a Node.js application using Express to send SMS messages via the Vonage Messages API and receive delivery status webhooks with real-time tracking.
What are delivery status callbacks? When you send an SMS, you need to know whether it reached the recipient. Delivery status callbacks (also called Delivery Receipt Reports or DLRs) are HTTP POST requests Vonage sends to your server containing the message status –
delivered,failed,rejected, or other states. Without these callbacks, you're sending messages blind with no confirmation of successful delivery.Why do you need them? Track delivery success rates, alert customers when critical messages fail, implement retry logic for failed sends, maintain audit logs for compliance, and provide users with real-time delivery feedback.
Project Overview and Goals
Build a Node.js application that demonstrates 2 core functionalities:
delivered,failed,rejected) for the messages you send.Track delivery success for critical notifications like password resets, 2FA codes, appointment reminders, order confirmations, and payment alerts where knowing the delivery status directly impacts your application's reliability and user experience.
Technologies Used:
@vonage/server-sdk: The official Vonage Node.js SDK (version 3.24.1 as of January 2025) for interacting with the API.dotenv: A module to load environment variables from a.envfile for secure credential management.ngrok: A tool to expose local development servers to the internet, necessary for testing webhooks during local development. Production deployments use your server's public URL.System Architecture:
(Note: For published documentation_ replace this ASCII diagram with an image for consistent rendering across platforms.)
Final Outcome:
By the end of this guide_ you'll have a functional Node.js application capable of:
/webhooks/status).Expected time: 45–60 minutes for developers familiar with Node.js and Express. If you're new to webhooks or SMS APIs_ allow 90 minutes.
Prerequisites:
ngrok: Install and authenticate. Download ngrok (A free account is sufficient).Pricing: Vonage charges per SMS sent (~$0.0070–$0.15 depending on destination country). New accounts receive free trial credit ($2.00 as of January 2025). Check current pricing at Vonage SMS Pricing.
1. Node.js Project Setup and Dependency Installation
Initialize the project_ install dependencies_ and set up the basic structure.
Create Project Directory: Open your terminal and create a new directory for your project_ then navigate into it.
Initialize Node.js Project: Initialize npm to create a
package.jsonfile.Install Dependencies: Install
expressfor the web server_@vonage/server-sdkto interact with the Vonage API_ anddotenvto manage environment variables.Note: This installs
@vonage/server-sdkversion 3.24.1 (latest as of January 2025). This version uses TypeScript for improved code completion and follows modern async/await patterns.Create Project Structure: Create the necessary files and directories.
index.js: Main file for the Express server (webhook listener).send-sms.js: Script to trigger sending an SMS message..env: Stores sensitive credentials (API keys_ etc.)..gitignore: Specifies files/directories Git should ignore.Configure
.gitignore: Addnode_modulesand.envto your.gitignorefile to prevent committing dependencies and sensitive credentials.Note: Add
private.keyassuming you might save the downloaded key file directly in the project root during development.Warning: While convenient for local development_ storing private keys directly in the project folder carries risks. Never commit your private key file to version control (Git). Ensure
.gitignorecorrectly lists the key file name.Configure
.envFile: Open the.envfile and add placeholders for your Vonage credentials. Fill these in later.YOUR_API_KEY_HERE_YOUR_API_SECRET_HERE_YOUR_APPLICATION_ID_HERE_ and the phone numbers with your actual values.14155552671for a US number.2. Vonage Account Configuration and API Credentials
Configure your Vonage account_ create an application_ and obtain the necessary credentials.
Log in to Vonage Dashboard: Access your Vonage API Dashboard.
Find API Key and Secret: Your API Key and Secret appear prominently on the main dashboard page. Copy these values and paste them into your
.envfile forVONAGE_API_KEYandVONAGE_API_SECRET.Set Default SMS API: Ensure your account uses the Messages API for sending SMS_ as this guide relies on it.
Create a Vonage Application: The Messages API requires an application context for authentication using a private key.
SMS Status Guide App).private.keyfile. Save this file securely. For development_ place it in your project's root directory (as configured in.env). Vonage typically names thisprivate.key– ensure the path you set in.envmatches the exact filename and location where you saved it.ngrokURL. For now_ leave them blank or enter temporary placeholders likehttp://example.com/status..envfile forVONAGE_APPLICATION_ID.Link Your Vonage Number: Link your Vonage virtual number to the application you just created.
.envfile forVONAGE_NUMBER.Update
.envwith Private Key Path: EnsureVONAGE_PRIVATE_KEY_PATHin your.envfile correctly points to where you saved the downloaded private key file (e.g.,./private.keyif it's in the root and namedprivate.key). Match the actual filename if it differs.Your
.envfile should now contain your actual credentials (except for the webhook URLs, handled later).3. Implementing SMS Sending with Vonage Messages API
Write the script to send an SMS message using the Vonage Node.js SDK and the Messages API.
Edit
send-sms.js: Open thesend-sms.jsfile and add the following code:Code Explanation:
require('dotenv').config(): Loads variables from the.envfile intoprocess.env.process.env. Includes basic validation.AuthwithapplicationIdandprivateKey– this is the required authentication method for sending via the Messages API when an application context is needed (standard practice). While the API Key/Secret are included in theAuthobject, the Application ID and Private Key primarily drive authentication for this Messages API operation; the Key/Secret might be leveraged by other SDK functionalities or legacy API interactions.new Vonage(credentials, options)creates the Vonage client instance.sendSmsFunction:asyncfunction handles the asynchronous API call.vonage.messages.send({...}): The core method call.message_type: "text": Specifies a standard text message.text: The SMS content. Maximum 160 characters for single-segment SMS using GSM-7 encoding. Longer messages split into multiple segments (up to 1,530 characters across 10 segments). Using Unicode characters reduces limit to 70 characters per segment.to: The recipient's phone number (from.env).from: Your Vonage virtual number (from.env).channel: "sms": Explicitly defines the channel.messageUuid– a unique identifier (format:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) that tracks this specific message through its lifecycle. Store this UUID to correlate webhook status updates with sent messages.try...catchblock to catch errors during the API call. Logs detailed error information if available in the Vonage response (err.response.data).async sendSmsfunction immediately when you execute the script (node send-sms.js).4. Building Express Webhook Endpoint for Delivery Status Callbacks
Create the Express server and the webhook endpoint (
/webhooks/status) to receive delivery status updates from Vonage.Edit
index.js: Openindex.jsand add the following code for the Express server:Code Explanation:
express.json()before route definitions to ensure request bodies parse correctly before handlers execute.express.json(): Parses incoming JSON request bodies (Vonage status webhooks use JSON).express.urlencoded(): Parses URL-encoded bodies (less common for status webhooks but included for completeness)./webhooks/statusEndpoint (POST):req.body: Contains the JSON payload from Vonage.message_uuid,status,timestamp– crucial for debugging.if/else ifblocks demonstrating how you might react to different statuses (delivered,failed,rejected). Integrate with your application's logic here (e.g., updating a database). TheIMPLEMENTATION POINTcomments highlight these areas.res.status(200).send('OK')): Critically important. You must respond with a2xxstatus code (like 200 or 204) quickly. If Vonage doesn't receive a timely2xxresponse, it assumes delivery failed and retries sending the webhook, leading to duplicate processing./healthEndpoint (GET): A simple endpoint to check if the server is running.Complete webhook payload structure:
Timeout: Vonage waits 10 seconds for your webhook endpoint to respond with a
2xxstatus code. If your endpoint doesn't respond within 10 seconds, Vonage retries the webhook. Implement retry logic as described in Section 6.5. Local Development with ngrok Tunneling for Webhook Testing
To receive webhooks from Vonage on your local machine, expose your local server to the internet using
ngrok.Start Your Local Server: Open a terminal in your project directory and run:
You should see "Server listening on port 3000…" (or your configured port).
Start
ngrok: Open a second terminal window (leave the first one running your server). Runngrokto tunnel traffic to your local server's port (e.g., 3000).Get
ngrokURL:ngrokdisplays output similar to this:Copy the
https://<random-string>.ngrok-free.appURL. This is your public webhook URL.Update Vonage Application Status URL:
SMS Status Guide App).ngrokURL into the Status URL field, appending/webhooks/status. It should look like:https://<random-string>.ngrok-free.app/webhooks/statushttps://<random-string>.ngrok-free.app/webhooks/inboundif you plan to handle incoming SMS replies later (requires adding a/webhooks/inboundroute inindex.js).Verification: Send a Test SMS:
Go back to the terminal where your
node index.jsserver is not running.Run the send script again:
Note the
messageUuidlogged by the script.Observe Webhook:
node index.jsserver is running.message_uuidin the webhook payload matches the one logged bysend-sms.js.statusfield (e.g.,submitted,delivered,failed). You might receive multiple updates for a single message as it progresses.Inspect with
ngrokWeb Interface: Open thengrokWeb Interface (usuallyhttp://127.0.0.1:4040) in your browser to see incoming requests to your tunneled endpoint in real-time, inspect headers, and replay requests for debugging.Troubleshooting ngrok connection issues:
ngrok versionto verify.node index.jsin a separate terminal.6. Production-Ready Error Handling and Retry Logic
While basic logging is in place, enhance it for production:
Implement error handling middleware:
Robust Logging: Replace
console.logwith a structured logging library like Winston or Pino. Log levels (info, warn, error), timestamps, and request IDs make debugging easier. Log status updates to a persistent store or logging service.Webhook Retries: Vonage retries webhook delivery using this policy:
2xxEnsure your endpoint responds quickly. If your processing logic is slow, acknowledge the request immediately (
res.status(200).send()) and perform the processing asynchronously (e.g., using a job queue like BullMQ or Redis Queue).Idempotency: Design your webhook handler to be idempotent. If Vonage retries a webhook (e.g., due to a temporary network issue), your handler might receive the same status update multiple times. Use the
message_uuidand potentially thetimestampor a unique webhook attempt ID (if provided by Vonage) to ensure you don't process the same update twice. Store processedmessage_uuid+statuscombinations temporarily if needed.Example idempotency check:
7. Webhook Security: JWT Signature Verification and Best Practices
Securing your webhook endpoint is crucial:
Webhook Signature Verification (Highly Recommended): Vonage uses JWT (JSON Web Token) Bearer Authorization with HMAC-SHA256 to sign webhook requests, allowing you to verify they originated from Vonage.
How Vonage Webhook Security Works:
Authorization: Bearer <token>headerpayload_hashclaim (SHA-256 hash of the webhook body). Hash the incomingreq.bodyand compare to this claim to detect tamperingiat(issued at) claim is a UTC Unix timestamp. Compare to current time to reject stale/replayed tokensImplementation Example:
Note: Install
jsonwebtokenpackage:npm install jsonwebtokenHTTPS:
ngrokprovides HTTPS URLs, and your production deployment must use HTTPS to protect data in transit.IP Allowlisting: As an additional layer (or alternative if signature verification isn't feasible), configure your firewall or load balancer to only allow requests to your webhook endpoint from Vonage's known IP address ranges. Find current IP ranges at Vonage IP Ranges Documentation.
Input Sanitization: Although the data comes from Vonage, sanitize any data from the webhook before using it in database queries or other sensitive operations to prevent potential injection attacks if the data format unexpectedly changes.
Rate Limiting: Implement rate limiting on your webhook endpoint (e.g., using
express-rate-limit) to prevent potential abuse or denial-of-service if your endpoint is exposed accidentally.Rate limiting example:
8. Database Schema Design for SMS Message Tracking
For production use, store SMS details and status updates.
Why Store Data? Track message history, auditing, analytics, provide status feedback to users, handle retries based on failure codes.
Example Schema (Conceptual):
Performance considerations:
recipient_number,last_status,submitted_at,message_uuidJSONBforraw_payloadto enable efficient querying of webhook dataVARCHARlengths to optimize storageData retention: Implement a cleanup strategy to archive or delete old records:
Archive messages older than 90 days to separate cold storage
Delete status updates for archived messages after 1 year
Use scheduled jobs (cron or database triggers) for automated cleanup
Implementation:
send-sms.js): Insert a new record intosms_messageswith themessage_uuidand initial details.index.js):sms_messagesusingmessage_uuid.sms_status_updates.last_status,last_status_timestamp, and potentiallyfinal_status,error_code,error_reasonin thesms_messagestable.9. Handling Special SMS Delivery Cases and Edge Scenarios
submitted,delivered,failed,expired,rejected,accepted,unknown, etc.). Handlefailedandrejectedspecifically, potentially logging theerror.codeanderror.reasonprovided in the webhook. Consult the official Vonage documentation for detailed explanations of all status and error codes.message_uuid. Your status updates might reflect individual parts or the message as a whole depending on the carrier and Vonage processing.timestampfield) are typically in UTC. Store them appropriately (e.g.,TIMESTAMPTZin PostgreSQL) and convert to local timezones only for display purposes.unknownstatus means the carrier didn't provide delivery confirmation (common in certain regions). Treat as uncertain rather than failed. Don't automatically retry, but log for monitoring. If many messages showunknown, consider alternative carriers or channels for that destination.10. Application Monitoring and Performance Metrics
/healthendpoint is a basic start. Production systems often require more detailed checks (e.g., database connectivity).prom-clientto expose metrics for Prometheus/Grafana.Metrics implementation example:
11. Vonage Delivery Status Codes and Error Reference
Understanding Vonage's Delivery Receipt (DLR) status codes is essential for proper error handling and monitoring.
Complete DLR Status Codes Reference
submitteddelivered(DELIVRD)failed(FAILED)rejected(REJECTD)expired(EXPIRED)undelivered(UNDELIV)accepted(ACCEPTD)unknown(UNKNOWN)deleted(DELETED)Source: Vonage DLR Statuses Documentation (accessed January 2025)
SMS Error Codes and Troubleshooting Guide
When status is
failedorrejected, the webhook includes anerrorobject withcodeandreasonfields:Important: Vonage charges for messages regardless of final delivery status, except when rejected by the Vonage platform before submission (certain error codes like 2, 3, 4).
Source: Vonage SMS Delivery Error Codes (accessed January 2025)
12. Common Issues and Debugging Solutions
ngrokIssues:ngrokis running and the correct URL (HTTPS) is configured in the Vonage dashboard Status URL.ngrokAccount Limitations (2025):ngrokaccounts are sufficient for development but consider paid plans for production-like testing or higher bandwidth needs.ngrok..envvalues. Ensure the private key file path (VONAGE_PRIVATE_KEY_PATH) is correct, matches the actual filename, and the file is readable by your application.https://…/webhooks/status) in the Vonage application settings.node index.jsoutput) for errors during webhook processing.node send-sms.jsoutput) for API errors during sending.dotenvis loading variables correctly (console.log(process.env.VONAGE_API_KEY)early in the script to test).send-sms.jslog formessageUuid).ngrokis running and accessible.failed/rejectedstatus):error.codeanderror.reasonin the webhook payload.vonage.messages.sendonly means Vonage accepted the message for delivery, not that it was delivered. The webhook provides the actual delivery status.Manual webhook testing with curl:
13. Production Deployment and CI/CD Pipeline Setup
.envvariables securely using your hosting provider's mechanism (e.g., Heroku Config Vars, AWS Secrets Manager, Docker secrets). Never commit.envfiles or private keys to Git.web: node index.js), push code, set environment variables via the dashboard/CLI.Dockerfileto containerize the application. Manage theprivate.keysecurely (e.g., mount as a volume or use Docker secrets).ngrokURL in the Vonage Application Status URL settings. Ensure it's HTTPS.VONAGE_PRIVATE_KEY_PATHenvironment variable. Do not store it in version control.Example Dockerfile:
Example GitHub Actions workflow:
Frequently Asked Questions About Vonage SMS Delivery Status
How do I track SMS delivery status with Vonage?
Configure a webhook endpoint in your Vonage Application settings to receive delivery status callbacks. Vonage sends POST requests with status updates (
submitted,delivered,failed,rejected) to your webhook URL containing themessage_uuidand status information.What is the difference between message submitted and delivered status?
submittedmeans Vonage accepted your message and sent it to the carrier network.deliveredconfirms the carrier successfully delivered the message to the recipient's device. Always wait fordeliveredstatus to confirm actual delivery.How long does it take to receive delivery status webhooks?
Delivery status webhooks typically arrive within seconds to minutes after sending. However, carrier delays can extend this to several minutes or hours. Some carriers in certain countries may not provide delivery receipts at all.
Why am I not receiving webhooks from Vonage?
Common causes: incorrect webhook URL in Vonage dashboard,
ngroknot running, webhook endpoint not responding with2xxstatus code, firewall blocking requests, or your SMS was never successfully sent (check send script logs for errors).How do I verify webhook requests are from Vonage?
Implement JWT signature verification using the signature secret from your Vonage Application settings. Verify the JWT token in the
Authorizationheader, validate thepayload_hashclaim matches your request body hash, and check theiattimestamp is recent (within 5 minutes).What HTTP status code should my webhook return?
Always return a
2xxstatus code (typically200 OKor204 No Content) immediately to acknowledge receipt. If you don't respond with2xx, Vonage assumes delivery failed and retries the webhook, causing duplicate processing.Can I test webhooks locally without deploying?
Yes, use
ngrokto create a public HTTPS tunnel to your local development server. Configure thengrokURL in your Vonage Application Status URL setting to receive webhooks on your local machine during development.What does error code 10 mean in delivery status?
Error code 10 ("Illegal sender") means the sender ID or phone number is not allowed for the destination country. Some countries require pre-registered sender IDs or only allow messages from local numbers.
How do I handle duplicate webhook deliveries?
Design your webhook handler to be idempotent. Store processed
message_uuid+statuscombinations and check before processing. Vonage retries webhooks if it doesn't receive a2xxresponse, so duplicates can occur during network issues.Does Vonage charge for failed messages?
Yes, Vonage charges for messages regardless of final delivery status, except when rejected by the Vonage platform before submission (error codes 2, 3, 4). Carrier rejections and delivery failures still incur charges.