code examples
code examples
Vonage SMS with Node.js: Delivery Status Webhooks & Callbacks Tutorial
Learn how to build a Node.js Express server that sends SMS messages, receives inbound SMS, and handles delivery status webhooks using the Vonage Messages API. Complete tutorial with code examples.
Vonage SMS with Node.js: Delivery Status Webhooks & Callbacks Tutorial
Build a Node.js Express server that sends SMS messages, receives inbound SMS, and handles delivery status updates via webhooks using the Vonage Messages API. This guide covers project setup, implementation, security, and deployment.
What you'll build:
- Send SMS messages programmatically via an API endpoint.
- Receive incoming SMS messages sent to your Vonage virtual number via webhook.
- Receive delivery status updates for sent messages via webhook.
Technologies you'll use:
- Node.js: JavaScript runtime for server-side development.
- Express: Minimal Node.js web framework.
- Vonage Messages API: Multi-channel API for sending and receiving messages (focus on SMS).
- @vonage/server-sdk: Official Vonage Node.js SDK.
- ngrok: Exposes local servers to the internet for webhook testing.
- dotenv: Loads environment variables from a
.envfile.
System Architecture:
+-----------------+ +---------------------+ +-----------------+
| Your Application|----->| Vonage Messages API |----->| SMS Network |
| (Node.js/Express)| SMS Send Request (POST) | | (Mobile Carrier)|
+-----------------+ +---------------------+ +-----------------+
^ | | ^
| | Webhook (POST) | | Webhook (POST)
| | /webhooks/inbound | | /webhooks/status
| +----------------------|--+
| |
+-------------------------+
Receives Inbound SMS Receives Delivery Status
& Delivery StatusPrerequisites:
- Vonage API account (Sign up here)
- Node.js and npm (or yarn) installed locally
- Vonage virtual phone number capable of sending/receiving SMS
- ngrok installed (Download here). Authenticate ngrok with
ngrok authtoken your-tokenfor stable URLs and longer sessions. - Basic familiarity with Node.js, Express, and terminal commands
1. Set Up the Project
Initialize your Node.js project and install dependencies.
-
Create project directory: Open your terminal and create a new directory for the project.
bashmkdir vonage-sms-app cd vonage-sms-app -
Initialize Node.js project: Create a
package.jsonfile.bashnpm init -y -
Install dependencies: Install Express, the Vonage SDK, and dotenv.
bashnpm install express @vonage/server-sdk dotenv -
Create project structure: Create the main application file and environment variables file.
bashtouch server.js .env .gitignore -
Configure
.gitignore: Prevent committing sensitive information and dependencies.text# .gitignore node_modules .env private.keyNote: Add
private.keypreemptively to ensure this sensitive file is never accidentally committed to version control. -
Set up environment variables (
.env): Create a.envfile in the project root. You'll populate this with credentials from Vonage later.dotenv# .env - Placeholder values, replace later # Vonage Credentials (Obtained from Vonage Application) VONAGE_APPLICATION_ID= VONAGE_PRIVATE_KEY_PATH=./private.key # Path relative to project root # Vonage Number (Your purchased virtual number) VONAGE_NUMBER= # Your Test Mobile Number (For sending test messages) MY_NUMBER= # Use E.164 format, e.g., 15551234567 # Server Port PORT=3000VONAGE_APPLICATION_ID: Identifies your specific Vonage application.VONAGE_PRIVATE_KEY_PATH: Local path to the private key file for authenticating API requests.VONAGE_NUMBER: Your Vonage virtual phone number for sending SMS.MY_NUMBER: Your personal mobile number for testing (use E.164 format).PORT: Port your local Express server listens on.
2. Integrate with Vonage
Configure Vonage by creating an Application and linking a number to get credentials and webhook URLs.
-
Log in to Vonage Dashboard: Access your Vonage API Dashboard.
-
Purchase a virtual number: Navigate to
Numbers→Buy numbers. Find a number with SMS capabilities in your desired country and purchase it. -
Create a Vonage Application:
- Navigate to
Applications→Create a new application. - Name your application (e.g., "NodeJS SMS App").
- Click
Generate public and private key. Save theprivate.keyfile immediately and place it in your project's root directory. - Enable the
Messagescapability. - Enter temporary placeholders for
Inbound URLandStatus URL(e.g.,http://example.com/webhooks/inboundandhttp://example.com/webhooks/status). Set HTTP Method toPOSTfor both. You'll update these later with your ngrok URL. - Create the application.
- Navigate to
-
Get Application ID: Copy the
Application IDdisplayed after creation. -
Link your virtual number:
- On the application's settings page, find
Linked numbers. - Link your purchased virtual number.
- On the application's settings page, find
-
Update
.envfile: Populate your.envfile with the Application ID and Vonage virtual number.dotenv# .env - Example with values filled VONAGE_APPLICATION_ID=your-actual-application-id # Paste from Vonage Dashboard VONAGE_PRIVATE_KEY_PATH=./private.key VONAGE_NUMBER=your-vonage-virtual-number # E.g., 12015550123 MY_NUMBER=your-personal-mobile-number # E.g., 15551234567 PORT=3000 -
Start ngrok: Open a new terminal window and run ngrok, pointing it to port 3000.
bashngrok http 3000Copy the
ForwardingHTTPS URL (e.g.,https://randomstring.ngrok.io). -
Configure webhook URLs in Vonage Application:
- Return to your Vonage Application settings (
Applications→ Your App Name →Edit). - Update the
Messagescapability URLs:- Inbound URL:
https://randomstring.ngrok.io/webhooks/inbound - Status URL:
https://randomstring.ngrok.io/webhooks/status
- Inbound URL:
- Ensure HTTP method is
POSTfor both. - Save changes.
Webhook URL purposes:
Inbound URL: Vonage sends data about incoming SMS messages (sent to your Vonage number) here.Status URL: Vonage sends delivery status updates (e.g.,submitted,delivered,rejected) for messages you send here.
- Return to your Vonage Application settings (
3. Implement Core Functionality (Express Server)
Write the Node.js code in server.js to handle sending, receiving, and status updates.
// server.js
require('dotenv').config(); // Load environment variables from .env file
const express = require('express');
const { Vonage } = require('@vonage/server-sdk');
const fs = require('fs'); // Needed to read the private key file
// --- Configuration ---
const app = express();
const port = process.env.PORT || 3000;
// Express middleware to parse JSON and URL-encoded request bodies
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// --- Vonage SDK Initialization ---
let vonage;
try {
// Check if private key file exists before initializing
if (!fs.existsSync(process.env.VONAGE_PRIVATE_KEY_PATH)) {
throw new Error(`Private key file not found at path: ${process.env.VONAGE_PRIVATE_KEY_PATH}`);
}
vonage = new Vonage({
applicationId: process.env.VONAGE_APPLICATION_ID,
privateKey: process.env.VONAGE_PRIVATE_KEY_PATH
});
console.log("Vonage SDK initialized successfully.");
} catch (error) {
console.error("Error initializing Vonage SDK:", error.message);
console.error("Ensure VONAGE_APPLICATION_ID and VONAGE_PRIVATE_KEY_PATH are set correctly in your .env file and the private key file exists.");
process.exit(1); // Exit if SDK initialization fails
}
// --- API Endpoint for Sending SMS ---
app.post('/send-sms', async (req, res) => {
console.log("Received request to /send-sms:", req.body);
const { to, text } = req.body;
// Basic input validation – presence check only
// TODO: Implement more robust validation (e.g., phone number format using libphonenumber-js, text sanitization) for production. See Section 6.
if (!to || !text) {
console.error("Validation Error: 'to' and 'text' fields are required.");
return res.status(400).json({ error: "'to' and 'text' fields are required." });
}
if (!process.env.VONAGE_NUMBER) {
console.error("Configuration Error: VONAGE_NUMBER is not set in .env");
return res.status(500).json({ error: "Server configuration error: Sender number not set." });
}
const from = process.env.VONAGE_NUMBER;
try {
const resp = await vonage.messages.send({
message_type: "text",
to: to,
from: from,
channel: "sms",
text: text,
});
console.log("Message sent successfully. Message UUID:", resp.message_uuid);
res.status(200).json({ message: "SMS sent successfully", message_uuid: resp.message_uuid });
} catch (error) {
console.error("Error sending SMS:", error.response ? error.response.data : error.message);
// Provide more specific error feedback if available from Vonage response
if (error.response && error.response.data) {
res.status(error.response.status || 500).json({
error: "Failed to send SMS",
details: error.response.data
});
} else {
res.status(500).json({ error: "Failed to send SMS", details: error.message });
}
}
});
// --- Webhook Endpoint for Inbound SMS ---
// Vonage sends incoming messages here (POST request)
app.post('/webhooks/inbound', (req, res) => {
// TODO: Implement webhook signature verification for production. See Section 6.
console.log("--- Inbound SMS Received ---");
console.log("From:", req.body.from);
console.log("To:", req.body.to);
console.log("Text:", req.body.text);
console.log("Full Body:", JSON.stringify(req.body, null, 2)); // Log the full payload
// Vonage expects a 2xx response to acknowledge receipt of the webhook
res.status(200).end();
});
// --- Webhook Endpoint for Delivery Status Updates ---
// Vonage sends status updates for sent messages here (POST request)
app.post('/webhooks/status', (req, res) => {
// TODO: Implement webhook signature verification for production. See Section 6.
console.log("--- Delivery Status Update Received ---");
console.log("Message UUID:", req.body.message_uuid);
console.log("Status:", req.body.status);
console.log("Timestamp:", req.body.timestamp);
if(req.body.error) {
console.error("Error Code:", req.body.error.code);
console.error("Error Reason:", req.body.error.reason);
}
console.log("Full Body:", JSON.stringify(req.body, null, 2)); // Log the full payload
// Vonage expects a 2xx response (200 OK or 204 No Content)
res.status(200).end(); // Can also use res.sendStatus(204);
});
// --- Simple Root Route ---
app.get('/', (req, res) => {
res.send('SMS Application is running!');
});
// --- Start Server ---
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
console.log(`Configure your ngrok forwarding URL in Vonage Dashboard.`);
// Remind user about ngrok and environment variables
if (!process.env.VONAGE_APPLICATION_ID || !process.env.VONAGE_PRIVATE_KEY_PATH || !process.env.VONAGE_NUMBER) {
console.warn("WARN: One or more Vonage environment variables (APPLICATION_ID, PRIVATE_KEY_PATH, VONAGE_NUMBER) seem missing in .env");
}
});
// --- Basic Error Handling Middleware (Optional but Recommended) ---
app.use((err, req, res, next) => {
console.error("Unhandled Error:", err.stack);
res.status(500).send('Something broke!');
});Code explanation:
- Dependencies & setup: Loads
dotenv, importsexpressandVonage, initializes Express, and sets up middleware for parsing request bodies. - Vonage SDK initialization: Creates a
Vonageinstance using theapplicationIdandprivateKeypath from.env. Includes error handling if the key file is missing or credentials are not set. /send-sms(POST):- Defines an endpoint to trigger sending SMS.
- Expects
to(recipient number) andtext(message content) in the JSON request body. - Performs basic presence validation (
!to || !text). Note: Production applications need more robust validation (see Section 6). - Uses
vonage.messages.send()with appropriate parameters (message_type,to,from,channel,text). - Logs the
message_uuidupon successful submission to Vonage. - Includes
try...catchfor robust error handling, logging errors, and returning appropriate HTTP status codes (400 for bad input, 500 or specific Vonage status for API errors).
/webhooks/inbound(POST):- This endpoint matches the
Inbound URLconfigured in Vonage. - When an SMS is sent to your
VONAGE_NUMBER, Vonage makes a POST request here. - Logs the sender (
from), recipient (to), message content (text), and the full request body. Note: Production applications should verify the webhook signature (see Section 6). - Sends back a
200 OKstatus usingres.status(200).end(). If Vonage doesn't receive a 2xx response, it assumes the webhook failed and retries, potentially leading to duplicate processing.
- This endpoint matches the
/webhooks/status(POST):- This endpoint matches the
Status URLconfigured in Vonage. - When the delivery status of an SMS you sent changes (e.g.,
submitted,delivered,failed,rejected), Vonage makes a POST request here. - Logs the
message_uuid(linking it back to the sent message), thestatus, timestamp, any potential error details, and the full request body. Note: Production applications should verify the webhook signature (see Section 6). - Sends back a
200 OKstatus to acknowledge receipt.
- This endpoint matches the
- Server start & basic error handling: Starts the Express server on the configured port and includes a basic catch-all error handler.
4. Running and Testing the Application
-
Ensure ngrok is running: Keep the terminal window where you started
ngrok http 3000open. Confirm the HTTPS URL is correctly configured in your Vonage Application settings. -
Start the Node.js server: In the terminal window for your project directory (where
server.jsis), run:bashnode server.jsYou should see output indicating the server is listening and the Vonage SDK initialized.
-
Test sending SMS: Open a new terminal window or use a tool like Postman or Insomnia to send a POST request to your
/send-smsendpoint. ReplaceYOUR_PERSONAL_MOBILE_NUMBERwith the actual value from your.envfile (MY_NUMBER).Using
curl:bashcurl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -d '{ "to": "YOUR_PERSONAL_MOBILE_NUMBER", # Replace with your actual number (e.g., 15551234567) "text": "Hello from Vonage via Node.js! Test time: '"$(date)"'" }'- Expected outcome:
- Terminal running
curl: You should get a JSON response like{"message":"SMS sent successfully","message_uuid":"some-uuid-string"}. - Terminal running
server.js: You'll see logs for the/send-smsrequest and "Message sent successfully…". - Your mobile phone: You should receive the SMS message shortly.
- Terminal running
server.js: Soon after, you should see logs from/webhooks/statusshowing the status changing (e.g.,submitted, then potentiallydelivered). Check themessage_uuidto match the sent message.
- Terminal running
- Expected outcome:
-
Test receiving inbound SMS:
- From your personal mobile phone, send an SMS message to your
VONAGE_NUMBER(the one configured in.env). - Expected outcome:
- Terminal running
server.js: You should see logs from/webhooks/inboundshowing the message details (From:,To:,Text:). - ngrok terminal: You might see activity indicating POST requests to
/webhooks/inbound.
- Terminal running
- From your personal mobile phone, send an SMS message to your
-
Test delivery failure (optional): Try sending an SMS to an invalid or non-existent number via the
/send-smsendpoint. Observe the/webhooks/statuslogs in yourserver.jsterminal – you should receive statuses likefailedorrejectedwith corresponding error codes/reasons.
5. Error Handling and Logging
- API Errors: The
/send-smsroute includestry...catchto handle errors during the Vonage API call. It attempts to parse and return specific error details from the Vonage response. - Webhook Errors: If your webhook endpoints (
/inbound,/status) encounter an error processing the request before sending the200 OK, Vonage will retry. Implement robust internal error handling within these routes if you perform complex logic (e.g., database lookups). - Logging: We are currently using
console.logandconsole.error. For production, this is insufficient. Implement a structured logging library likewinstonorpino. This allows for better log formatting (e.g., JSON), writing logs to files or external services, setting different log levels (debug, info, warn, error), and easier log analysis. This guide does not include the implementation of structured logging. - Vonage Retries: Vonage requires a
2xxstatus code response from your webhook endpoints within a reasonable time (usually a few seconds) to consider the delivery successful. Design your webhook handlers to be fast and acknowledge receipt quickly, performing heavier processing asynchronously if needed (e.g., using a message queue).
6. Security Considerations
- Environment Variables: Never commit your
.envfile or yourprivate.keyfile to source control. Use a.gitignorefile as shown. In production, use your hosting provider's mechanism for managing environment variables securely. - Private Key Security: Treat your
private.keyfile like a password. Ensure its file permissions restrict access (e.g.,chmod 400 private.keyon Linux/macOS). - Webhook Security (Crucial for Production): The current implementation does not verify if incoming webhooks genuinely originate from Vonage. This is a security risk in production. Attackers could send fake requests to your webhook endpoints. The Vonage Messages API supports signing webhooks with JWT (JSON Web Tokens). You must:
- Configure a signature secret in your Vonage Application settings.
- Use a library like
jsonwebtokenin your Node.js app (npm install jsonwebtoken). - Implement logic in your
/webhooks/inboundand/webhooks/statusroutes to verify theAuthorization: Bearer <token>header of incoming requests against your configured secret before processing the payload. This guide does not include the JWT verification implementation. Refer to the official Vonage documentation on "Signed Webhooks" for implementation details.
- Input Validation (Important for Production): The
/send-smsendpoint currently only checks iftoandtextexist. This is insufficient for production. Add more robust validation:- Phone Number Format: Validate the
tonumber against the E.164 standard using a library likelibphonenumber-js(npm install libphonenumber-js). - Text Sanitization: If the
textcontent could potentially come from user input elsewhere in your system, sanitize it to prevent cross-site scripting (XSS) or other injection attacks, depending on how you use the text later. This guide does not include robust input validation implementation.
- Phone Number Format: Validate the
- Rate Limiting (Recommended for Production): To prevent abuse of your
/send-smsendpoint (either accidental or malicious), implement rate limiting. Use middleware likeexpress-rate-limit(npm install express-rate-limit) to restrict the number of requests a user can make in a given time window. Check Vonage's own API rate limits as well. This guide does not include rate limiting implementation.
7. Troubleshooting and Caveats
- ngrok Issues:
- Ensure ngrok is running and hasn't timed out (free accounts have session limits). Authenticating ngrok might provide longer/more stable sessions.
- Double-check the HTTPS URL from ngrok matches exactly what's configured in the Vonage Application
Inbound URLandStatus URL. - Firewalls might block ngrok; ensure your network allows outbound connections for ngrok.
- Credentials Errors:
Error initializing Vonage SDK: Private key file not found…: VerifyVONAGE_PRIVATE_KEY_PATHin.envis correct relative to where you runnode server.js, and the file exists.- API errors (401 Unauthorized): Double-check
VONAGE_APPLICATION_IDis correct. Ensure theprivate.keyfile content hasn't been corrupted.
- Webhook Not Received:
- Confirm ngrok is running and URLs match in Vonage.
- Check the Vonage Dashboard under
Logs→API Logs(or similar section) for errors related to webhook delivery failures from Vonage's side. - Ensure your server is running (
node server.js) and didn't crash. Check server logs for errors. - Make sure your webhook endpoints (
/webhooks/inbound,/webhooks/status) return a200 OKor204 No Contentstatus quickly. Check server logs for errors within these handlers. Failure to respond quickly or with a 2xx status will cause Vonage to retry.
- Delivery Status (
delivered) Not Received:- Delivery Receipts (DLRs) are carrier-dependent. Not all mobile networks or countries provide reliable
deliveredstatus updates back to Vonage. You will usually receivesubmitted, butdeliveredis not guaranteed. - Check if the
Status URLis correctly configured and your/webhooks/statusendpoint is working and returning200 OK.
- Delivery Receipts (DLRs) are carrier-dependent. Not all mobile networks or countries provide reliable
- Incorrect Phone Number Formatting: Always use the E.164 format (e.g.,
15551234567– the SDK often handles adding the+if needed, but being explicit is safer) fortoandfromnumbers. - Vonage Number Capabilities: Ensure the Vonage number you purchased is SMS-enabled for the direction you need (sending/receiving) in the relevant country.
8. Deployment Considerations
- Hosting: Deploy this application to a platform like Heroku, AWS (EC2, Lambda, Elastic Beanstalk), Google Cloud (App Engine, Cloud Run), DigitalOcean (App Platform), or similar PaaS/IaaS providers.
- Public URL: Replace the ngrok URL with your server's permanent public HTTPS URL in the Vonage Application webhook settings. Ensure your server is accessible from the internet and configured for HTTPS.
- Environment Variables: Configure environment variables (
VONAGE_APPLICATION_ID,VONAGE_PRIVATE_KEY_PATH,VONAGE_NUMBER,PORT, etc.) securely using your hosting provider's tools (e.g., Heroku Config Vars, AWS Secrets Manager, .env files managed securely on the server). Do not include the.envfile in your deployment package/repository. Securely transfer or provide theprivate.keyfile to your production server and ensure theVONAGE_PRIVATE_KEY_PATHenvironment variable points to its location on the server. - Process Management: Use a process manager like
pm2to keep your Node.js application running reliably in production.pm2handles automatic restarts on crashes, manages logs, enables clustering for better performance, and more. - CI/CD: Set up a Continuous Integration/Continuous Deployment pipeline (e.g., using GitHub Actions, GitLab CI, Jenkins, CircleCI) to automate testing and deployment processes, ensuring code quality and consistent releases.
9. Verification Checklist
Before considering this production-ready:
- Can successfully send an SMS via the
/send-smsendpoint? - Receive the SMS on the target mobile device?
- See logs for the
/send-smsrequest in the server console? - See logs for the
/webhooks/statusupdate (at leastsubmitted) in the server console? - Can successfully send an SMS to the Vonage number from a mobile device?
- See logs for the
/webhooks/inboundmessage in the server console? - Do both webhook endpoints consistently return
200 OKor204 No Contentquickly? - Are all secrets (
.env,private.key) excluded from Git? - Is robust error handling implemented for API calls and webhook processing logic?
- Is structured logging configured (Strongly Recommended)?
- Is robust input validation implemented for the API endpoint (Strongly Recommended)?
- Is webhook signature verification (JWT) implemented (Crucial for Production)?
- Is rate limiting implemented on public-facing endpoints (Recommended)?
- Have deployment procedures been tested (environment variables, public URL update, process manager)?
This guide provides a solid foundation for sending, receiving, and tracking SMS messages using Node.js and Vonage. Enhance logging, error handling, and especially security (webhook verification, input validation, rate limiting) based on your specific production requirements. Refer to the official Vonage Messages API documentation for further details and advanced features.
Frequently Asked Questions
How to send SMS with Node.js and Vonage?
Send SMS messages using Node.js, Express, and the Vonage Messages API by creating a POST request to the /send-sms endpoint with the recipient's number and the message text in the request body. The server-side code then uses the Vonage SDK to handle sending the SMS through the Vonage API.
What is Vonage Messages API used for?
The Vonage Messages API is a multi-channel communication platform for sending and receiving messages across various channels, including SMS. This tutorial focuses on using the API for sending and receiving text messages (SMS) within a Node.js application.
How to set up Vonage for sending SMS?
Setting up Vonage involves purchasing a virtual number, creating a Vonage application, and linking the number to your application. You will need to generate public and private keys, save the private key securely and enable the messages capability and configure webhook URLs for inbound messages and delivery statuses.
Why use ngrok with Vonage webhooks?
ngrok creates a secure tunnel to your local server, providing a temporary public HTTPS URL. This allows Vonage to deliver webhooks to your local development environment during testing, simulating how real-world webhooks function when deployed.
What are Vonage webhooks used for in this Node.js app?
Vonage uses webhooks to send real-time updates to your server. Inbound SMS messages to your Vonage number trigger a webhook to the '/webhooks/inbound' endpoint. Delivery status updates for your sent SMS messages trigger a webhook to the '/webhooks/status' endpoint.
How to receive SMS messages with Vonage?
Receive incoming SMS messages sent to your Vonage virtual number by configuring a webhook URL in your Vonage application settings that points to the '/webhooks/inbound' endpoint of your Node.js application.
How to track delivery status of sent SMS messages?
Delivery status updates are received via webhooks sent to the '/webhooks/status' endpoint of your server. The webhook request will include the message UUID, the status of the delivery (e.g., 'submitted', 'delivered', 'rejected'), and a timestamp.
What Node.js libraries are needed for sending SMS with Vonage?
The project utilizes several Node.js libraries: 'express' for the web framework, '@vonage/server-sdk' for interacting with the Vonage API, 'dotenv' for managing environment variables, and optionally 'jsonwebtoken' for webhook signature verification in a production environment.
How to install required Node.js packages for this SMS app?
You can install the necessary dependencies (Express, the Vonage Server SDK, and dotenv) by using either 'npm install express @vonage/server-sdk dotenv' or 'yarn add express @vonage/server-sdk dotenv' in your terminal within the project directory.
How to secure private key for Vonage?
Safeguard the private key by never committing it to version control and securing file permissions. In production environments, utilize environment variables provided by your hosting platform to handle sensitive information like API keys.
What is the importance of webhook signature verification?
Webhook signature verification, especially with JWT (JSON Web Tokens), is crucial in production for confirming the authenticity of incoming webhooks. This process ensures that the incoming requests originate from Vonage and haven't been tampered with.
What are the common errors in Vonage SMS integration?
Common errors include ngrok timing out, credentials not being set correctly in .env, webhook URLs being wrong, private keys being handled incorrectly, and servers not running.
Why is a 200 OK response required from a webhook?
Vonage expects a 2xx response from your webhook endpoint to acknowledge that your server received it. Without a 2xx response, the system may assume the webhook failed and retry the request, potentially causing duplicate messages or other issues.