Frequently Asked Questions
Set up a webhook endpoint in your application to receive DLRs. Configure your Vonage account settings to send DLRs to this endpoint. Use a tool like ngrok to expose your local development server during testing. Ensure your endpoint responds with a 2xx status code to acknowledge receipt.
A DLR is a notification from Vonage that provides the delivery status of an SMS message. It confirms whether the message was successfully delivered, failed, or encountered another status like "buffered" or "expired". This allows for real-time tracking and handling of message delivery outcomes.
DLR webhooks provide real-time delivery status updates, going beyond the initial confirmation from the Vonage API. This enables accurate delivery tracking, handling failed messages, and gathering analytics on SMS campaign effectiveness, leading to a more robust and user-friendly application.
Use a switch statement or similar logic in your webhook handler to process different statuses such as 'delivered', 'failed', 'expired', etc. 'delivered' indicates success. 'failed' might require retries or support notifications. Check the 'err-code' for failure reasons. Always respond with a 2xx status code, even if your internal handling fails.
Common statuses include 'delivered', 'failed', 'expired', 'rejected', 'accepted', 'buffered', and 'unknown'. Consult Vonage documentation for the complete list and details. 'delivered' signifies successful delivery. 'failed' implies delivery failure. 'buffered' means temporary holding by the network.
Run your Node.js application and ensure ngrok is forwarding to the correct port. Send a test SMS from your Vonage number to a real mobile number. Observe the ngrok terminal for incoming webhook requests and the Node.js console for the DLR payload. Ensure your Vonage account settings point to the correct ngrok URL.
Secure your webhooks using HTTPS and a non-guessable URL path. While not foolproof, checking the 'api-key' in the payload against your Vonage key provides a basic verification step. Implement rate limiting to prevent abuse. Consult Vonage documentation for signed webhook options for added security.
Use express.json() middleware before defining your webhook route. It's crucial because Vonage sends DLR data as JSON in the POST request body. This middleware parses incoming JSON payloads and makes the data accessible on req.body for processing within your route handler.
Vonage retries webhooks if it doesn't receive a 2xx success status code (like 200 or 204) within its timeout period. Ensure your endpoint responds with a success code even if your application logic has errors. Handle any errors asynchronously after acknowledging receipt.
Verify that ngrok is running and your Vonage settings are pointed to the correct ngrok forwarding URL, including the path. Confirm that the SMS was sent from the Vonage number linked to the configured API key. Check firewall settings. Ensure the server is running and your webhook route is correctly defined.
Ngrok creates a secure tunnel from a public URL to your local development server. This allows Vonage to send webhook requests to your application running locally during development, bypassing the need for a publicly accessible server.
The 'err-code' field in the DLR payload provides specific error information when the 'status' is 'failed' or 'rejected'. A value of '0' generally indicates success. Other codes indicate different failure reasons, which can be found in the Vonage API documentation.
Ensure express.json() middleware is used before defining your webhook route. Confirm the 'Content-Type' of the incoming request is 'application/json' using your ngrok inspection interface or server-side logging.
While the standard DLR applies to SMS, DLR behavior might differ for other channels like WhatsApp or Viber used through the Vonage Messages API. Refer to the specific channel's documentation within the Messages API for details on delivery receipts.
DLR reliability varies by country and carrier network. Not all networks provide timely or accurate DLRs, and some don't offer them at all. This is a limitation of SMS technology itself. Vonage documentation provides details on country-specific capabilities.
Implementing SMS Delivery Status Webhooks in Node.js with Vonage
Reliably sending SMS messages is crucial, but knowing if and when they arrive is equally important for many applications. Simply getting a successful API response when sending doesn't guarantee delivery to the end user's handset. Factors like network issues, invalid numbers, or carrier filtering can prevent successful delivery.
This guide provides a step-by-step walkthrough for building a robust webhook endpoint using Node.js and Express to receive real-time SMS Delivery Receipts (DLRs) from Vonage. This enables your application to track message status accurately, react to delivery failures, and provide better feedback to users or internal systems.
Project Overview and Goals
Goal: To create a Node.js Express application featuring a webhook endpoint that listens for, receives, processes, and logs SMS Delivery Receipts (DLRs) sent by the Vonage platform.
Problem Solved: This implementation provides near real-time visibility into the final delivery status of SMS messages sent via the Vonage API, moving beyond the initial ""message accepted by Vonage"" confirmation. This is essential for:
Technologies Involved:
.envfile intoprocess.env, keeping sensitive credentials out of source code.System Architecture:
Prerequisites:
1. Setting up the Project
Let's initialize our Node.js project and install the necessary dependencies.
Create Project Directory: Open your terminal or command prompt and create a new directory for the project, then navigate into it.
Initialize Node.js Project: This creates a
package.jsonfile to manage project dependencies and scripts.Install Dependencies: We need
expressfor the web server, anddotenvto manage environment variables securely.express: The web framework.dotenv: Loads environment variables from a.envfile.Create Core Files: Create the main application file and files for environment variables and Git ignore rules.
Configure
.gitignore: Addnode_modulesand.envto your.gitignorefile to prevent committing dependencies and sensitive credentials to version control.Set Up Environment Variables (
.env): Open the.envfile and add placeholders for your Vonage credentials and the port your server will run on.VONAGE_API_KEY/VONAGE_API_SECRET: Obtain these from your Vonage Dashboard. They authenticate your application with Vonage APIs (though not strictly needed for receiving this simple webhook, they are good practice to have configured).PORT: The local port your Express server will listen on. We'll use3000in this guide.Project Structure: Your basic project structure should now look like this:
This structure separates configuration (
.env) from code (index.js) and keeps sensitive data out of Git.2. Configuring Vonage and Ngrok
Before writing the webhook code, we need to tell Vonage where to send the delivery receipts. Since our application will run locally during development, we use
ngrokto create a temporary public URL that tunnels requests to our local machine.Retrieve Vonage Credentials:
.envfile, replacingYOUR_API_KEYandYOUR_API_SECRET.Ensure You Have a Vonage Number:
Start ngrok: Open a new terminal window (keep your project terminal open) and start
ngrok, telling it to forward HTTP traffic to the port defined in your.envfile (e.g., 3000).ngrokwill display output similar to this:https://Forwarding URL (e.g.,https://xxxxxxxxxxxx.ngrok.io). This is your temporary public URL. Keep thisngrokterminal window running. If you stop and restart ngrok, you will get a new URL and must update the Vonage configuration.Configure Vonage DLR Webhook URL:
ngrokhttps://Forwarding URL into the field./webhooks/delivery-receipts. The full URL will look like:https://xxxxxxxxxxxx.ngrok.io/webhooks/delivery-receiptsPOST. Vonage sends DLR data in the request body via POST.The ""SMS settings"" section contains fields for Default SMS Sender ID, Inbound SMS webhooks, and Delivery receipts (DLR) webhooks. You need to fill the DLR webhook field with your full ngrok URL + path and select POST.
Why this configuration? Now, whenever the delivery status of an SMS sent from your Vonage account changes, Vonage will make an HTTP POST request containing the DLR details to the public
ngrokURL you provided.ngrokwill then forward this request to your local Node.js application running on port 3000, specifically hitting the/webhooks/delivery-receiptsroute we will define next.3. Implementing the Webhook Endpoint
Now, let's write the Express code in
index.jsto create the server and handle incoming DLR webhooks.Code Explanation:
dotenvto access environment variables andexpress.express.json(): Parses incoming requests with JSON payloads and makes the data available onreq.body. This is essential as Vonage sends DLRs in JSON format via POST requests.express.urlencoded(): Parses incoming requests with URL-encoded payloads. While less common for DLRs, it's good practice to include for general webhook handling.handleDeliveryReceiptFunction:req.queryandreq.bodyinto a singleparamsobject to handle data regardless of the HTTP method (though DLRs primarily use POST/req.body).console.logwith a proper logging library as detailed in Section 6.messageIdandstatus.res.status(204).send();sends an HTTP204 No Contentstatus back to Vonage. This confirms successful receipt. If you don't send a2xxresponse, Vonage will assume the webhook failed and will retry sending it multiple times, potentially leading to duplicate processing in your application.app.route('/webhooks/delivery-receipts').get(...).post(...)sets up the listener for the specific path (/webhooks/delivery-receipts) you configured in the Vonage dashboard. It routes both GET and POST requests to the samehandleDeliveryReceiptfunction.app.listen()starts the Express server, making it ready to accept incoming connections on the specified port.4. Running and Testing the Application
Now, let's run the server and send an SMS to trigger a delivery receipt.
Run the Node.js Application: In your project terminal (
vonage-sms-dlr-webhookdirectory), start the server:You should see output confirming the server is running:
Ensure
ngrokis Still Running: Double-check that yourngrokterminal window (from Step 2.3) is still active and forwarding traffic.Send a Test SMS: You need to send an SMS from your Vonage virtual number to a real, reachable mobile number (like your own cell phone). The most straightforward ways to do this for testing are:
tofield, your Vonage number in thefromfield, add sometext, enter your API key and secret, and click ""Send request"". This uses the dashboard's built-in tool.@vonage/server-sdkfor Node.js) in a separate script or project to send the message programmatically. This is the recommended approach for actual applications as it handles authentication and API interaction more robustly.https://api.nexmo.com/v1/messages). Ensure you use secure authentication methods (like Basic Auth with API key/secret in headers) rather than including credentials in the request body.Important: Ensure the SMS is sent using the same Vonage account (API Key) for which you configured the DLR webhook URL in the settings.
Observe Webhook Activity:
ngrokTerminal: You should see an incoming POST request logged in thengrokterminal shortly after the SMS is delivered (or fails). It will showPOST /webhooks/delivery-receipts 204 No Content.node index.jsapplication should output the DLR payload received from Vonage.The output in your Node.js terminal will look similar to this (the exact fields might vary slightly, timestamps are examples):
Key DLR Fields:
messageId: The unique ID of the SMS message. Use this to correlate the DLR with the message you originally sent.status: The delivery status (e.g.,delivered,failed,expired,rejected,accepted,buffered). This is the most critical field.msisdn: The recipient's phone number.to: The sender ID or Vonage number used.err-code: Error code if the status isfailedorrejected.0usually indicates success.message-timestamporscts: Timestamp related to the status update from the carrier.network-code: Identifies the recipient's mobile network carrier.price: The cost charged for the message segment.api-key: The Vonage API key associated with the sent message.5. Handling Different Delivery Statuses
The
statusfield in the DLR payload tells you the final outcome of the message. Your webhook logic should handle these appropriately.Common Statuses:
delivered: The message was successfully delivered to the recipient's handset. This is the ideal outcome.accepted: The message was accepted by the downstream carrier but final delivery status is not yet known or available.buffered: The message is temporarily held by the network (e.g., handset offline) and delivery will be retried.failed: The message could not be delivered (e.g., invalid number, network issue, phone blocked). Checkerr-codefor details.expired: The message could not be delivered within the carrier's validity period (often 24-72 hours).rejected: The message was rejected by the carrier or Vonage (e.g., spam filtering, destination blocked). Checkerr-code.unknown: The final status could not be determined.Refer to the Vonage SMS API Documentation - Delivery Receipts for a complete list and detailed explanations.
Example Logic (Conceptual):
In your
handleDeliveryReceiptfunction, after parsingparams:6. Error Handling and Logging
Production applications require more robust error handling and structured logging than simple
console.log.Error Handling Strategy:
try...catchblocks around your database interactions or other critical processing within the webhook handler.2xx: Crucially, even if your internal processing fails, your webhook must still return a200 OKor204 No Contentto Vonage. This acknowledges receipt. Handle the processing failure asynchronously (e.g., retry mechanism with exponential backoff, dead-letter queue). Failure to respond2xxcauses Vonage retries, potentially exacerbating the problem.Logging:
Replace
console.logwith a structured logging library likepinoorwinston.Example using
pino:pino:logger.js):index.js:This provides structured JSON logs in production (good for log aggregation tools like Datadog, Splunk, ELK stack) and human-readable logs during development.
7. Security Considerations
Webhooks are public-facing endpoints, so securing them is important.
https://for your webhook URL (ngrokprovides this automatically for development). In production, ensure your server is configured for HTTPS using valid TLS/SSL certificates (e.g., via Let's Encrypt or your hosting provider).api-key(Basic Check, Not Foolproof): The DLR payload includes theapi-keyassociated with the message. You could check if this matches your expected Vonage API key (process.env.VONAGE_API_KEY). However, this is not strong security: the API key itself isn't secret if the transport isn't secure (hence HTTPS is vital), and simply checking the key doesn't guarantee the request came from Vonage (it could be replayed) or prevent tampering if the connection wasn't secure end-to-end. Treat this as a basic sanity check, not a primary security mechanism./webhooks/dlr/aBcD3fGhIjK1mN2oPqR3sT4uV5wX6yZ7) makes it harder for attackers to guess your endpoint URL. This is security through obscurity and should not be relied upon alone.express-rate-limit) to protect against brute-force attacks, accidental loops, or denial-of-service (DoS) attempts. Configure sensible limits based on expected traffic.messageId,status) before processing. This prevents errors if the payload structure changes unexpectedly or if malformed requests are received. Log any validation failures.For many use cases, HTTPS combined with rate limiting and potentially IP whitelisting provides a reasonable security baseline for standard DLRs. If strong guarantees of authenticity and integrity are required, investigate signed webhook options in the latest Vonage documentation.
8. Troubleshooting and Caveats
ngrok: Isngrokrunning? Is the Forwarding URL correct in Vonage? Has thengroksession expired or the URL changed (restartingngrokoften changes the URL)?https://, include your specific path like/webhooks/delivery-receipts, and have the method set toPOST)? Are changes saved?ngrokforwards to (e.g., 3000) or your production server's port?node index.jsapplication actually running and listening on the correct port without crashing? Check startup logs.req.bodyis empty/undefined):app.use(express.json());middleware is correctly included inindex.jsand, crucially, placed before your route definition (app.route('/webhooks/delivery-receipts')...).Content-TypeHeader: Check theContent-Typeheader of the incoming request from Vonage. Use thengrokweb interface (http://127.0.0.1:4040) or detailed logging to inspect the request headers. It should beapplication/jsonforexpress.json()to parse it. If it's something else (e.g.,application/x-www-form-urlencoded), you might needexpress.urlencoded()instead or investigate why Vonage is sending a different content type.2xxResponse: You are most likely not sending a successful HTTP status code (like200 OKor204 No Content) back to Vonage quickly enough or at all. Ensureres.status(204).send();(or similarres.sendStatus(204)) is called reliably at the end of your handler function, even if your internal processing encounters an error. Handle internal errors asynchronously if needed, but always acknowledge the webhook receipt to Vonage.