Frequently Asked Questions
Use the Vonage Node.js SDK and the vonage.sms.send() method. This method requires the recipient's number, your Vonage virtual number, and the message text. Ensure your API key and secret are configured correctly in your .env file and loaded into your application.
A Delivery Receipt (DLR) is a status update from the carrier network confirming the delivery status of your SMS message. Vonage forwards these updates to your application via webhooks, providing information like 'delivered', 'failed', or 'expired'.
Webhooks provide a real-time, asynchronous mechanism for Vonage to push delivery status updates to your application. This avoids the need for your application to constantly poll Vonage for status information.
Configure your webhook URL in the Vonage Dashboard before sending SMS messages if you want to track their delivery status. This ensures you receive DLRs as soon as they're available from the carriers.
Yes, ngrok creates a public HTTPS URL that tunnels requests to your local development server, allowing Vonage to deliver webhooks even if your server isn't publicly accessible.
Create an endpoint in your Express app (e.g., /webhooks/dlr) to listen for POST requests. Parse the request body, log the DLR information, and always respond with a 2xx status code to acknowledge receipt.
The messageId in the DLR corresponds to the message-id returned by the Vonage SMS API when you send a message. This ID is crucial for correlating DLRs with the original outbound messages.
Common status codes include 'delivered', 'failed', 'expired', 'buffered', 'rejected', 'accepted', and 'unknown'. Check the Vonage API reference for a comprehensive list and explanations of error codes.
Vonage retries sending the webhook if it doesn't receive a 2xx response quickly. Make your webhook handler idempotent so that processing the same DLR multiple times has no adverse effects. Use the messageId to check if the DLR has been processed before and skip the update if needed
Check your ngrok tunnel, server logs, and Vonage Dashboard settings. Ensure the webhook URL is correct and points to the right endpoint. Verify your server is running and responding with a 2xx status code.
Use HTTPS. Vonage signed webhooks with a JWT signature are supported by the Messages API but are not generally supported for standard SMS API DLRs. Use a rate limiter (e.g., express-rate-limit) to prevent abuse. Secure your API Key and secret. Though IP whitelisting is possible, it is less flexible due to potential IP range changes and generally avoided in favor of other security methods
Store message information and DLR statuses in a database. Create a table with fields for vonage_message_id (unique and indexed), status, error_code, timestamps, etc. Use the messageId from the DLR to update the corresponding record.
DLR delays depend on carrier network reporting times and can vary from seconds to minutes or occasionally longer. Design your application to tolerate these delays.
Not all carriers reliably support DLRs. Don't rely solely on DLRs for critical application logic. Check the recipient number and carrier capabilities. If issues persist, contact Vonage support.
Send SMS and Receive Delivery Status Callbacks with Node.js, Express, and Vonage
This guide provides a complete walkthrough for building a Node.js application using the Express framework to send SMS messages via the Vonage API and reliably receive delivery status updates (Delivery Receipts or DLRs) through webhooks. We'll cover everything from project setup to handling DLRs, ensuring you have a production-ready foundation.
You'll learn how to configure Vonage, send messages programmatically, set up a webhook endpoint to receive status updates, and handle potential issues. By the end, you'll have a functional application capable of sending SMS and tracking its delivery status.
Key Technologies:
System Architecture:
Prerequisites:
1. Setting up the Project
Let's create the project structure and install the necessary dependencies.
Create Project Directory: Open your terminal or command prompt and create a new directory for your project_ then navigate into it.
Initialize Node.js Project: Initialize the project using npm. The
-yflag accepts default settings.This creates a
package.jsonfile.Install Dependencies: We need the Vonage Server SDK for interacting with the API_ Express for our web server_ and
dotenvto manage environment variables securely.Create Project Files: Create the main files for our application logic and environment variables.
(Note for Windows CMD users: If
touchis not available_ you can usetype nul > filenamefor each file or create them manually in your editor.)index.js: Will contain the code to send SMS messages.server.js: Will contain the Express server code to receive DLR webhooks..env: Will store sensitive credentials like API keys and phone numbers..gitignore: Specifies files that Git should ignore (like.envandnode_modules).Configure
.gitignore: Addnode_modulesand.envto your.gitignorefile to prevent committing them to version control.Configure Environment Variables (
.env): Open the.envfile and add your Vonage credentials and configuration. Replace the placeholder values with your actual data.VONAGE_API_KEY,VONAGE_API_SECRET: Found on the main page of your Vonage API Dashboard.VONAGE_NUMBER: One of your virtual numbers purchased from Vonage (Vonage Dashboard > Numbers > Your Numbers). Ensure it's SMS-capable. Use E.164 format (e.g.,14155550100).TO_NUMBER: The destination phone number for your test SMS, also in E.164 format.PORT: The local port ngrok will expose and your Express server will listen on.3000is a common choice.2. Implementing Core Functionality: Sending SMS
Now, let's write the code in
index.jsto send an SMS message using the Vonage SDK.Edit
index.js: Openindex.jsand add the following code:Explanation:
require('dotenv').config();: Loads the variables defined in your.envfile intoprocess.env.process.env.VONAGE_API_KEY, etc.). Includes basic validation.Vonageclient using your API Key and Secret.sendSmsFunction:vonage.sms.send(). This method is specifically designed for the Vonage SMS API.to,from, andtextproperties.async/awaitfor cleaner handling of the promise returned by the SDK.messagesarray. We check thestatusof the first message ('0'indicates success) and log themessage-idwhich is crucial for correlating with Delivery Receipts later. We also log anyerror-textif the status is not'0'.try...catchblock to gracefully handle network issues or API errors during the send process. It attempts to log detailed error information if available from the Vonage SDK error object.sendSms()function to initiate the process.Test Sending: Make sure you've saved your
.envfile with the correct credentials and numbers. Run the script from your terminal:You should see output indicating the message was sent successfully and receive an SMS on the
TO_NUMBERphone. Keep the loggedMessage IDhandy – you'll see it again when the DLR comes back.3. Building the API Layer: Webhook for Delivery Receipts
Now, we'll create the Express server in
server.jsto listen for incoming POST requests from Vonage containing the DLRs.Edit
server.js: Openserver.jsand add the following code:Explanation:
express.json()andexpress.urlencoded({ extended: true })are crucial for parsing incoming request bodies. Vonage DLRs typically arrive asapplication/x-www-form-urlencoded, but supporting JSON is good practice./webhooks/dlr):app.all()to handle both GET and POST requests on this path, though Vonage primarily uses POST for DLRs.req.queryandreq.bodyinto a singleparamsobject to handle data regardless of the HTTP method used by Vonage (though POST body is standard for DLRs).messageId,status,msisdn,err-code.// TODO:comment indicating where you would add your application-specific logic (e.g., updating a database record with the delivery status).res.status(200).send('OK');: This is vital. You must acknowledge receipt of the webhook to Vonage with a2xxstatus code. Failing to respond or responding with an error code will cause Vonage to retry delivering the webhook./health): A simple GET endpoint useful for verifying the server is running and accessible.PORT.4. Integrating with Vonage: Configuring Webhooks
To receive DLRs, you need to tell Vonage where to send them. This involves exposing your local server using ngrok (during development) and configuring the webhook URL in the Vonage Dashboard.
Start Your Local Server: Open a terminal window in your project directory and run:
You should see
Server listening for webhooks at http://localhost:3000.Expose Local Server with ngrok: Open a second terminal window. Run ngrok to create a public URL tunnel to your local port
3000(or whichever port your server is using, matching thePORTin your.env).ngrok will display output similar to this:
Copy the
https://<random_string>.ngrok-free.appURL. This is your public base URL.Configure Vonage SMS Settings:
https://<random_string>.ngrok-free.app/webhooks/dlrVerification:
server.jsshould be running.3000.5. Implementing Error Handling and Logging
Robust error handling and logging are crucial for production systems.
SMS Sending Errors (
index.js):try...catchblock inindex.jsalready provides basic error handling for the API call itself.message['error-text']) if the API indicates a failure (status !== '0').Webhook Handling Errors (
server.js):200 OKor204 No Contentto Vonage before undertaking potentially slow or fallible processing. This prevents Vonage retries due to timeouts. The example below demonstrates this "respond first" pattern.// TODO:section) in its owntry...catchblock. If your internal processing fails (e.g., database error), log the error comprehensively, but ensure the 2xx response has already been sent to Vonage. You'll need separate monitoring/alerting for failures in your internal processing logic.Vonage Retries: Be aware that if Vonage doesn't receive a
2xxresponse from your webhook endpoint within a reasonable timeframe (usually a few seconds), it will retry sending the DLR. Your webhook handler should be idempotent – meaning processing the same DLR multiple times should not cause adverse effects (e.g., use themessageIdto ensure you only update a status once, or use database transactions appropriately). The "respond first" pattern helps avoid retries due to processing time but doesn't inherently make the processing logic idempotent.6. Database Schema and Data Layer (Conceptual)
While this guide doesn't implement a full database layer, here's a conceptual schema for storing message and delivery status information:
Entity Relationship Diagram (Conceptual):
Data Access:
index.js), you would insert a new record into theMessagestable with an initial status like'submitted', storing thevonage_message_idreturned by the API. You'd get back theinternal_message_id.server.js) receives a status update, you would use themessageIdfrom the payload (which corresponds tovonage_message_idin your table) to find the relevant record in yourMessagestable and update thestatus,last_status_update_at,error_code, andpricefields accordingly.Tools:
sequelize-clior Prisma Migrate help manage database schema changes over time.7. Adding Security Features
Securing your application, especially the webhook endpoint, is crucial.
Secure Credential Storage: Already addressed by using
.envand.gitignore. Never commit secrets to version control. Use environment variable management provided by your hosting platform for production (VONAGE_API_KEY,VONAGE_API_SECRET, etc.).Webhook Security:
/webhooks/dlr/aBcD3fG7hJkL9mN1oPqR) offers minimal protection but can deter casual scanners.Rate Limiting: Protect your webhook endpoint from abuse or potential retry storms by implementing rate limiting. The
express-rate-limitpackage is a popular choice.Input Validation: While the DLR payload comes from Vonage, it's still good practice to validate expected fields and types before processing, though less critical than user-submitted data.
8. Handling Special Cases Relevant to SMS DLRs
Consider these real-world scenarios:
delivered: Message successfully reached the handset.failed: Message could not be delivered (checkerr-codefor reason).expired: Message delivery timed out (often handset off or out of coverage).buffered: Message is waiting on the carrier network (temporary state).rejected: Message was rejected by Vonage or the carrier (often due to spam filters, invalid number, or permissions).accepted: Message accepted by the downstream carrier, delivery pending.unknown: The final status couldn't be determined.err-codemeanings.deliveredstatus. Sometimes, a message might be delivered without a DLR ever arriving.messageIdout of sequence (e.g.,bufferedthendelivered). Ensure your status update logic handles this gracefully (e.g., only update if the new status is considered 'more final' than the current one, perhaps using timestamps).message-id. You might receive separate DLRs for each part. You may need logic to determine the overall delivery status based on the statuses of all parts.9. Implementing Performance Optimizations
For high-volume applications, optimize your webhook handler.
2xximmediately using the "respond first" pattern.2xx.Messagestable (or equivalent) has an index on thevonage_message_idcolumn (as shown in the conceptual schema) for fast lookups when processing DLRs.server.jsapplication behind a load balancer and run multiple instances (e.g., using PM2 cluster mode or container orchestration like Kubernetes). Ensure your processing logic (especially database updates) can handle concurrent operations correctly (idempotency, transactions).10. Adding Monitoring, Observability, and Analytics
Understand how your SMS sending and DLR processing are performing.
/healthendpoint inserver.jsis a basic start. Production health checks might involve checking database connectivity or queue status.index.js) and DLR processing (server.js).vonage.sms.send().delivered,failed, etc.)./webhooks/dlr).try...catchblock inserver.js).prom-clientfor Prometheus metrics or platform-specific monitoring agents (Datadog Agent, etc.).index.jsandserver.js, especially the internal DLR processing errors.11. Troubleshooting and Caveats
Common issues and things to be aware of:
node server.jsrunning? Any startup errors? Check the console logs for incoming requests and processing logs.node index.js. Watch both theserver.jsconsole and the ngrok console (http://127.0.0.1:4040) for incoming requests to/webhooks/dlr.VONAGE_API_KEYandVONAGE_API_SECRETin your.envfile are correct and have no extra spaces.fromNumber: EnsureVONAGE_NUMBERis a valid, SMS-capable number purchased in your Vonage account and formatted correctly (E.164).2xxquickly.