code examples
code examples
Developer Guide: Handling Infobip SMS Delivery Status Webhooks with Node.js and Express
A comprehensive guide on building a Node.js/Express application to send SMS via Infobip and process delivery status webhooks (DLRs).
Developer Guide: Handling Infobip SMS Delivery Status Webhooks with Node.js and Express
This guide provides a complete walkthrough for building a Node.js application using the Express framework to send SMS messages via the Infobip API and handle real-time delivery status updates through webhooks (callbacks).
By the end of this tutorial, you will have a functional application capable of:
- Sending SMS messages programmatically using Infobip.
- Receiving delivery reports (DLRs) from Infobip to track message status (e.g., delivered, failed, rejected).
- Processing these DLRs within your Express application.
This enables developers to build more robust messaging workflows, providing visibility into message delivery success and enabling actions based on status updates — such as retrying failed messages or logging delivery confirmations.
Target Audience: Developers familiar with Node.js, Express, and basic API concepts.
System Architecture
The system involves the following components:
- User/Client: Initiates the request to send an SMS (e.g., via a web form or another service).
- Node.js/Express Application:
- Exposes an API endpoint to receive SMS sending requests.
- Calls the Infobip API to send the SMS, including a
notifyUrlfor callbacks. - Exposes a webhook endpoint (
/infobip-dlr) to receive delivery reports from Infobip. - Processes incoming delivery reports.
- Infobip Platform:
- Receives the SMS send request from the Node.js application.
- Attempts to deliver the SMS to the recipient's handset via mobile networks.
- Sends a POST request containing the delivery status to the
notifyUrlprovided by the application.
sequenceDiagram
participant User/Client
participant Node.js/Express App
participant Infobip API
participant Mobile Network/Handset
User/Client->>+Node.js/Express App: POST /send-sms (to, message)
Node.js/Express App->>+Infobip API: Send SMS Request (to, message, notifyUrl='/infobip-dlr')
Infobip API-->>-Node.js/Express App: Acknowledge Request (messageId)
Node.js/Express App-->>-User/Client: Success/Failure Sending Initiated
Infobip API->>+Mobile Network/Handset: Deliver SMS
Mobile Network/Handset-->>-Infobip API: Delivery Status Update
Infobip API->>+Node.js/Express App: POST /infobip-dlr (Delivery Report Payload)
Node.js/Express App-->>-Infobip API: 200 OK (Acknowledge Receipt)
Note over Node.js/Express App: Process DLR (Log, Update DB, etc.)Prerequisites
- Node.js and npm (or yarn): Ensure you have a recent version installed. (Node.js Downloads)
- Infobip Account: You need an active Infobip account. A free trial account works, but remember it typically restricts sending SMS only to the phone number used during registration. (Infobip Signup)
- Infobip API Key and Base URL: Obtain these from your Infobip account dashboard.
- Publicly Accessible URL: For Infobip to send delivery reports back to your application, your callback endpoint must be reachable from the public internet. During development, tools like ngrok are essential.
- Basic understanding: Familiarity with JavaScript, Node.js, Express, REST APIs, and environment variables.
1. Setting up the Project
Let's initialize our Node.js project and install the necessary dependencies.
1.1 Create Project Directory
Open your terminal or command prompt and create a new directory for the project.
mkdir infobip-dlr-app
cd infobip-dlr-app1.2 Initialize Node.js Project
Initialize the project using npm (or yarn). This creates a package.json file.
npm init -y- Why
-y? This flag accepts the default settings during initialization, speeding up the process. You can omit it to customize project details.
1.3 Install Dependencies
We need express for our web server, axios to make HTTP requests to the Infobip API, and dotenv to manage environment variables securely.
npm install express axios dotenvexpress: A minimal and flexible Node.js web application framework.axios: A popular promise-based HTTP client for making requests to external APIs like Infobip's.dotenv: Loads environment variables from a.envfile intoprocess.env, keeping sensitive information like API keys out of your source code.
1.4 Create Project Structure
Create the basic files and folders:
# For Linux/macOS
touch index.js .env .gitignore infobipService.js
# For Windows (Command Prompt)
echo. > index.js
echo. > .env
echo. > .gitignore
echo. > infobipService.js
# For Windows (PowerShell)
New-Item index.js -ItemType File
New-Item .env -ItemType File
New-Item .gitignore -ItemType File
New-Item infobipService.js -ItemType FileYour initial structure should look like this:
infobip-dlr-app/
├── node_modules/
├── .env
├── .gitignore
├── index.js
├── infobipService.js
└── package.json
1.5 Configure .gitignore
Add node_modules and .env to your .gitignore file. This prevents committing dependencies and sensitive credentials to version control.
# .gitignore
node_modules
.env- Why ignore
.env? It contains sensitive API keys and credentials. Never commit this file to public or shared repositories. Team members should create their own.envfiles based on a template or secure sharing mechanism.
1.6 Set up Environment Variables (.env)
Open the .env file and add placeholders for your Infobip credentials and application settings. You will replace these placeholders later.
# .env
# Infobip Credentials
INFOBIP_API_KEY=YOUR_INFOBIP_API_KEY_HERE
INFOBIP_BASE_URL=YOUR_INFOBIP_BASE_URL_DOMAIN_HERE # e.g., xyz.api.infobip.com
# Application Settings
PORT=3000
APP_BASE_URL=http://localhost:3000 # Replace with your public URL (e.g., ngrok URL) during testingINFOBIP_API_KEY: Your secret key for authenticating API requests.INFOBIP_BASE_URL: The specific domain assigned to your Infobip account for API access.PORT: The port your Express application will listen on.APP_BASE_URL: The base URL where your application is accessible from the internet. Infobip needs this to send callbacks. For local development, this will be your ngrok tunnel URL.
2. Implementing Core Functionality: Sending SMS
Let's create a service to interact with the Infobip API for sending SMS messages.
2.1 Create Infobip Service File
This step was included in the project structure setup (1.4). You should have an infobipService.js file.
2.2 Implement sendSms Function
Add the following code to infobipService.js. This code adapts the logic from the Infobip developer blog post, adding the crucial notifyUrl parameter.
// infobipService.js
const axios = require('axios');
// Load environment variables (ensure dotenv is configured in index.js first)
const apiKey = process.env.INFOBIP_API_KEY;
const baseUrlDomain = process.env.INFOBIP_BASE_URL;
const appBaseUrl = process.env.APP_BASE_URL; // Base URL for callbacks
/**
* Constructs the full API endpoint URL for sending SMS.
* @param {string} domain - The Infobip base URL domain.
* @returns {string} The full API URL.
*/
const buildUrl = (domain) => {
if (!domain) {
throw new Error('INFOBIP_BASE_URL is not defined in environment variables.');
}
// Using the recommended Advanced SMS endpoint
return `https://${domain}/sms/2/text/advanced`;
};
/**
* Constructs the necessary HTTP headers for Infobip API authentication.
* @param {string} key - The Infobip API Key.
* @returns {object} Headers object.
*/
const buildHeaders = (key) => {
if (!key) {
throw new Error('INFOBIP_API_KEY is not defined in environment variables.');
}
return {
'Authorization': `App ${key}`,
'Content-Type': 'application/json',
'Accept': 'application/json', // Explicitly accept JSON responses
};
};
/**
* Constructs the request body for the Infobip Send SMS API.
* Includes the destination number, message text, and the callback URL.
* @param {string} destinationNumber - The recipient's phone number in international format (e.g., 447... ).
* @param {string} message - The text content of the SMS.
* @param {string} callbackUrl - The publicly accessible URL for delivery reports.
* @returns {object} Request body object.
*/
const buildRequestBody = (destinationNumber, message, callbackUrl) => {
const destinationObject = {
to: destinationNumber,
// messageId: 'OPTIONAL-CUSTOM-MESSAGE-ID' // Optional: Add a custom ID if needed
};
const messageObject = {
destinations: [destinationObject],
text: message,
// from: 'YourSenderID', // Optional: Specify a custom sender ID if configured/allowed
notifyUrl: callbackUrl, // ** Crucial for receiving delivery reports **
notifyContentType: 'application/json', // Specify format for DLRs
// See Infobip docs for more options: validityPeriod, sendAt, etc.
};
return {
messages: [messageObject],
// bulkId: 'OPTIONAL-CUSTOM-BULK-ID' // Optional: Group messages
};
};
/**
* Sends an SMS message using the Infobip API.
* @param {string} destinationNumber - Recipient's phone number (international format).
* @param {string} message - SMS text content.
* @returns {Promise<object>} A promise that resolves with the Infobip API response or rejects with an error.
*/
const sendSms = async (destinationNumber, message) => {
if (!destinationNumber || !message) {
throw new Error('Destination number and message text are required.');
}
if (!appBaseUrl) {
throw new Error('APP_BASE_URL is not defined. Needed for callback URL.');
}
const apiUrl = buildUrl(baseUrlDomain);
const headers = buildHeaders(apiKey);
// Construct the full callback URL
const callbackUrl = `${appBaseUrl}/infobip-dlr`;
const requestBody = buildRequestBody(destinationNumber, message, callbackUrl);
console.log(`Sending SMS to ${destinationNumber} via ${apiUrl}`);
console.log(`Callback URL set to: ${callbackUrl}`);
try {
const response = await axios.post(apiUrl, requestBody, { headers });
console.log('Infobip API Response:', JSON.stringify(response.data, null, 2));
// The response here confirms Infobip accepted the request, not final delivery.
// It includes a messageId useful for tracking.
return response.data;
} catch (error) {
console.error('Error sending SMS via Infobip:');
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.error('Status:', error.response.status);
console.error('Headers:', JSON.stringify(error.response.headers, null, 2));
console.error('Data:', JSON.stringify(error.response.data, null, 2));
// Rethrow a more specific error or handle based on status code
throw new Error(`Infobip API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
} else if (error.request) {
// The request was made but no response was received
console.error('Request Error:', error.request);
throw new Error('Error sending SMS: No response received from Infobip.');
} else {
// Something happened in setting up the request that triggered an Error
console.error('Axios Error:', error.message);
throw new Error(`Error sending SMS: ${error.message}`);
}
}
};
module.exports = {
sendSms,
};- Why
async/await? It simplifies working with promises returned byaxios, making the code cleaner and easier to read than using.then()and.catch()chains directly for complex flows. - Why
notifyUrlandnotifyContentType? These parameters instruct Infobip where and how (JSON format) to send the delivery report once the final status of the message is known. This is the core mechanism for enabling callbacks. - Why
APP_BASE_URL? The callback URL must be absolute and publicly accessible. We construct it using the base URL defined in our environment variables.
3. Building the API Layer and Handling Callbacks
Now, let's set up our Express server, create an endpoint to trigger SMS sending, and critically, an endpoint to receive the delivery reports from Infobip.
3.1 Basic Express Server Setup
Modify your index.js file:
// index.js
// Load environment variables from .env file
require('dotenv').config();
const express = require('express');
const infobipService = require('./infobipService'); // Import our service
const app = express();
const port = process.env.PORT || 3000; // Use port from .env or default to 3000
// Middleware to parse JSON request bodies
// Crucial for both our API endpoint and receiving Infobip's JSON callbacks
app.use(express.json());
// Simple root route for health check or basic info
app.get('/', (req, res) => {
res.send('Infobip DLR Handler App is running!');
});
// --- API Endpoint to Send SMS ---
app.post('/send-sms', async (req, res) => {
const { to, text } = req.body; // Expect 'to' (number) and 'text' (message) in JSON body
// Basic Input Validation
if (!to || typeof to !== 'string' || to.trim() === '') {
return res.status(400).json({ error: 'Missing or invalid "to" phone number in request body.' });
}
if (!text || typeof text !== 'string' || text.trim() === '') {
return res.status(400).json({ error: 'Missing or invalid "text" message in request body.' });
}
try {
console.log(`Received request to send SMS to: ${to}`);
const result = await infobipService.sendSms(to.trim(), text.trim());
// Send back the initial response from Infobip (acknowledgment, messageId)
res.status(202).json({ // 202 Accepted: Request taken, processing underway
message: 'SMS sending initiated successfully.',
infobipResponse: result,
});
} catch (error) {
console.error('Error in /send-sms endpoint:', error.message);
// Avoid sending detailed internal errors back to the client in production
res.status(500).json({ error: 'Failed to initiate SMS sending.' });
}
});
// --- Webhook Endpoint to Receive Infobip Delivery Reports ---
app.post('/infobip-dlr', (req, res) => {
console.log('--- Received Infobip Delivery Report ---');
console.log('Timestamp:', new Date().toISOString());
console.log('Headers:', JSON.stringify(req.headers, null, 2)); // Log headers for debugging if needed
console.log('Body:', JSON.stringify(req.body, null, 2)); // Log the full DLR payload
// --- Processing Logic ---
// The actual payload structure can vary slightly based on Infobip product/settings.
// Inspect the logged 'Body' from a real callback to confirm structure.
// Common structure includes a 'results' array.
if (req.body && Array.isArray(req.body.results)) {
req.body.results.forEach(report => {
const messageId = report.messageId;
const status = report.status ? report.status.name : 'UNKNOWN';
const groupName = report.status ? report.status.groupName : 'UNKNOWN';
const description = report.status ? report.status.description : 'No description';
const recipient = report.to;
const errorCode = report.error ? report.error.id : null;
const errorName = report.error ? report.error.name : null;
const errorDescription = report.error ? report.error.description : null;
console.log(`\n--- Processing DLR for Message ID: ${messageId} ---`);
console.log(` To: ${recipient}`);
console.log(` Status Group: ${groupName}`); // e.g., PENDING, UNDELIVERABLE, DELIVERED, EXPIRED, REJECTED
console.log(` Status: ${status}`); // e.g., PENDING_ACCEPTED, DELIVERED_TO_HANDSET, UNDELIVERABLE_NOT_DELIVERED
console.log(` Description: ${description}`);
if (errorCode) {
console.error(` Error Code: ${errorCode} (${errorName})`);
console.error(` Error Description: ${errorDescription}`);
}
// TODO: Add your business logic here:
// 1. Find the original message record in your database using messageId.
// 2. Update the message status based on 'groupName' or 'status'.
// 3. If status is 'UNDELIVERABLE' or 'REJECTED', trigger alerts or retry logic.
// 4. Log detailed information for auditing.
});
} else {
console.warn('Received DLR in unexpected format:', JSON.stringify(req.body));
}
// ** Crucial: Respond to Infobip quickly with a 2xx status code **
// Failing to respond or responding with an error might cause Infobip to retry sending the DLR.
res.sendStatus(200); // Send 'OK' back to Infobip to acknowledge receipt.
});
// Start the server
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
console.log(`Ensure INFOBIP_API_KEY and INFOBIP_BASE_URL are set.`);
console.log(`Callback endpoint configured internally as /infobip-dlr`);
console.log(`Ensure APP_BASE_URL is set correctly in .env for callbacks`);
if (process.env.NODE_ENV !== 'production') {
console.warn(`--- Development Mode ---`);
console.warn(`Use a tool like ngrok to expose port ${port} publicly.`);
console.warn(`Update APP_BASE_URL in .env with your ngrok URL (e.g., https://xxxxx.ngrok.io)`);
console.warn(`Example ngrok command: ngrok http ${port}`);
}
});- Why
require('dotenv').config()first? This line must execute before any code that accessesprocess.envvariables (like ourinfobipService.js) to ensure those variables are loaded from the.envfile. - Why
express.json()middleware? Infobip sends delivery reports as JSON payloads in the POST request body. This middleware parses that JSON and makes it available asreq.body. It's also needed for our/send-smsendpoint to parse the incoming JSON request. - Why
/send-smsendpoint? This provides a clean interface for other parts of your system (or external clients) to request an SMS without needing direct access to the Infobip service logic. - Why
/infobip-dlrendpoint? This is the publicly accessible listener that Infobip will call. Its sole purpose is to receive, acknowledge, and process incoming delivery status updates. - Why
res.sendStatus(200)in/infobip-dlr? You must respond to Infobip's webhook request with a success status (2xx) quickly. This acknowledges receipt. If you respond with an error (4xx, 5xx) or time out, Infobip will likely retry sending the report according to their retry schedule, potentially leading to duplicate processing if not handled carefully. Do complex processing asynchronously if needed. - Why parse
req.body.results? Based on Infobip documentation and common webhook patterns, delivery reports often come batched within aresultsarray in the JSON payload. Always inspect the actual payload you receive during testing to confirm the structure.
4. Integrating with Infobip: Configuration
Let's get the necessary credentials from Infobip and configure our .env file.
4.1 Obtain Infobip API Key and Base URL
- Log in to your Infobip Portal.
- Navigate to the API Keys management section. This is typically found under your user profile, account settings, or a dedicated ""Developers"" section. The exact location might change, explore the portal if needed.
- Example Path (might vary): Click your username/icon -> Account Settings -> API Keys. Or look for Developers -> API Keys.
- Create a new API Key if you don't have one already. Give it a descriptive name (e.g.,
Node DLR App Key). - Copy the API Key immediately and store it securely. You often only see the full key upon creation.
- Find your Base URL (API Domain). This is usually displayed prominently in the API section or on the main dashboard after logging in. It looks like
xxxxxx.api.infobip.com.- Example Location (might vary): Often shown on the API Keys page or a general API information page.
4.2 Update .env File
Open your .env file and replace the placeholders with the actual values you obtained:
# .env
# Infobip Credentials
INFOBIP_API_KEY=YOUR_ACTUAL_INFOBIP_API_KEY
INFOBIP_BASE_URL=YOUR_ACTUAL_INFOBIP_BASE_URL_DOMAIN # e.g., abc12.api.infobip.com
# Application Settings
PORT=3000
APP_BASE_URL=http://localhost:3000 # <-- IMPORTANT: Update this when using ngrok4.3 Set Up ngrok for Local Development
Infobip needs to reach your /infobip-dlr endpoint over the public internet. ngrok creates a secure tunnel from a public URL to your local machine.
-
Authenticate ngrok if you have an account (optional but recommended for longer sessions).
-
In your terminal (in a separate window from your running Node app), start ngrok, pointing it to the port your Express app is running on (defined by
PORTin.env, default 3000).bashngrok http 3000 -
ngrok will display output including lines like:
Forwarding http://<random-string>.ngrok.io -> http://localhost:3000 Forwarding https://<random-string>.ngrok.io -> http://localhost:3000 -
Copy the
httpsForwarding URL (e.g.,https://<random-string>.ngrok.io). This is your public URL. -
Update
APP_BASE_URLin your.envfile with thishttpsngrok URL.dotenv# .env (Example Update) # ... other variables ... APP_BASE_URL=https://b7a9-123-45-67-89.ngrok.io # Use YOUR ngrok HTTPS URL -
Restart your Node.js application (
node index.jsor usingnodemon) after updating.envso it picks up the newAPP_BASE_URL.
- Why ngrok? It makes your locally running server accessible from the internet without complex network configuration, essential for receiving webhooks during development.
- Why HTTPS ngrok URL? Always use HTTPS for security, especially when dealing with potentially sensitive callback data.
5. Error Handling and Logging
Our current code includes basic console logging and error handling. Let's refine it slightly.
- Infobip Service (
infobipService.js): ThesendSmsfunction already includes atry...catchblock that logs detailed errors fromaxios, distinguishing between response errors, request errors, and setup errors. It re-throws a generic error to the caller. - API Endpoint (
/send-smsinindex.js): The endpoint wraps the service call intry...catch. It logs the error server-side and returns a generic 500 error to the client to avoid leaking internal details. Basic 400 Bad Request errors are returned for invalid input. - Callback Endpoint (
/infobip-dlrinindex.js): This endpoint logs the entire incoming payload. It includes aconsole.warnfor unexpected formats and logs specific details for recognized formats. Crucially, it always responds with 200 OK to prevent Infobip retries, even if internal processing fails. Robust error handling here might involve:- Wrapping the processing logic in a
try...catch. - Logging any processing errors internally (e.g., database update failure).
- Still sending
res.sendStatus(200)back to Infobip. - Implementing a separate mechanism (like a dead-letter queue or retry queue) to handle DLRs that failed processing.
- Wrapping the processing logic in a
Further Improvements (Beyond this Guide):
- Structured Logging: Use a library like
winstonorpinofor structured JSON logging, making logs easier to parse and analyze, especially in production. - Error Tracking Services: Integrate with services like Sentry or Datadog to capture, aggregate, and alert on application errors.
- Retry Mechanisms: For sending SMS, if the initial request to Infobip fails due to network issues or temporary Infobip problems (e.g., 5xx errors), implement a retry strategy with exponential backoff using libraries like
axios-retryorasync-retry. Do not retry based on DLRs indicating final failure (likeUNDELIVERABLE).
6. Database Schema and Data Layer (Conceptual)
While this guide doesn't implement a database, here's how you would typically integrate one:
6.1 Conceptual Schema
You'd likely need a table to store information about outgoing messages:
CREATE TABLE outgoing_messages (
id SERIAL PRIMARY KEY, -- Or UUID
infobip_message_id VARCHAR(255) UNIQUE, -- Store the ID from Infobip's initial response
recipient_number VARCHAR(20) NOT NULL,
message_text TEXT NOT NULL,
status VARCHAR(50) DEFAULT 'PENDING_INFOBIP', -- Initial status before Infobip confirms acceptance
infobip_status_group VARCHAR(50), -- e.g., PENDING, DELIVERED, UNDELIVERABLE
infobip_status_name VARCHAR(100), -- Specific status e.g., PENDING_ACCEPTED
infobip_status_description TEXT,
error_code INTEGER,
error_name VARCHAR(100),
error_description TEXT,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
last_updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
callback_received_at TIMESTAMPTZ -- Timestamp when the DLR was processed
);
-- Index for faster lookups when processing callbacks
CREATE INDEX idx_infobip_message_id ON outgoing_messages(infobip_message_id);6.2 Data Layer Integration
- On Sending (
/send-sms):- Before calling
infobipService.sendSms, insert a new record intooutgoing_messageswith statusPENDING_INFOBIP. - After a successful call to
infobipService.sendSms, update the record using themessageIdfrom the Infobip response, setting theinfobip_message_idfield and potentially updating status toPENDING_ACCEPTED(or similar based on the initial response).
- Before calling
- On Receiving Callback (
/infobip-dlr):- Inside the loop processing
req.body.results:- Extract the
messageIdfrom the report. - Find the corresponding record in
outgoing_messagesusinginfobip_message_id. - If found, update the
infobip_status_group,infobip_status_name,infobip_status_description, error fields,callback_received_at, andlast_updated_atbased on the DLR content. - Handle cases where the
messageIdis not found (log an error/warning).
- Extract the
- Inside the loop processing
Tools: Use an ORM like Sequelize (for SQL databases) or Mongoose (for MongoDB) to manage database interactions more easily. Use migration tools like sequelize-cli or knex migrations to manage schema changes.
7. Security Features
Security is paramount, especially when handling API keys and potentially sensitive message data.
- Environment Variables: We are already using
.envto keepINFOBIP_API_KEYout of the code and.gitignoreto prevent committing it. This is crucial. - Input Validation (
/send-sms): Basic validation is implemented to check for the presence and type oftoandtext. Enhance this based on expected phone number formats or message length constraints. Use libraries likejoiorexpress-validatorfor more robust validation schemas. - Webhook Security (
/infobip-dlr):- HTTPS: Using
ngrokwith HTTPS and deploying to a server with HTTPS configured is essential to encrypt data in transit. - Secret Validation (Optional/Advanced): Check if Infobip supports sending a secret signature in a header (e.g.,
X-Infobip-Signature) along with the webhook. If so, you would:- Configure a secret string in your Infobip webhook settings (if available).
- Store the same secret in your application's environment variables.
- On receiving a webhook, calculate the expected signature based on the request body and your secret (using HMAC-SHA1 or similar, as specified by Infobip).
- Compare the calculated signature with the one provided in the header. Reject requests if they don't match. Note: Simple signature verification doesn't seem to be a standard, easily configurable feature for basic Infobip SMS DLRs via
notifyUrlas of common knowledge, but check their latest documentation.
- IP Whitelisting (If feasible): If Infobip publishes a list of IP addresses from which they send webhooks, you could configure your firewall or application middleware to only accept requests to
/infobip-dlrfrom those specific IPs. This can be difficult to maintain if IPs change.
- HTTPS: Using
- Rate Limiting: Protect your
/send-smsendpoint from abuse by implementing rate limiting using middleware likeexpress-rate-limit. - Helmet: Use the
helmetmiddleware for Express to set various security-related HTTP headers (e.g., Content-Security-Policy, X-Content-Type-Options).npm install helmetandapp.use(helmet());.
8. Handling Special Cases
- Phone Number Formatting: The Infobip API generally expects numbers in international format (e.g.,
447123456789for the UK,14155552671for the US). Ensure your input validation or normalization logic enforces this format before sending to the API. - Character Encoding & Concatenation: Standard SMS messages have length limits (160 GSM-7 characters, 70 UCS-2 characters for non-standard alphabets). Longer messages are often automatically split (concatenated) by carriers and Infobip. Be aware that concatenated messages consume more credits. Infobip's API handles much of this, but be mindful of text length if cost is critical.
- DLR Latency: Delivery reports are not always instantaneous. There can be delays depending on carrier networks. Your application should handle the possibility that a DLR arrives seconds, minutes, or sometimes even longer after the message was sent. The
PENDINGstatus indicates the message is still in transit. - Duplicate DLRs: While Infobip aims to deliver reports reliably, network issues or slow responses from your endpoint (timeouts before you send 200 OK) could lead to Infobip retrying the webhook delivery. Design your processing logic (
/infobip-dlr) to be idempotent — meaning processing the same DLR multiple times has no adverse effects. Using theinfobip_message_idas a unique key in your database helps achieve this (e.g., update status only if the new status is different or more final than the existing one). - Time Zones: Use
TIMESTAMPTZ(Timestamp with Time Zone) in your database schemas (created_at,last_updated_at,callback_received_at) to store timestamps unambiguously. Log timestamps usingtoISOString()for clarity.
9. Performance Optimizations (Conceptual)
For high-volume applications:
- Asynchronous Processing: If DLR processing involves complex logic or database updates, consider moving it out of the main
/infobip-dlrrequest handler. Immediately send the200 OKresponse, and then push the DLR payload onto a message queue (like RabbitMQ, Redis Streams, or AWS SQS) for background workers to process. This prevents holding up Infobip's request and avoids timeouts. - Database Indexing: As shown in the conceptual schema, index columns frequently used in
WHEREclauses (especiallyinfobip_message_id). - Connection Pooling: Ensure your database client (e.g., Sequelize, node-postgres) uses connection pooling to reuse database connections efficiently.
- Caching: If you frequently query message statuses, consider caching recent statuses (e.g., in Redis) to reduce database load, especially for non-final states. Invalidate the cache when a final DLR (Delivered, Undeliverable, Rejected) is received.
- Load Testing: Use tools like
k6,artillery, orJMeterto simulate traffic to your/send-smsendpoint and (if possible) your/infobip-dlrendpoint to identify bottlenecks under load.
10. Monitoring, Observability, and Analytics (Conceptual)
- Health Checks: Keep the simple
app.get('/')endpoint or create a dedicated/healthendpoint that checks basic connectivity (e.g., can reach the database). Monitoring services can ping this endpoint.
Frequently Asked Questions
How to handle Infobip SMS delivery reports?
Handle Infobip SMS delivery reports by setting up a webhook endpoint in your Node.js/Express application. This endpoint, specified by the `notifyUrl` parameter in your Infobip API request, receives real-time delivery updates in JSON format. Your application should then process this data, updating internal systems and triggering actions based on the delivery status (e.g., delivered, failed).
What is a notify URL for Infobip SMS?
The notify URL is a crucial parameter in the Infobip SMS API. It's the publicly accessible URL of your application's webhook endpoint, where Infobip sends real-time delivery reports (DLRs). This URL must be reachable by Infobip for your application to receive status updates.
Why does Infobip need a callback URL?
Infobip needs a callback URL (the `notifyUrl`) to send your application asynchronous updates about the delivery status of your SMS messages. This enables your system to react to successful deliveries, failures, or other status changes without constantly polling the Infobip API.
When should I use ngrok with Infobip?
Use ngrok during local development with Infobip to create a publicly accessible URL for your webhook endpoint. Since Infobip needs to reach your local server for DLR callbacks, ngrok provides a tunnel from a public URL to your localhost, making testing and development easier.
Can I retry failed Infobip SMS messages?
You can implement retry mechanisms for failed Infobip SMS messages, but only for initial failures from the Infobip API (like network or 5xx errors), not for final statuses like 'UNDELIVERABLE'. Use exponential backoff to avoid overloading the system, but don't retry messages marked as permanently undeliverable by the carrier or Infobip.
What is the Infobip SMS notify content type?
The Infobip SMS notify content type is 'application/json'. This parameter specifies that delivery reports sent to your webhook endpoint will be in JSON format, making parsing and processing the data within your application more structured and efficient.
How to set up Infobip DLR with Node.js?
Set up Infobip DLR with Node.js by creating an Express.js server with two key endpoints: `/send-sms` to initiate message sending and `/infobip-dlr` to receive callbacks. The `infobipService.js` file contains the logic for sending SMS and handling API interactions using Axios.
What is the purpose of express.json() middleware?
The `express.json()` middleware in Express.js is essential for handling Infobip DLR callbacks. It parses incoming JSON payloads in POST requests, making the data accessible via `req.body`. This allows you to easily process delivery reports and update your application's internal state.
Why use dotenv with Infobip API integration?
Dotenv helps manage environment variables securely when integrating with the Infobip API. It loads credentials like your API key from a `.env` file, keeping sensitive information out of your source code and version control.
How to send SMS with Infobip API and Node.js?
To send SMS with the Infobip API and Node.js, use the `sendSms` function from `infobipService.js`. This function requires the recipient's phone number in international format and the message text. It handles constructing the API request, including headers, and making the request using Axios.
How to test Infobip webhooks locally?
To test Infobip webhooks locally, expose your development server using ngrok and update the `notifyUrl` in your Infobip API requests to point to the generated ngrok HTTPS URL. This allows Infobip to reach your local server with DLR callbacks during development.
How to structure a database for Infobip DLRs?
Structure a database for Infobip DLRs with a table for outgoing messages. Include fields for message details, Infobip message ID, recipient number, status, error codes, and timestamps. Index the `infobip_message_id` column for efficient lookup during callback processing.
What are common status groups in Infobip DLR?
Common status groups in Infobip DLRs include 'PENDING', 'UNDELIVERABLE', 'DELIVERED', 'EXPIRED', and 'REJECTED'. These groups provide a broad categorization of the message delivery status. More detailed status names within each group provide further information.
What are best practices for logging Infobip DLR?
Best practices for logging Infobip DLRs include logging the full incoming request body to understand its structure and content. Log specific fields from each report, including messageId, status, recipient, and errors. Use structured logging and external logging services for improved analysis and alerting.
How to implement security for Infobip webhook endpoint?
Implement security for your Infobip webhook endpoint by using HTTPS for secure communication. Optional security measures could include signature validation (if supported by Infobip) to verify the sender, and IP whitelisting if feasible. Basic input validation is also important for security and preventing unexpected input.