Frequently Asked Questions
Start by setting up a new Node.js project and installing Fastify, the MessageBird SDK, and necessary dependencies like dotenv, qs, async-retry, and @fastify/rate-limit. Create a project structure with directories for source code, routes, and environment variables. Finally, set up your .env file with your MessageBird API key, webhook signing key, and originator information from your MessageBird dashboard. Use `npm run dev` to start the server in development mode with automatic restarts, or `npm start` for production.
It's crucial for verifying the authenticity of incoming webhooks, ensuring they originate from MessageBird. It's used to generate a signature that your application compares against the signature received in the webhook request headers, preventing unauthorized access to your application from external sources and potential malicious actors.
Because MessageBird webhooks use the 'application/x-www-form-urlencoded' content type. It's necessary to handle webhook signature verification which requires the raw request body. The custom parser added in `app.js` stores the raw body string and then parses the form data reliably using the `qs` library. This raw body is needed in src/routes/webhook.js within the verifyMessageBirdSignature function.
Once your server is running, send a POST request to the /api/send-message endpoint with a JSON payload containing the recipient's phone number and message content. Use a tool like curl or Postman to send the request, ensuring a valid E.164 formatted number is used. You should receive a JSON response with the message ID and status. Make sure that validation is done at the API endpoint. Failure to comply with the format will result in error messages being returned.
In the MessageBird dashboard, go to Flow Builder, create a custom flow triggered by SMS, and choose your number. In the next step, select "Forward to URL" and paste your ngrok HTTPS URL with the secret path from your .env file. Use POST and no authentication, then save and publish. Ensure that ngrok is used to tunnel the SMS message between your Fastify app and MessageBird. Note that changes to the webhook path should also be reflected in the Flow Builder configuration.
ngrok creates a secure tunnel from a public URL to your localhost server, essential for development as it makes your local app accessible to MessageBird's webhooks. Run 'ngrok http 3000' to create the tunnel and use the HTTPS URL it provides when configuring the webhook in MessageBird's flow builder.
The provided code implements a pre-handler hook that verifies the signature using an HMAC-SHA256 algorithm with your webhook signing key. The hook extracts necessary data from the header and combines the raw body of the request with the message timestamp to recompute the signature. If verification fails, the hook returns a 401 error, thereby blocking all other handlers.
Use async-retry for outgoing API calls (like sending SMS replies) to gracefully handle temporary network issues or MessageBird API hiccups. The code example demonstrates its use with messagebird.messages.create, retrying up to 3 times with exponential backoff. The provided code demonstrates the use of async-retry and how to use the library.
It signals to MessageBird that you've received and processed the webhook successfully, preventing MessageBird from retrying the webhook. This should be done *even if your app fails to send the reply*. Reply failures should be handled asynchronously using techniques such as queues. This helps make the application robust against transient failures without being retried unnecessarily by MessageBird.
Use a long, random secret path for your webhook URL, stored in the WEBHOOK_SECRET_PATH environment variable. The code checks for this variable and prevents start-up if the path is too short or missing. This increases security making it more difficult to discover and trigger your endpoint from external sources.
The app.js file demonstrates a best practice for handling errors. The provided code includes a global error handler, which helps to consistently format all error responses from the application and enhances maintainability. The response formatting also distinguishes between client errors and server errors. This helps the user differentiate between errors which were caused by them and those where the server was responsible.
pino-pretty formats Fastify's log output for better readability during development. In production, Fastify uses JSON logging which can be processed by logging and monitoring systems. Log levels are set via the LOG_LEVEL environment variable. This helps enhance debugging locally during development while optimizing for log aggregators.
Create separate directories for routes (webhook.js, api.js), keep your main application logic in app.js, and manage environment variables with .env. The provided project structure uses `/src/routes` which enhances maintainability and keeps the logic for separate concerns isolated.
Yes, Prisma can be integrated for database interactions (optional). You would install the necessary Prisma dependencies and configure the DATABASE_URL environment variable. The code provides comments and .gitignore entries related to Prisma. It is up to the user to further configure the application to use the database if desired.
This guide provides a step-by-step walkthrough for building a production-ready Node.js application using the Fastify framework to handle two-way SMS communication via the MessageBird API. We will cover project setup, webhook implementation, sending replies, security considerations, error handling, deployment, and verification.
By the end of this tutorial, you will have a functional Fastify application capable of receiving incoming SMS messages sent to a MessageBird number and automatically replying to them. This foundational setup enables use cases like customer support bots, automated notifications with response capabilities, and interactive SMS services.
Technologies Used:
@messagebird/api
: The official MessageBird Node.js SDK.dotenv
: For managing environment variables.qs
: For parsing URL-encoded form data reliably.async-retry
: For implementing retry logic on outgoing API calls.@fastify/rate-limit
: For protecting endpoints against abuse.@sentry/node
, etc.): For error tracking and monitoring (Optional).ngrok
/ Tunneling Service: To expose the local development server to the internet for testing webhooks.System Architecture:
Prerequisites:
ngrok
or a similar tunneling service installed for local development testing.1. Setting up the Project
Let's initialize our Node.js project using Fastify and install the necessary dependencies.
1.1 Environment Setup:
Ensure you have Node.js (v16 or later recommended) and npm installed. You can manage Node.js versions using
nvm
(Node Version Manager) if needed.1.2 Project Initialization:
Open your terminal and create a new project directory:
1.3 Install Dependencies:
Install Fastify, the MessageBird SDK,
dotenv
,pino-pretty
for development logs, and other runtime dependencies we'll use later (qs
,async-retry
,@fastify/rate-limit
, Sentry packages). We'll also addnodemon
as a development dependency.(Note: Prisma dependencies will be installed later in Section 6 if you choose to implement the database layer).
1.4 Project Structure:
Create a basic project structure for better organization:
Create the
src
andsrc/routes
directories.1.5 Configure Environment Variables:
Create a
.env.example
file with the necessary variable names:Now, create a
.env
file by copying.env.example
. Never commit your.env
file to version control.How to obtain MessageBird credentials:
MESSAGEBIRD_API_KEY
:.env
file.MESSAGEBIRD_WEBHOOK_SIGNING_KEY
:.env
file.MESSAGEBIRD_ORIGINATOR
:+12025550181
) or approved Alphanumeric Sender ID you purchased/configured in MessageBird that will send the replies. Find this under Numbers in your dashboard. Note: Alphanumeric Sender IDs have country restrictions and cannot receive replies. For two-way messaging, use a virtual number.Fill in the values in your
.env
file. Choose a secure, random string forWEBHOOK_SECRET_PATH
. Add yourDATABASE_URL
andSENTRY_DSN
if you plan to use those features.1.6 Create
.gitignore
:Create a
.gitignore
file to prevent sensitive files and unnecessary directories from being committed:1.7 Basic Fastify Server Setup:
Create the main application file
src/app.js
. This includes the critical custom content type parser needed for webhook signature validation.1.8 Add
package.json
Scripts:Update the
scripts
section in yourpackage.json
. The defaulttest
script is a placeholder; implementing actual tests is recommended but outside the scope of this initial setup guide.Now you can run
npm run dev
to start the server in development mode with automatic restarts, ornpm start
for production.2. Implementing Core Functionality: Handling Incoming SMS
The core of our two-way messaging system is the webhook handler. MessageBird will send an HTTP POST request to this endpoint whenever an SMS is sent to your configured MessageBird number.
2.1 Create the Webhook Route:
Create the file
src/routes/webhook.js
. This includes the crucial signature verification logic using the raw body provided by the custom parser inapp.js
.Explanation:
verifyMessageBirdSignature
(Hook):async
function is registered as apreHandler
hook for the webhook route.request.rawBodyString
(made available by our custom parser inapp.js
).signedPayload
as specified by MessageBird (timestamp + '.' + rawBody
).webhookSigningKey
.crypto.timingSafeEqual
to securely compare the received signature with the expected one_ preventing timing attacks.400
_401
_ or500
) and returns_ preventing the mainhandleIncomingSms
function from executing.handleIncomingSms
:request.body
.originator
_payload
_ andrecipient
. Note: Always verify these field names by logging a real webhook payload from MessageBird.messagebird.messages.create
to send the reply_ wrapped inasync-retry
.204 No Content
response back to MessageBird to acknowledge successful receipt and processing of the webhook. This should be sent even if the outgoing reply fails_ to prevent MessageBird from retrying the incoming webhook.WEBHOOK_SECRET_PATH
. Includes a basic check to ensure it's set.POST
handler for that path.preHandler
array includes ourverifyMessageBirdSignature
hook.3. Building an API Layer (Optional)
While the core functionality relies on the webhook_ you might want an API endpoint to manually trigger an outbound SMS for testing or other application purposes.
3.1 Create API Route:
Create
src/routes/api.js
:3.2 Register API Route in
app.js
:This was already done in Section 1.7 when setting up
src/app.js
:3.3 Testing the API Endpoint:
Once the server is running (
npm run dev
)_ you can test this endpoint usingcurl
or Postman:Replace
+15551234567
with a valid test phone number in E.164 format.Response Example (Success):
Response Example (Validation Error):
4. Integrating with MessageBird (Webhook Configuration)
Now_ we need to tell MessageBird where to send incoming SMS messages by configuring the webhook URL in Flow Builder.
4.1 Expose Local Server:
While developing_ your server running on
localhost:3000
isn't accessible from the public internet. Usengrok
to create a secure tunnel.Open a new terminal window (keep the Fastify server running in the first one) and run:
ngrok
will display output similar to this:Copy the
https://
forwarding URL (e.g.,https://randomstring.ngrok.io
). This is your temporary public URL. Use the HTTPS version.4.2 Configure Flow Builder:
ngrok
HTTPS URL and append your secret webhook path (from.env
). Example:https://randomstring.ngrok.io/your-very-secret-random-webhook-path
2xx
quickly. MessageBird's default retries are usually sufficient if your app has temporary downtime.Now, when an SMS is sent to your selected MessageBird number, MessageBird will execute this flow, sending a POST request (content type
application/x-www-form-urlencoded
) to yourngrok
URL, which forwards it to your local Fastify application's/your-very-secret-random-webhook-path
endpoint. The signature verification hook will run first.5. Implementing Proper Error Handling and Logging
Robust error handling and clear logging are essential for production applications.
5.1 Fastify Error Handling:
Fastify has built-in error handling. Our
try...catch
blocks in the route handlers manage specific API call errors. The signature verification hook handles its own errors. For broader, uncaught errors, Fastify's default handler will catch them. We added a globalsetErrorHandler
inapp.js
(Section 1.7) for consistent error responses.5.2 Logging:
We configured
pino-pretty
for readable development logs. In production (NODE_ENV=production
), Fastify defaults to efficient JSON logging, suitable for log aggregation tools (Datadog, Splunk, ELK stack, CloudWatch Logs).setErrorHandler
)LOG_LEVEL
environment variable (info
for production,debug
for troubleshooting).5.3 Retry Mechanisms:
2xx
response within its timeout period. Our goal is to always send204 No Content
quickly from the webhook handler, even if the subsequent reply fails. Handle reply failures asynchronously if necessary (e.g., push to a background queue).messagebird.messages.create
to fail. We usedasync-retry
(installed in Section 1.3 and implemented insrc/routes/webhook.js
in Section 2.1) to handle this gracefully.5.4 Testing Error Scenarios:
MESSAGEBIRD_WEBHOOK_SIGNING_KEY
in.env
and send an SMS. Observe the401 Unauthorized
response from theverifyMessageBirdSignature
hook in your Fastify logs and thengrok
web interface (http://127.0.0.1:4040
).MESSAGEBIRD_API_KEY
or simulate network issues. Observe the retry attempts and final failure logs from thehandleIncomingSms
function. Check that the webhook still returns204
to MessageBird./api/send-message
) with missing or malformed JSON data. Observe the400 Bad Request
response generated by Fastify's schema validation or the global error handler.