Frequently Asked Questions
Build a dedicated Fastify endpoint that receives POST requests containing message delivery status updates from MessageBird. This endpoint should verify the request authenticity, parse the payload, log the status, and respond with a 200 OK status.
It's a secret key provided by MessageBird, distinct from your API key, specifically for verifying the authenticity of webhook requests. It's used to generate and compare signatures, ensuring requests originate from MessageBird.
Webhooks provide real-time delivery status updates, crucial for applications needing confirmation beyond the initial API response. They push updates to your application, enabling you to react to changes immediately.
Use ngrok during development to create a public HTTPS URL for your local server, allowing MessageBird to send webhooks to your application while it's running locally.
Yes, the MessageBird Node.js SDK allows you to send test SMS messages. You'll need your Live API Key, and the code example in the article provides a guide for implementing a send endpoint in your Fastify application.
Retrieve the signature, timestamp, and raw body from the request headers and implement HMAC-SHA256 verification using your webhook signing key. The article provides code for generating the expected signature and performing a timing-safe comparison for security.
This MessageBird webhook event triggers whenever the status of a message changes, such as when it's sent, buffered, delivered, failed, or expired, allowing your application to track these updates in real time.
Implement signature verification using the provided code example to authenticate requests. Use HTTPS, input validation, and rate limiting to further secure your endpoint from misuse or abuse. A firewall based on MessageBird's IP ranges can supplement security.
Store the message ID, status, status timestamp, and recipient from the webhook payload. It's also recommended to record the time your server received the update ('receivedAt') for tracking potential delays. Use the provided schema guide for database design.
Double-check for incorrect signing keys, raw body modification before verification, or potential timestamp skew. Ensure the signed payload construction matches MessageBird's requirements precisely.
Implement idempotent processing logic. Check if the incoming status and timestamp are newer than the current status and timestamp for that message in your database before updating, preventing duplicates from affecting data integrity.
Check your firewall, ensure the webhook URL in the MessageBird dashboard is correct, verify your server is running and publicly accessible, ensure correct DNS, and make sure the webhook configuration includes the "message.updated" event type.
Rely on the 'statusDatetime' from the webhook payload, not the 'receivedAt' time on your server. Ensure your database update logic prevents overwriting newer statuses with older ones based on the correct timestamp order from MessageBird.
Respond with 200 OK immediately, then process the webhook asynchronously using a job queue for long-running tasks like database updates. Optimize database queries with appropriate indexing, and employ caching where relevant.
Implement health checks, track key metrics like received webhooks, signature verifications, processing errors, and latency, integrate error tracking, and centralize logging for analysis and alerting. Use relevant Fastify plugins and monitoring tools.
Build a robust webhook endpoint using Fastify to receive and process real-time message delivery status updates from MessageBird (now Bird). This guide walks you through project setup, deployment, and verification to reliably track sent message status.
Focus on handling incoming Delivery Status Reports (DLRs) via webhooks from MessageBird. Build a dedicated Fastify endpoint that securely receives these updates, logs them, and acknowledges receipt to MessageBird – a crucial component of any application requiring message delivery confirmation.
Note: MessageBird rebranded to Bird in 2023. API endpoints, SDKs, and webhook functionality remain compatible. This guide uses "MessageBird" for consistency with existing implementations, but Bird documentation references apply equally.
Project Overview and Goals
Why Webhooks Matter:
Without webhooks, you're blind to delivery failures, delays, and carrier rejections. Webhooks enable:
What You'll Build:
A Node.js application using Fastify that exposes a secure webhook endpoint. This endpoint will:
POST
requests from MessageBird containing message delivery status updates200 OK
to acknowledge receiptProblem Solved:
When you send SMS or messages via MessageBird, you need confirmation that messages were delivered, failed, or changed status. The initial API response isn't enough. MessageBird provides webhooks that push status updates to your application in real-time, enabling you to update internal systems, notify users, or trigger actions based on delivery success or failure.
Technologies:
.env
intoprocess.env
, keeping sensitive keys out of source codecrypto
module: Verifies webhook signaturesSystem Architecture:
Prerequisites:
test_
): Test API connectivity without sending messages or consuming credits. Note: Test keys are not supported for Conversations API. Use for development and integration testing. (Source)1. Set Up the Project
Initialize the Node.js project and install dependencies.
Create Project Directory:
Initialize Node.js Project:
Install Dependencies:
Create Project Structure:
Configure
.gitignore
: Prevent sensitive files from being committed to version control:Configure
.env
: Add placeholders for MessageBird credentials and server configuration. Obtain theMESSAGEBIRD_WEBHOOK_SIGNING_KEY
from the MessageBird dashboard when configuring webhooks (see Section 4).PORT
: Port where your Fastify server listensHOST
: Network interface to bind to (0.0.0.0
makes it accessible from outside Docker containers or VMs)MESSAGEBIRD_WEBHOOK_SIGNING_KEY
: Secret key from MessageBird for verifying webhook signatures (NOT your API Key). Generated when you add a webhook in the dashboard (covered in Section 4).Set Up Initial Fastify Server (
src/server.js
):Add Start Script to
package.json
:Run the Server:
You should see output indicating the server is running on port 3000. Visit
http://localhost:3000
in your browser to see the{ "hello": "world" }
response. Stop the server withCtrl+C
.2. Implementing the Core Webhook Handler
Understanding the Webhook Lifecycle:
When you send an SMS via MessageBird, the API returns an initial status (typically
sent
). As the message progresses through the carrier network, MessageBird receives Delivery Reports (DLRs) from the carrier and forwards these updates to your webhook endpoint. The webhook lifecycle is:buffered
)200 OK
Retry Behavior: If MessageBird doesn't receive a
2xx
response within several seconds, it will retry webhook delivery up to 10 times with exponential backoff. Always respond quickly with200 OK
and process heavy operations asynchronously. (Source)Now, create the endpoint that will receive MessageBird's status updates.
Define the Webhook Route (
src/server.js
): MessageBird sends status updates viaPOST
request. Define a route,/webhooks/messagebird
, to handle these. Add the following code insidesrc/server.js
, before thestart
function definition.200 OK
response immediately. Important: Handle long-running processes asynchronously (e.g., push to a queue) after sending the 200 OK to avoid MessageBird timeouts and retries.Understanding the Payload: MessageBird's delivery status webhook payload typically looks like this (structure may vary slightly based on event type and API version):
Complete Status Values (verified from MessageBird SMS API documentation):
scheduled
scheduledDatetime
parameter)sent
buffered
delivered
expired
validity
parameter)delivery_failed
Status Reasons (accompanying
statusReason
field provides additional detail):successfully delivered
– Positive DLR receivedpending DLR
– Awaiting delivery report from carrierDLR not received
– No DLR received before validity expirationunknown subscriber
– Recipient number not associated with active line (error code 1)unavailable subscriber
– Recipient temporarily unreachable (error codes 8, 27-29, 31, 33)expired
– Validity period expired before delivery attemptopted out
– Recipient revoked consent to receive messages (error code 103)received network error
– Carrier network issue preventing delivery (error codes 7, 12, 21, 30, 34-36, 39-40, 71)insufficient balance
– Account balance too low (error code 100)carrier rejected
– Carrier blocked message due to registration requirements (error codes 104-105, 110)capacity limit reached
– Campaign volume/throughput limits exceeded (error codes 106-107)generic delivery failure
– No detailed information available from carrierFocus primarily on
id
,status
,statusDatetime
, and potentiallyrecipient
orreference
to correlate the update with your original message.Processing the Payload: Add basic processing logic to log the key information. Update the
/webhooks/messagebird
route handler insrc/server.js
:This code attempts to parse the
id
,status
,statusDatetime
, andrecipient
from the request body and logs them.3. Building a Complete API Layer (Optional Send Endpoint)
While the core goal is handling incoming webhooks, adding an endpoint to send a message via MessageBird helps test the full loop. You can skip this section if you plan to test webhooks only by sending messages from other applications or the MessageBird dashboard.
This requires the official MessageBird Node.js SDK.
Install MessageBird SDK:
Add API Key to
.env
: You'll need your Live API Key from the MessageBird Dashboard (Developers → API access (REST)). Add it to your.env
file.API Key Types:
test_
prefix): Tests API connectivity only; no messages sent, no credits consumed. Not supported for Conversations API.(Source)
Replace the placeholder with your actual key.
Implement Send Endpoint (
src/server.js
): Add a new route to trigger sending an SMS. Add this code insidesrc/server.js
, afterrequire('dotenv').config();
and before the webhook route.messagebird
SDK.recipient
andmessage
from the POST body.messagebird.messages.create
to send the SMS.originator
to a valid sender ID or Virtual Mobile Number registered in your MessageBird account. Alphanumeric IDs have restrictions.reportUrl
parameter inmessages.create
can override the default webhook URL set in the dashboard for specific messages, but rely on the dashboard setting primarily.Testing the Send Endpoint: Once your server is running (restart it after adding the new code and API key), use
curl
or a tool like Postman:4. Integrating with MessageBird (Webhook Configuration)
Connect MessageBird's status updates to your running application.
Expose Your Local Server: MessageBird needs a publicly accessible URL to send webhooks to. During development, tools like
ngrok
are essential.ngrok
will provide a public URL (e.g.,https://<random_string>.ngrok.io
). Copy this HTTPS URL.Configure Webhook in MessageBird Dashboard:
message.updated
. This event triggers for status changes like sent, delivered, failed, etc.https://<random_string>.ngrok.io/webhooks/messagebird
POST
is selected(Source)
Update
.env
with Signing Key: Paste the Webhook Signing Key (NOT your API key) you copied from the MessageBird dashboard into your.env
file:Restart your Fastify server for the new environment variable to load (
Ctrl+C
thennpm start
).5. Implementing Error Handling and Logging
Enhance error handling, especially around webhook processing.
Consistent Error Handling: The current
try...catch
in the webhook handler is a good start. Ensure critical errors (like signature failure, added later) prevent further processing and potentially return a non-200 status (though MessageBird generally prefers 200 OK for acknowledgment, so log the failure).Logging Levels: Fastify's logger supports levels (
info
,warn
,error
,debug
, etc.). Use them appropriately:info
: Standard operations (webhook received, status processed)warn
: Non-critical issues (payload missing optional fields, unexpected status)error
: Critical failures (signature mismatch, database errors, unhandled exceptions)Retry Mechanisms:
2xx
response within several seconds. Your primary goal is to respond quickly with200 OK
. (Source)async-retry
or push the job to a queue (like BullMQ, RabbitMQ) that handles retries with exponential backoff. This is beyond the scope of the basic handler but essential for production robustness.Log Management Best Practices:
pino-pretty
for development and log rotation tools likelogrotate
(Linux) or Winston transports in production6. Creating a Database Schema and Data Layer (Conceptual)
Store message statuses for historical tracking and analytics.
Choosing a Database:
Conceptual ERD:
Data Layer Interaction (Pseudocode within webhook handler): This pseudocode shows where database interaction would fit inside the webhook handler's
try
block, after signature verification (added in the next step).messageId
from the webhook payload as the primary key or foreign key to find and update your message record.status
andstatusTimestamp
.receivedAt
) to track potential delays.7. Adding Security Features
Secure your webhook endpoint.
Webhook Signature Verification (CRITICAL): Confirm the request genuinely came from MessageBird/Bird. Important: The official signature verification method differs from simpler timestamp+body approaches.
Option A: Using fastify-raw-body plugin (Recommended for Fastify 4.x)
Then register it:
Then enable it for the webhook route:
Option B: Custom Content Type Parser
Add the following near the top of
src/server.js
, after instantiatingfastify
:The official verification process (as documented in Bird API docs, November 2024) is:
messagebird-signature
headertimestamp + "\n" + requestURL + "\n" + bodyHash
Create this function in
src/server.js
:Troubleshooting Signature Verification:
Common failures and solutions:
MESSAGEBIRD_WEBHOOK_SIGNING_KEY
matches dashboard value exactly (no extra spaces)timestamp
,requestUrl
, andrawBody
length to debug payload construction/webhooks/messagebird
route handler to call the verification function with the request URL.Critical Implementation Notes:
\n
)X-Forwarded-Proto
andX-Forwarded-Host
headers to construct the correct URLInput Validation: While signature verification is key, basic checks on the payload structure (as shown in the processing step: checking for
payload.id
andpayload.status
) help prevent errors if MessageBird's payload format changes unexpectedly. For more complex validation, consider using Fastify's built-in schema validation capabilities.Rate Limiting: Protect your endpoint from abuse or misconfigured retries.
npm install @fastify/rate-limit
start
function insrc/server.js
, beforefastify.listen
:This adds basic IP-based rate limiting. Configure
max
andtimeWindow
based on expected legitimate traffic from MessageBird.HTTPS: Always use HTTPS for your webhook endpoint.
ngrok
provides this automatically for development. In production, ensure your deployment environment (e.g., behind a load balancer or reverse proxy) terminates TLS/SSL.Additional Security Measures:
bodyLimit
in Fastify options to prevent oversized payloads.8. Handling Special Cases
Handle edge cases in real-world scenarios.
Duplicate Webhooks:
MessageBird might occasionally send the same update twice (e.g., during network issues or retries). Make your processing logic idempotent. Before updating a status, check if the current status in your database matches the incoming one, or if the incoming
statusTimestamp
is older than the last recorded update for that message.Example Idempotency Check:
Out-of-Order Updates:
Network latency could cause an older status update (e.g._ 'sent') to arrive after a newer one ('delivered'). Always use the
statusTimestamp
from the payload to determine the actual sequence of events_ not the time your server received the webhook (receivedAt
). When updating your database_ ensure you don't overwrite a newer status with an older one based on thestatusTimestamp
.Example Timestamp-Based Ordering:
Different Status Types:
Handle all possible statuses appropriately in your logic. Official status values (as of 2024-2025_ source):
scheduled
sent
buffered
orpending
delivered
expired
delivery_failed
orfailed
Common failure reasons in DLR (
statusReason
field):unknown subscriber
– Number not associated with active line (error code 1)unavailable subscriber
– Temporarily unreachable (error codes 8_ 27-29_ 31_ 33)insufficient balance
– Account balance too low (error code 100)carrier rejected
– Carrier blocked due to registration requirements (error codes 104-105_ 110)capacity limit reached
– Campaign limits exceeded (error codes 106-107)generic delivery failure
– No detailed info from carrierConsult the official Bird/MessageBird documentation for a complete list of statuses and error codes at https://docs.bird.com/ and https://developers.messagebird.com/api/sms-messaging
Timestamp Time Zones:
The
statusDatetime
is typically in ISO 8601 format with a UTC offset (often+00:00
). Store timestamps consistently in your database_ preferably as UTC (e.g._ using JavaScriptDate
objects or database timestamp types that handle time zones)_ and handle time zone conversions only when displaying data to users.9. Implementing Performance Optimizations
Webhook endpoints need to respond to MessageBird promptly.
Respond Quickly:
Send the
200 OK
response before doing any potentially slow processing (like complex database updates_ external API calls). Signature verification should be fast enough to happen before the response.Asynchronous Processing:
Offload heavy tasks (database writes_ further API calls_ notifications) to a background job queue. The webhook handler simply verifies the signature_ parses essential data_ pushes a job to the queue_ and returns
200 OK
. A separate worker process consumes jobs from the queue and handles the database updates or other logic.Popular Queue Libraries:
amqplib
): Enterprise-grade message broker_ supports complex routingkafkajs
): High-throughput_ distributed streaming platform for large-scale systemsDatabase Indexing:
Ensure your
messages
table (or equivalent) is indexed onmessageId
(or whatever field you use to look up messages based on the webhook payload) for fast lookups when processing updates.Caching:
Caching is less relevant for writing status updates but could be used if the webhook needed to read related data frequently during processing (though ideally offload this to the async worker).
10. Adding Monitoring_ Observability_ and Analytics
Know what your webhook handler is doing in production.
Health Checks:
Add a simple health check endpoint that monitoring systems can poll.
Metrics to Track:
/webhooks/messagebird
requestsUse monitoring tools like Prometheus/Grafana, Datadog, New Relic, or your cloud provider's monitoring services. Fastify plugins like
fastify-metrics
can help expose Prometheus metrics.Error Tracking:
Integrate an error tracking service (e.g., Sentry, Bugsnag, Datadog APM). Use Fastify's
setErrorHandler
to capture unhandled errors globally and report them.Logging:
Ensure logs are structured (JSON format is good, which Pino, Fastify's default logger, provides) and aggregated in a central logging system (e.g., ELK stack, Loki, Datadog Logs, CloudWatch Logs) for analysis, searching, and alerting on specific error patterns.
11. Troubleshooting and Caveats
Webhook Not Received:
https://
and the correct path (/webhooks/messagebird
). Verify that yourngrok
tunnel (or production URL) is active and pointing to the running Fastify application.Signature Verification Fails (401 Errors):
MESSAGEBIRD_WEBHOOK_SIGNING_KEY
in your.env
file exactly matches the key generated and displayed in the MessageBird dashboard for that specific webhook URL. Ensure there are no extra spaces or characters. Restart your server after updating.env
.verifyMessageBirdSignature
function accessesrequest.rawBody
. TheaddContentTypeParser
setup should preserve it correctly.requestUrl
construction matches the exact URL MessageBird uses. Behind proxies, ensureX-Forwarded-Proto
andX-Forwarded-Host
headers are correctly read.Processing Errors (Logged after 200 OK):
MessageBird Retries:
If MessageBird repeatedly sends the same webhook, your endpoint isn't consistently returning a
2xx
status code within their timeout period (typically a few seconds). Focus on responding quickly (Step 9) and ensure signature verification is correct. Check MessageBird's webhook delivery logs in their dashboard for details on failures.