Frequently Asked Questions
Set up a webhook endpoint using Fastify and Node.js to receive real-time SMS delivery reports from Infobip. This involves creating a Fastify application, validating incoming requests, parsing the payload, and storing the status information in a database. The guide provides a step-by-step process for implementing this.
Infobip delivery reports provide real-time updates on the status of your sent SMS messages, such as whether they were delivered, failed, or are pending. This allows for more accurate tracking than relying solely on the initial API response when sending messages.
Fastify is a high-performance Node.js web framework chosen for its speed and developer-friendly plugin ecosystem. Its efficiency makes it ideal for handling real-time updates from webhooks like those from Infobip.
Use Infobip delivery report webhooks when you need real-time tracking of SMS message statuses beyond the initial API sending response. They are crucial for applications requiring confirmation of delivery or handling failures.
Yes, while the example uses PostgreSQL with Prisma, you can adapt the project to other databases. The provided Prisma schema can be modified to work with MySQL, SQLite, SQL Server, or MongoDB by changing the provider in the schema file.
Verify the signature using an HMAC-SHA256 algorithm with a shared secret configured in your Infobip account and your .env file. The signature is calculated based on the raw request body, so ensure your Fastify setup stores this raw body before parsing the JSON.
Prisma acts as an ORM (Object-Relational Mapper), simplifying database interactions. It's used to define the database schema, manage migrations, and provide a client for querying and storing delivery report data in the database.
Implement robust error handling using try...catch blocks within the webhook handler, especially within database operations. Use Fastify's HTTP error utilities (@fastify/sensible) for clear error responses, and implement logging for tracking issues.
Secure your endpoint by verifying the HMAC-SHA256 signature of incoming requests, using HTTPS, validating input against a schema, and implementing rate limiting to prevent abuse. Proper secrets management and least privilege database access are also essential.
You need a working Node.js environment, npm or yarn, an active Infobip account with API access, database access, a way to publicly expose your server (like ngrok for development), and a basic understanding of JavaScript, Node.js, REST APIs, and webhooks.
The rawBody contains the original, unparsed request body which is crucial for calculating the HMAC-SHA256 signature. Ensure your Fastify application's content type parser stores this raw body before any JSON parsing occurs.
Store sensitive information like the INFOBIP_WEBHOOK_SECRET and DATABASE_URL in environment variables. Never hardcode or commit them to version control. In production, use a dedicated secrets management system.
The provided code handles duplicates using Prisma's upsert functionality, ensuring idempotent database updates based on the unique messageId. This, combined with signature verification, prevents processing unauthorized or unintended duplicates.
The handler processes results in batches using an array. For extremely large batches, consider potential performance impacts on database transactions and optimize accordingly. Ensure your database operations are efficient for handling bulk updates.
Track the status of your sent SMS messages in real-time by setting up a webhook endpoint using Fastify to receive delivery reports directly from Infobip. This guide provides a step-by-step walkthrough for building a webhook handler foundation in Node.js.
We'll cover everything from initial project setup and core logic implementation to security considerations, database integration, error handling, deployment, and testing.
Project Overview and Goals
This guide details how to build a reliable webhook endpoint using Node.js and the Fastify framework to receive and process SMS delivery status updates sent by Infobip.
Problem Solved: When you send SMS messages via an API like Infobip's, you often need confirmation of whether the message was successfully delivered, failed, or is still pending. Relying solely on the initial API response isn't enough for final status tracking. Infobip provides delivery report webhooks, pushing status updates to an endpoint you specify. This project builds that endpoint.
Key Goals:
Technologies Used:
@prisma/client
: The runtime client for Prisma.dotenv
: For managing environment variables.pino
: Fastify's default high-performance JSON logger.Prerequisites:
npm
oryarn
package manager.ngrok
) for testing webhooks, or a deployed environment.Final Outcome: A functional Node.js application capable of securely receiving, validating, processing, and storing Infobip SMS delivery reports, providing a solid foundation for production deployment.
1. Setting up the project
Let's initialize our Node.js project and install the necessary dependencies.
Create Project Directory: Open your terminal and create a new directory for the project, then navigate into it.
Initialize npm Project: Initialize the project using npm. You can accept the defaults or customize them.
This creates a
package.json
file.Install Dependencies: We need Fastify, dotenv for environment variables, @fastify/sensible for utilities, the Prisma client for database interaction, and Prisma CLI/pino-pretty for development.
fastify
: The core web framework.dotenv
: Loads environment variables from a.env
file.@fastify/sensible
: Adds useful decorators and error handling utilities.@prisma/client
: The runtime Prisma client library.pino-pretty
: Formats Pino logs during development (dev dependency).prisma
: The Prisma CLI for migrations and generation (dev dependency).nodemon
: Utility to auto-restart the server during development (dev dependency).Configure
package.json
Scripts: Add scripts to yourpackage.json
for running the application:start
: Runs the application directly with Node.dev
: Runs the application usingnodemon
for auto-restarts on file changes and pipes logs throughpino-pretty
.db:migrate
: Applies database migrations using Prisma during development.db:generate
: Generates the Prisma client based on your schema.Create Project Structure: Organize your project files:
Create
.gitignore
: Add common Node.js ignores:Create
.env
File: This file will hold your environment variables. Do not commit this file to version control.INFOBIP_WEBHOOK_SECRET
: A secret string shared between your application and Infobip, used for verifying webhook signatures. You must define this and configure it in your Infobip webhook settings.DATABASE_URL
: Connection string for your database. Adjust accordingly.Initialize Prisma: Set up Prisma in your project.
This creates the
prisma/schema.prisma
file and updates.env
with a placeholderDATABASE_URL
if it wasn't already present. Configureprisma/schema.prisma
as shown in the next section.2. Creating a database schema and data layer (Prisma)
We need a way to store the delivery status updates. We'll use Prisma for this.
Define Prisma Schema: Open
prisma/schema.prisma
and define the data source, generator, and a model to store status updates.MessageStatus
table.messageId
is marked unique as we typically want the latest status for a given message.Run Initial Migration: Create the database table based on the schema. Make sure your database server is running and accessible using the
DATABASE_URL
in.env
.This command:
prisma/migrations/
.node_modules/.prisma/client
).Create Prisma Plugin for Fastify: Create a Fastify plugin to instantiate and provide the Prisma client to your routes.
PrismaClient
instance.fastify.prisma
, making the client available in request handlers.onClose
hook.3. Implementing core functionality & API layer
Now, let's build the main application logic and the webhook endpoint.
Configure Fastify Application (
src/app.js
): Set up the core Fastify application, register plugins, and routes.dotenv
.application/json
. This parser does two things:req.rawBody
. This is essential for verifying the webhook signature later, as the signature is calculated based on the raw, unparsed body.done(null, json)
).@fastify/sensible
and ourprismaPlugin
./webhooks
prefix./health
check endpoint.Create Server Entry Point (
src/server.js
): This file builds and starts the Fastify server.app.js
.SIGINT
andSIGTERM
.Implement Webhook Route (
src/routes/webhooks.js
): Define the endpoint that Infobip will call.deliveryReportSchema
). This is explicitly marked as a placeholder. You must replace this with the actual schema based on Infobip's documentation.preHandler
):INFOBIP_WEBHOOK_SECRET
.X-Infobip-Signature
– verify this header name).request.rawBody
.sha256
– verify this algorithm) using the secret andrequest.rawBody
.crypto.timingSafeEqual
.fastify.post('/infobip', ...)
):results
array.fastify.prisma.messageStatus.upsert
for idempotent database updates/creates.Promise.allSettled
.200 OK
response back to Infobip.4. Integrating with Infobip
Your application is ready to receive webhooks, but you need to tell Infobip where to send them and provide the necessary secret.
Obtain Public URL: Your webhook endpoint must be accessible from the public internet.
ngrok
to expose your local server. Runngrok http 3000
(if your app runs on port 3000).ngrok
provides a temporary public HTTPS URL (e.g.,https://<unique-subdomain>.ngrok.io
). Note: Freengrok
URLs change each time you restart it, and have usage limitations; they are suitable only for temporary development testing.https://your-app-domain.com
). This URL must be HTTPS.Configure Infobip Webhook:
https://<your-public-url>/webhooks/infobip
..env
file forINFOBIP_WEBHOOK_SECRET
.preHandler
code (crypto.createHmac('sha256', ...)
).API Key Security: While this guide focuses on receiving webhooks, remember that your sending application needs an Infobip API Key. Store this key securely (e.g., environment variable) and never commit it to version control. The
INFOBIP_WEBHOOK_SECRET
is also highly sensitive and should be treated like a password.5. Implementing proper error handling, logging, and retry mechanisms
@fastify/sensible
provides convenient HTTP error generation (e.g.,fastify.httpErrors.unauthorized()
).preHandler
catches authentication/configuration errors early.try...catch
within the mapping loop (viaPromise.allSettled
) to handle potential database errors for individual status updates without crashing the entire request.fastify.setErrorHandler()
if needed.pino
for efficient JSON logging. Configured insrc/app.js
. SetLOG_LEVEL
via.env
.pino-pretty
(package.json
'sdev
script) for readability. In production, ingest structured JSON logs into a log management system (e.g., Datadog, ELK stack, Splunk, Loki).200 OK
(e.g., downtime, crash, signature failure causing 4xx/5xx), Infobip may retry sending the webhook. Check their documentation for specific retry policies (timing, number of attempts).prisma.upsert
based on the uniquemessageId
achieves idempotency at the database level. The signature check prevents processing unauthorized duplicates.async-retry
). However, often it's better to log the error clearly and rely on idempotency + Infobip's retries, or push failed updates to a dead-letter queue for separate investigation/processing. Ensure your endpoint still returns200 OK
quickly even if some internal processing fails, to prevent unnecessary Infobip retries.6. Adding security features
preHandler
hook using HMAC (algorithm verified against Infobip docs) and a strong, unique shared secret. Ensures authenticity and integrity.deliveryReportSchema
) provides basic structure validation. Crucially, ensure this schema matches Infobip's actual payload. SetremoveAdditional: false
cautiously; for stricter security, define a precise schema and consider settingremoveAdditional: true
or explicitly handle unexpected properties.ngrok
provides this for development. In production, ensure your load balancer or server terminates TLS/SSL.@fastify/rate-limit
.INFOBIP_WEBHOOK_SECRET
andDATABASE_URL
securely using environment variables. Never hardcode or commit them. Use a secrets management system in production (e.g., HashiCorp Vault, AWS Secrets Manager, Doppler, platform-specific secrets).DATABASE_URL
should have only the minimum required permissions (e.g.,SELECT
,INSERT
,UPDATE
on theMessageStatus
table) and not broad administrative rights.7. Handling special cases relevant to the domain
upsert
and the unique constraint onmessageId
. Signature verification prevents processing unauthorized duplicates.DELIVERED
beforeACCEPTED
). Theupsert
logic inherently handles this by always storing the latest received state for a givenmessageId
. If sequence matters critically, you might need to compare timestamps (doneAt
from Infobip orreceivedAt
from your system).results
array. If batches become extremely large, consider performance implications. Ensure database transactions or operations can handle the batch size efficiently.sentAt
,doneAt
,receivedAt
). Store timestamps in UTC (DateTime
in Prisma typically maps totimestamp with time zone
in PostgreSQL) and perform conversions as needed for display or comparison.