code examples
code examples
Build a WhatsApp Messaging Integration with Node.js, Fastify, and Infobip API
Complete step-by-step guide to building a Node.js application using Fastify framework to send and receive WhatsApp messages via Infobip API, including webhooks, error handling, and deployment.
Build a WhatsApp Messaging Integration with Node.js, Fastify, and Infobip API
This guide provides a step-by-step walkthrough for building a Node.js application using the Fastify framework to send and receive WhatsApp messages via the Infobip API. We will cover everything from initial project setup to handling webhooks, error management, and basic deployment considerations.
By the end of this tutorial, you will have a functional Fastify application capable of sending WhatsApp text messages and receiving incoming messages through Infobip's webhook service. This solves the common need for businesses to programmatically interact with customers on WhatsApp for notifications, support, or engagement, leveraging the speed of Fastify and the robust infrastructure of Infobip.
Project Overview and Goals
-
Goal: Create a Node.js application using Fastify to send outbound WhatsApp messages and process inbound messages via Infobip.
-
Problem Solved: Enables programmatic WhatsApp communication without directly managing the complexities of the WhatsApp Business API infrastructure. Provides a foundation for building chatbots, notification systems, or integrating WhatsApp into existing business workflows.
-
Technologies:
- Node.js: The JavaScript runtime environment.
- Fastify: A high-performance, low-overhead Node.js web framework chosen for its speed (capable of serving up to 30,000 requests per second depending on code complexity), extensibility, and developer-friendly features like built-in logging and validation. Schema-based validation using JSON Schema is recommended but not mandatory.
- Infobip: A communication platform-as-a-service (CPaaS) providing APIs for various channels, including WhatsApp. This integration uses their official Node.js SDK (
@infobip-api/sdk). The WhatsApp API has a rate limit of 4,000 requests per second per account. - dotenv: A module to load environment variables from a
.envfile for secure configuration management.
-
Architecture:
mermaidgraph LR A[User/Client App] -- HTTP POST --> B(Fastify App); B -- Send Message API Call --> C(Infobip API); C -- Delivers --> D(WhatsApp User); D -- Sends Reply --> C; C -- Webhook POST --> B; B -- Processes Incoming Message --> E(Log/Database/Logic); style B fill:#f9f,stroke:#333,stroke-width:2px; style C fill:#ccf,stroke:#333,stroke-width:2px; -
Prerequisites:
- Node.js installed. The Infobip SDK requires Node.js v14 minimum, but production deployments should use an Active or Maintenance LTS version: v22 (Active LTS, recommended) or v20 (Maintenance LTS). See the Node.js release schedule for current support status.
- npm (included with Node.js).
- An active Infobip account. A free trial account is sufficient for testing but has limitations (typically, you can only send messages to your registered phone number). Sign up at Infobip.
- Your Infobip API Key and Base URL (obtain from the Infobip Portal).
- A WhatsApp sender number registered and approved within your Infobip account.
- A publicly accessible URL for receiving webhooks. Use tools like
ngrokfor local development. Production requires a deployed server with a public IP or domain name and HTTPS. - Important WhatsApp Policy: Text messages can only be delivered if the recipient has contacted your business within the last 24 hours. Outside this window, you must use WhatsApp template messages. See WhatsApp's documentation for details.
-
Outcome: A running Fastify server with two primary endpoints:
POST /send: Accepts a request to send a WhatsApp message.POST /infobip-webhook: Listens for incoming WhatsApp messages forwarded by Infobip.
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 your project, then navigate into it:
bashmkdir fastify-infobip-whatsapp cd fastify-infobip-whatsapp -
Initialize Node.js Project: Run
npm initand follow the prompts. You can accept the defaults for most options.bashnpm init -yThis creates a
package.jsonfile. -
Install Dependencies: We need Fastify for the web server, the Infobip Node.js SDK to interact with their API, and
dotenvto manage our API credentials securely.bashnpm install fastify @infobip-api/sdk dotenv -
Create Project Structure: Set up a basic directory structure for clarity:
bashmkdir src touch src/server.js touch .env touch .gitignoresrc/server.js: Will contain our main application code..env: Stores sensitive information like API keys (will be ignored by Git)..gitignore: Specifies files and directories that Git should ignore.
-
Configure
.gitignore: Addnode_modulesand.envto your.gitignorefile to prevent committing dependencies and secrets:plaintext# .gitignore node_modules .env npm-debug.log -
Set up Environment Variables: Open the
.envfile and add your Infobip credentials and the phone number associated with your Infobip WhatsApp sender. You'll also define the port for your server.-
Purpose:
INFOBIP_API_KEY: Your secret API key for authenticating requests with Infobip.INFOBIP_BASE_URL: The specific API endpoint domain provided by Infobip for your account (the full URL).INFOBIP_WHATSAPP_SENDER: The registered phone number (including country code) used to send messages via Infobip.PORT: The local port your Fastify server will listen on.WEBHOOK_SECRET: A secret string you define, used to verify incoming webhooks (basic security).
-
How to Obtain Infobip Values:
- Log in to your Infobip Portal.
- Navigate to the
Developerssection orAPI Keys(exact location might vary slightly). - Create or copy an existing API Key (
INFOBIP_API_KEY). - Your Base URL (
INFOBIP_BASE_URL) is usually displayed prominently on the API Keys page or homepage after login. Copy the complete Base URL provided (e.g.,xyz123.api.infobip.com). - Your WhatsApp Sender number (
INFOBIP_WHATSAPP_SENDER) is the number you registered with Infobip for sending WhatsApp messages. Find this under theChannels and Numbers->WhatsAppsection.
dotenv# .env INFOBIP_API_KEY=YOUR_ACTUAL_INFOBIP_API_KEY INFOBIP_BASE_URL=YOUR_FULL_INFOBIP_BASE_URL INFOBIP_WHATSAPP_SENDER=YOUR_WHATSAPP_SENDER_NUMBER_WITH_COUNTRY_CODE PORT=3000 WEBHOOK_SECRET=a_very_secret_string_you_should_changeImportant: Replace the placeholder values with your actual credentials. Never commit your
.envfile to version control. -
2. Implementing core functionality: Sending Messages
Now, let's write the code to initialize Fastify and the Infobip client, and create an endpoint to send messages.
-
Load Environment Variables and Initialize Clients: Open
src/server.js. We'll start by loadingdotenv, importing dependencies, and setting up Fastify and the Infobip client.javascript// src/server.js 'use strict'; // Load environment variables from .env file require('dotenv').config(); // Import dependencies const Fastify = require('fastify'); const { Infobip, AuthType } = require('@infobip-api/sdk'); // --- Configuration Validation --- // Ensure essential environment variables are set const requiredEnv = ['INFOBIP_API_KEY', 'INFOBIP_BASE_URL', 'INFOBIP_WHATSAPP_SENDER', 'PORT', 'WEBHOOK_SECRET']; for (const variable of requiredEnv) { if (!process.env[variable]) { console.error(`Error: Missing required environment variable ${variable}. Please check your .env file.`); process.exit(1); // Exit if critical config is missing } } const PORT = process.env.PORT || 3000; const INFOBIP_API_KEY = process.env.INFOBIP_API_KEY; const INFOBIP_BASE_URL = process.env.INFOBIP_BASE_URL; const INFOBIP_WHATSAPP_SENDER = process.env.INFOBIP_WHATSAPP_SENDER; const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; // --- Initialize Fastify --- // Enable Fastify's built-in logger for better debugging const fastify = Fastify({ logger: true }); // --- Initialize Infobip Client --- // Use API Key authentication as recommended let infobipClient; try { infobipClient = new Infobip({ baseUrl: INFOBIP_BASE_URL, apiKey: INFOBIP_API_KEY, authType: AuthType.ApiKey, // Specify the authentication type }); fastify.log.info('Infobip client initialized successfully.'); } catch (error) { fastify.log.error({ err: error }, 'Failed to initialize Infobip client'); process.exit(1); // Cannot proceed without the client } // --- Simple In-Memory Store for Received Messages (Illustrative) --- // In production, use a proper database (PostgreSQL, MongoDB, Redis, etc.) const receivedMessages = []; // --- Routes will go here --- // --- Start Server Function --- const start = async () => { try { await fastify.listen({ port: PORT, host: '0.0.0.0' }); // Listen on all available network interfaces fastify.log.info(`Server listening on port ${fastify.server.address().port}`); } catch (err) { fastify.log.error(err); process.exit(1); } }; // --- Run the server --- start();- Why
dotenv.config()first? It ensures environment variables are loaded before any other code tries to access them. - Why configuration validation? Catching missing configuration early prevents runtime errors.
- Why
Fastify({ logger: true })? Enables detailed logging of requests, responses, and errors, crucial for development and debugging. - Why
AuthType.ApiKey? Explicitly tells the SDK which authentication method to use, matching the Infobip standard. - Why
try...catcharoundnew Infobip()? Handles potential errors during client initialization (e.g., invalid base URL format). - Why
host: '0.0.0.0'? Makes the server accessible from outside its container or local machine (important for Docker or receiving webhooks).
- Why
-
Create the Send Message Route: Add the following route definition inside
src/server.jsbefore thestart()function call.javascript// src/server.js // ... (previous code) ... // --- Routes --- // Route to send a WhatsApp text message fastify.post('/send', async (request, reply) => { const { to, text } = request.body; // Basic input validation if (!to || !text) { reply.code(400); // Bad Request return { error: 'Missing required fields: `to` and `text`' }; } // Validate 'to' number format (basic example - refine as needed) // This regex checks for a plus sign followed by digits (E.164 format). // E.164 format: 1-15 digits after the + sign, representing country code and number. if (!/^\+\d+$/.test(to)) { reply.code(400); return { error: 'Invalid `to` phone number format. Use E.164 format (e.g., +14155552671).' }; } // Validate message length (WhatsApp limit: 4,096 characters) if (text.length > 4096) { reply.code(400); return { error: 'Message text exceeds WhatsApp limit of 4,096 characters.' }; } const messagePayload = { type: 'text', // Specify message type as text from: INFOBIP_WHATSAPP_SENDER, // Use sender from .env to: to, // Destination number from request body content: { text: text, // Message content from request body }, }; fastify.log.info({ msg: 'Attempting to send WhatsApp message', payload: messagePayload }); try { // Sends via Infobip API endpoint: POST /whatsapp/1/message/text const response = await infobipClient.channels.whatsapp.send(messagePayload); fastify.log.info({ msg: 'WhatsApp message sent successfully', response }); return reply.code(200).send({ message: 'Message sent successfully', infobipResponse: response, // Include Infobip's response }); } catch (error) { fastify.log.error({ err: error, msg: 'Failed to send WhatsApp message' }); // Provide more specific feedback if possible const errorMessage = error.response?.data?.requestError?.serviceException?.text || error.message || 'Unknown error'; const errorCode = error.response?.status || 500; // Use Infobip's status code or default to 500 reply.code(errorCode); // Use appropriate HTTP status code return { error: 'Failed to send message', details: errorMessage }; } }); // --- Start Server Function --- // ... (rest of the code) ...- Why
async (request, reply)? The route handler needs to beasyncbecause sending the message (infobipClient.channels.whatsapp.send) is an asynchronous operation (it returns a Promise). - Why Input Validation? Prevents sending invalid data to Infobip, reducing errors and API calls. We check for presence and a basic E.164 format.
- Why
messagePayloadstructure? This follows the structure required by the Infobip SDK'ssendmethod for text messages, as documented here. - Why
try...catch? Essential for handling potential network errors or API errors returned by Infobip during the send operation. - Why Detailed Error Logging/Response? Logging the error object and returning specific error details (
errorMessage,errorCode) helps diagnose problems quickly. We try to extract the error message from Infobip's response structure if available.
- Why
3. Building the API Layer
The /send route created above forms the basic API layer for sending messages.
-
Authentication/Authorization: For this simple example, there's no built-in user authentication. In a production scenario, you would add authentication middleware (e.g., using JWT with
fastify-jwtor API keys) to protect this endpoint. -
Request Validation: We implemented basic validation for
toandtext. For more complex scenarios, Fastify's schema validation is highly recommended for robustness:javascriptconst sendMessageSchema = { body: { type: 'object', required: ['to', 'text'], properties: { to: { type: 'string', pattern: '^\\+\\d+' }, // E.164 format; Note double backslash for JSON string text: { type: 'string', minLength: 1 } } } } // Add schema to the route options fastify.post('/send', { schema: sendMessageSchema }, async (request, reply) => { // ... handler logic ... // Fastify automatically validates request.body against the schema }); -
API Endpoint Documentation:
- Endpoint:
POST /send - Description: Sends a WhatsApp text message via Infobip.
- Request Body (JSON):
json
{ ""to"": ""+14155552671"", ""text"": ""Hello from Fastify!"" } - Success Response (200 OK - JSON):
json
{ ""message"": ""Message sent successfully"", ""infobipResponse"": { ""messages"": [ { ""to"": ""+14155552671"", ""status"": { ""groupId"": 1, ""groupName"": ""PENDING"", ""id"": 26, ""name"": ""PENDING_ACCEPTED"", ""description"": ""Message sent to next instance"" }, ""messageId"": ""some-unique-message-id"" } ], ""bulkId"": ""some-unique-bulk-id"" } } - Error Response (e.g., 400 Bad Request - JSON):
json
{ ""error"": ""Missing required fields: \""to\"" and \""text\"""" } - Error Response (e.g., 500 Internal Server Error / Infobip Error - JSON):
json
{ ""error"": ""Failed to send message"", ""details"": ""Invalid login details"" }
- Endpoint:
-
Testing with
curl: Replace placeholders with your actual running server address, recipient number (must be your own number if using a free trial Infobip account), and desired message.bashcurl -X POST http://localhost:3000/send \ -H ""Content-Type: application/json"" \ -d '{ ""to"": ""+1xxxxxxxxxx"", ""text"": ""Testing from curl!"" }'
4. Integrating with Infobip (Receiving Messages - Webhooks)
To receive incoming WhatsApp messages, Infobip needs a publicly accessible URL (a webhook) to send the message data to.
-
Create the Webhook Route: Add this route definition to
src/server.js, before thestart()function.javascript// src/server.js // ... (previous code, including /send route) ... // Route to handle incoming WhatsApp messages from Infobip Webhook fastify.post('/infobip-webhook', async (request, reply) => { fastify.log.info({ msg: 'Received request on /infobip-webhook' }); // --- Basic Webhook Security Check --- // Compare a secret header/query param with your defined WEBHOOK_SECRET. // This is basic; STRONGLY prefer signature verification if Infobip offers it. const providedSecret = request.headers['x-webhook-secret'] || request.query.secret; if (providedSecret !== WEBHOOK_SECRET) { fastify.log.warn('Unauthorized webhook attempt: Invalid or missing secret.'); reply.code(401); // Unauthorized return { error: 'Unauthorized' }; } const incomingData = request.body; // Log the entire incoming payload for inspection fastify.log.info({ msg: 'Incoming Infobip Webhook Payload:', payload: incomingData }); // --- Process the Incoming Message --- // Infobip webhook payload structure can vary. Inspect the logs to adapt. // This assumes the standard structure for incoming WhatsApp messages. if (incomingData && incomingData.results && incomingData.results.length > 0) { incomingData.results.forEach(message => { if (message.messageId && message.from && message.to && message.message?.content?.text) { const received = { messageId: message.messageId, from: message.from, to: message.to, // Your WhatsApp sender number text: message.message.content.text, receivedAt: new Date().toISOString(), providerData: message // Store the full original message data if needed }; fastify.log.info({ msg: 'Processed incoming message:', receivedMessage: received }); // Store the message (using our simple in-memory store for now) receivedMessages.push(received); // TODO: Add your business logic here // - Store in a database // - Trigger a response message // - Forward to another system // Example: Echo back the message (Use with caution to avoid loops!) /* if (received.text.toLowerCase() !== 'stop') { // Basic stop condition const echoText = `You said: ${received.text}`; try { await infobipClient.channels.whatsapp.send({ type: 'text', from: INFOBIP_WHATSAPP_SENDER, to: received.from, // Send back to the original sender content: { text: echoText } }); fastify.log.info(`Echoed message back to ${received.from}`); } catch (echoError) { fastify.log.error({ err: echoError }, `Failed to echo message to ${received.from}`); } } */ } else { fastify.log.warn({ msg: 'Received webhook event is not a standard text message or missing fields', eventData: message }); } }); // Respond to Infobip quickly to acknowledge receipt reply.code(200).send({ status: 'Received' }); } else if (incomingData && incomingData.results && incomingData.results.length === 0) { fastify.log.info('Received empty results array from Infobip webhook (e.g., delivery reports).'); reply.code(200).send({ status: 'Received (empty results)' }); } else { fastify.log.warn({ msg: 'Received unexpected webhook payload structure', payload: incomingData }); reply.code(400); // Bad Request - Payload not understood return { error: 'Unexpected payload structure' }; } }); // --- Start Server Function --- // ... (rest of the code) ...- Why Webhook Security? Anyone could potentially send data to your webhook URL. A simple shared secret provides a basic layer of protection. Strongly Recommended: Check Infobip's documentation for signature verification options. If available, implement signature verification as it is significantly more secure than a shared secret.
- Why Log the Full Payload? Infobip's webhook structure might change or contain different event types (delivery reports, seen status, non-text messages). Logging helps you understand the data you're receiving.
- Why Process
incomingData.resultsArray? Infobip often sends webhook events in batches within theresultsarray. - Why
reply.code(200).send()Quickly? Webhook providers expect a fast acknowledgment (typically within a few seconds). Perform complex processing asynchronously (e.g., using message queues or background jobs) if needed, but respond to the webhook request promptly. - Why commented-out Echo Logic? Provides an example of acting on an incoming message but is commented out to prevent accidental loops during initial testing.
-
Configure Webhook in Infobip:
- Get a Public URL: During local development, use a tool like
ngrokto expose your local server to the internet.bash# Install ngrok if you haven't already (https://ngrok.com/download) ngrok http 3000 # Exposes localhost:3000ngrokwill provide a public HTTPS URL (e.g.,https://<unique-id>.ngrok.io). Use this URL. For production, you must use your deployed server's actual public domain/IP address and ensure it's configured for HTTPS. - Add Webhook URL in Infobip Portal:
- Log in to the Infobip Portal.
- Navigate to
Channels and Numbers->WhatsApp. - Select your registered sender number.
- Find the section for configuring webhooks or inbound message URLs (often called
Forward messages to URLor similar). - Enter your public webhook URL:
https://<your-public-url>/infobip-webhook - (Optional but Recommended) If Infobip has a field for a custom header or query parameter for the secret, configure it there (e.g., Header
X-Webhook-Secretwith valuea_very_secret_string_you_should_change). Otherwise, you might need to append it as a query parameter:https://<your-public-url>/infobip-webhook?secret=a_very_secret_string_you_should_change. Again, prioritize signature verification if available over this method. - Save the configuration.
- Get a Public URL: During local development, use a tool like
-
Test Receiving: Send a WhatsApp message from your personal phone to your registered Infobip WhatsApp sender number. Check your Fastify application's console logs. You should see the
Received request on /infobip-webhookmessage, followed by the payload details and theProcessed incoming messagelog.
5. Error Handling, Logging, and Retry Mechanisms
- Error Handling Strategy:
- Use
try...catchblocks for asynchronous operations (like API calls). - Validate inputs early to prevent unnecessary processing or API calls.
- Log errors with sufficient context (error object, relevant request data).
- Return meaningful HTTP status codes and error messages to the client (for the
/sendendpoint) or respond appropriately to the webhook provider (200 OK for successful receipt, 4xx/5xx for errors). - Centralized error handling can be implemented using Fastify's
setErrorHandlerfor more complex applications.
- Use
- Logging:
- Fastify's built-in Pino logger (
logger: true) is excellent and performant. - Log key events: Server start, successful/failed API calls, incoming webhooks, processed messages, critical errors.
- Use structured logging (JSON format) – Pino does this by default – which makes logs easier to parse and analyze with tools like Datadog, Splunk, or ELK stack.
- Adjust log levels (e.g.,
infofor general operations,warnfor potential issues,errorfor failures) based on environment (more verbose in dev, less in prod).
- Fastify's built-in Pino logger (
- Retry Mechanisms:
- Sending messages: If an Infobip API call fails due to transient network issues or temporary Infobip errors (like 5xx status codes), implement a retry strategy. Libraries like
async-retrycan help. Use exponential backoff (wait longer between each retry) to avoid overwhelming the API. - Receiving webhooks: Retries are typically handled by the sender (Infobip). Ensure your webhook endpoint is reliable and responds quickly. If processing fails after acknowledging the webhook (sending 200 OK), you'll need internal retry mechanisms (e.g., using a message queue like RabbitMQ or Kafka) to re-process the job later. For this simple guide, we are not implementing explicit retries.
- Sending messages: If an Infobip API call fails due to transient network issues or temporary Infobip errors (like 5xx status codes), implement a retry strategy. Libraries like
6. Database Schema and Data Layer (Conceptual)
While this guide uses an in-memory array (receivedMessages), a production application requires a persistent database.
-
Why? To store message history, track conversation states, manage user data, and prevent data loss on server restarts.
-
Choice: PostgreSQL, MongoDB, MySQL, Redis (depending on needs).
-
Example Schema (Conceptual - PostgreSQL):
sqlCREATE TABLE whatsapp_messages ( id SERIAL PRIMARY KEY, infobip_message_id VARCHAR(255) UNIQUE, -- Infobip's ID for deduplication direction VARCHAR(10) NOT NULL, -- 'inbound' or 'outbound' sender_number VARCHAR(20) NOT NULL, recipient_number VARCHAR(20) NOT NULL, message_body TEXT, status VARCHAR(50), -- e.g., PENDING, SENT, DELIVERED, FAILED, RECEIVED infobip_status_details JSONB, -- Store full status object from Infobip created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); -- Index for querying by numbers or status CREATE INDEX idx_messages_sender ON whatsapp_messages(sender_number); CREATE INDEX idx_messages_recipient ON whatsapp_messages(recipient_number); CREATE INDEX idx_messages_status ON whatsapp_messages(status); -
Data Layer: Use an ORM (like Prisma, Sequelize, TypeORM) or a query builder (like Knex.js) to interact with the database, handle migrations, and abstract SQL queries. Replace the
receivedMessages.push()logic with database insertion calls within the webhook handler. Similarly, log sent messages to the database after a successful API call in the/sendroute.
7. Security Features
- Input Validation: Already implemented basic checks. Use Fastify's schema validation for robust protection against malformed requests. Sanitize any data received from users (like message text) before storing or displaying it elsewhere to prevent XSS attacks (though less critical in a backend-only context unless reflecting data).
- Webhook Security: Implemented basic secret check. Prioritize Infobip's signature verification if available. Ensure your webhook endpoint uses HTTPS.
- API Key Security: Use environment variables (
dotenv) and.gitignoreto keep keys out of source control. Use tools like HashiCorp Vault or cloud provider secret managers for production environments. - Rate Limiting: Protect your API endpoints (especially
/send) from abuse. Usefastify-rate-limit:bashnpm install fastify-rate-limitjavascript// src/server.js // ... imports ... const rateLimit = require('fastify-rate-limit'); // ... fastify initialization ... // Register rate limiting plugin fastify.register(rateLimit, { max: 100, // Max requests per window timeWindow: '1 minute' // Time window }); // ... routes ... - Common Vulnerabilities: Be mindful of Node.js security best practices (e.g., keeping dependencies updated with
npm audit, avoiding insecure code patterns). Use security linters. - HTTPS: Always use HTTPS for production deployment to encrypt data in transit. Use a reverse proxy like Nginx or Caddy, or deploy to platforms that handle TLS termination.
8. Handling Special Cases
- Message Types: This guide focuses on text messages. Infobip supports images, documents, templates, buttons, etc. Adapt the
messagePayloadin the/sendroute and the parsing logic in the/infobip-webhookaccording to the Infobip WhatsApp API documentation. - Delivery Reports: Infobip can send webhook events for message status updates (e.g., delivered, seen). Your webhook handler needs to identify these different event types (based on the payload structure) and update message status in your database accordingly.
- Rate Limits (Infobip): Be aware of Infobip's sending limits. Implement throttling or queuing if sending bulk messages. Handle
429 Too Many Requestserrors gracefully. - Free Trial Limitations: Remember, free trial accounts can typically only send messages to the phone number used during registration. Sending from your personal number to the Infobip sender should work for testing webhooks.
- Character Limits/Encoding: WhatsApp messages have limits. Be mindful of message length. Ensure correct text encoding (usually UTF-8).
- Opt-ins/Opt-outs: Respect WhatsApp policies regarding user consent. Implement logic to handle STOP/START commands received via webhook.
9. Performance Optimizations
- Fastify: Already a high-performance framework.
- Infobip Client: The SDK client is initialized once and reused, which is efficient.
- Asynchronous Operations: Node.js and Fastify are inherently asynchronous. Ensure you are not blocking the event loop with synchronous code, especially in route handlers.
- Database Queries: Optimize database interactions (indexing, efficient queries) if using a database.
- Payload Size: Keep request/response payloads concise where possible.
- Caching: Not directly applicable for simple message sending/receiving, but could be used for frequently accessed configuration or user data if integrated.
- Load Testing: For high-throughput applications, use tools like
k6,autocannon, orwrkto simulate load and identify bottlenecks in your/sendendpoint or webhook processing.
Frequently Asked Questions (FAQ)
Can I send WhatsApp messages to any phone number?
No. WhatsApp enforces a 24-hour messaging window policy. You can only send text messages to users who have contacted your business within the last 24 hours. Outside this window, you must use pre-approved WhatsApp template messages. Free trial Infobip accounts typically restrict sending to only your registered phone number.
What are the rate limits for the Infobip WhatsApp API?
The Infobip WhatsApp API has a rate limit of 4,000 requests per second per account. If you exceed this limit, you'll receive a 429 Too Many Requests error. Implement throttling, queuing, or exponential backoff retry logic for high-volume applications.
How do I test webhook integration during local development?
Use ngrok or similar tunneling tools to expose your local development server to the internet. Run ngrok http 3000 to get a public HTTPS URL, then configure this URL in your Infobip Portal webhook settings. Remember to append your webhook path (e.g., https://your-ngrok-url.ngrok.io/infobip-webhook).
What Node.js version should I use for production?
Use an Active LTS or Maintenance LTS version. As of October 2025, Node.js v22 (Active LTS) is recommended for new projects, while v20 (Maintenance LTS) is also supported. The Infobip SDK requires Node.js v14 minimum, but avoid using EOL (End-of-Life) versions in production.
How do I handle WhatsApp message delivery failures?
Implement error handling with try-catch blocks around API calls. Use Infobip's delivery reports (webhook events) to track message status. For transient failures (network issues, 5xx errors), implement retry logic with exponential backoff using libraries like async-retry. Log all errors with sufficient context for debugging.
What's the character limit for WhatsApp text messages?
WhatsApp text messages have a maximum length of 4,096 characters. Messages exceeding this limit will be rejected by the API. Implement validation in your application to check message length before sending.
10. Monitoring, Observability, and Analytics
- Logging: Already set up with Fastify/Pino. Ship logs to a centralized logging platform for analysis.
- Health Checks: Add a simple health check endpoint:
Monitoring tools can ping this endpoint to verify service availability.javascript
// src/server.js // ... routes ... // Health check endpoint fastify.get('/health', async (request, reply) => { // Optional: Add checks for database connectivity or Infobip API status return { status: 'ok', timestamp: new Date().toISOString() }; }); // ... start function ... - Metrics: Use libraries like
fastify-metricsto expose application metrics (request counts, latency, error rates) in Prometheus format. Visualize these metrics using Grafana or similar tools. - Error Tracking: Integrate services like Sentry or Bugsnag using their Node.js SDKs to capture, aggregate, and alert on runtime errors.
Frequently Asked Questions
How to send WhatsApp messages with Fastify?
Use the '/send' POST endpoint with a JSON body containing the recipient's number ('to') in E.164 format (e.g., +14155552671) and the message text ('text'). This endpoint leverages the Infobip API to deliver the message, simplifying WhatsApp integration within your Fastify app.
What is the Infobip API used for in this project?
Infobip is a CPaaS (Communication Platform as a Service) that provides the necessary infrastructure and API for sending and receiving WhatsApp messages programmatically. The project uses Infobip's Node.js SDK to interact with their API, abstracting away the complexities of the WhatsApp Business API.
Why use Fastify for a WhatsApp messaging app?
Fastify is a high-performance Node.js web framework known for its speed and developer-friendly features like built-in logging and validation. Its efficiency makes it well-suited for handling real-time messaging traffic and building scalable WhatsApp applications.
When should I use ngrok with Infobip?
Ngrok is useful during local development to create a publicly accessible URL for receiving Infobip webhooks. In production, you'll need a properly deployed server with a public domain or IP and HTTPS.
Can I send different WhatsApp message types with Infobip?
Yes, besides text messages, Infobip supports various message types like images, documents, templates, and interactive buttons. Refer to the Infobip WhatsApp API documentation for details on structuring the payload for each type and adapting the webhook handler to process them.
How to receive WhatsApp messages with Infobip and Fastify?
Set up a '/infobip-webhook' POST route in your Fastify app. Configure this URL as a webhook in your Infobip account settings, and Infobip will forward incoming WhatsApp messages to this endpoint. Add security measures like secret verification to protect your webhook.
What are the prerequisites for this WhatsApp project?
You'll need Node.js v14 or later, an active Infobip account (a free trial is sufficient for testing), your Infobip API Key and Base URL, a registered WhatsApp sender in your Infobip account, and a public URL for receiving webhooks.
How to secure my Infobip API key?
Store your Infobip API key as an environment variable in a '.env' file, and add this file to your '.gitignore' to prevent it from being committed to version control. For production, consider using more secure secret management solutions.
What database schema is recommended for storing messages?
A suggested schema includes fields for message ID, direction, sender/recipient numbers, message body, status, timestamps, and Infobip-specific status details (stored as JSON). Use indexing for optimal querying by numbers or status.
Why validate the 'to' phone number format?
Validating the 'to' field ensures that only correctly formatted numbers (E.164 format) are sent to the Infobip API, minimizing errors and unnecessary API calls.
How to handle errors when sending WhatsApp messages?
Implement try...catch blocks around API calls to handle network errors or issues returned by Infobip. Log errors with relevant context and return informative error messages to the client with appropriate HTTP status codes.
What is the purpose of the webhook secret?
The webhook secret is used to verify that incoming requests to your '/infobip-webhook' endpoint are actually coming from Infobip. This adds a basic security layer to prevent unauthorized access. While a secret is helpful, prioritize signature verification if Infobip offers it.
How to implement rate limiting for my Fastify WhatsApp app?
Use the 'fastify-rate-limit' plugin to control the rate of incoming requests, protecting your application from abuse. Configure the maximum number of requests allowed within a specified time window. This is especially important for the /send endpoint.
What are some performance considerations for this WhatsApp integration?
Leverage Fastify's inherent performance, ensure asynchronous operations are non-blocking, optimize database interactions, and keep request/response payloads concise. For high-throughput, conduct load testing to identify potential bottlenecks.
How can I monitor the health of my Fastify WhatsApp application?
Implement a '/health' endpoint that returns the application's status. Integrate logging and metrics collection using tools like Pino and Prometheus, and set up error tracking with services like Sentry or Bugsnag.