code examples

Sent logo
Sent TeamMay 3, 2025 / code examples / Article

Build a Node.js Express App for WhatsApp Messaging with Infobip

A step-by-step guide to creating a Node.js and Express application for sending and receiving WhatsApp messages using the Infobip API, covering setup, webhooks, error handling, and security.

Build a Functional Node.js Express App for WhatsApp Messaging with Infobip

Integrate the power of WhatsApp messaging directly into your Node.js applications using Express and the Infobip API. This guide provides a step-by-step walkthrough to build a functional system capable of sending text messages and handling incoming messages via webhooks. While this guide covers essential foundations, further hardening in areas like comprehensive monitoring, advanced error handling/retries, and fully implemented security measures (like webhook signature verification) is recommended for robust production deployments.

We'll cover everything from initial project setup and Infobip configuration to implementing core messaging logic, handling errors, securing your application, and preparing for deployment. By the end, you'll have a functional Express application ready to communicate over WhatsApp.

Project Overview and Goals

What We're Building:

A Node.js application using the Express framework that can:

  1. Send outbound WhatsApp text messages: Expose an API endpoint to trigger messages to specified phone numbers via Infobip.
  2. Receive inbound WhatsApp messages: Set up a webhook to receive notifications from Infobip when users send messages to your designated WhatsApp number.

Problem Solved: This application enables businesses or developers to programmatically interact with users on WhatsApp, facilitating automated notifications, customer support interactions, or conversational applications directly within their Node.js backend.

Technologies Used:

  • Node.js: The JavaScript runtime environment.
  • Express.js: A minimal and flexible Node.js web application framework used for building the API and handling webhooks.
  • Infobip WhatsApp API: The third-party service used to connect to the WhatsApp Business Platform and send/receive messages.
  • @infobip-api/sdk: The official Node.js SDK for interacting with the Infobip API, simplifying requests and authentication.
  • dotenv: To manage environment variables securely.
  • nodemon (Development): To automatically restart the server during development.
  • ngrok (Development): To expose the local development server to the internet for webhook testing.

System Architecture:

+-------------+ +---------------------+ +-----------------+ +-----------+ | User | <---> | WhatsApp App | <---> | Infobip Platform| <---> | WhatsApp | | (Via API) | | (Node.js/Express) | | (API Gateway) | | Network | +-------------+ +---------------------+ +-----------------+ +-----------+ | ^ | ^ | | Send Request | | Send API Call | | Message Delivery v | v | v +---------------------+ | +-----------------------+ | +-----------------+ | API Endpoint | ----> | Infobip SDK | ---> | Send Message API| | (/send-message) | | (Send Request) | +-----------------+ +---------------------+ +-----------------------+ | ^ | | Incoming Message | | Webhook Notification v | v +---------------------+ +-----------------------+ <------ +-----------------+ | Webhook Endpoint | <---- | Handle Incoming | | Inbound Message | | (/infobip-webhook) | | Message Logic | | Webhook Config | +---------------------+ +-----------------------+ +-----------------+

(Note: This text-based diagram illustrates the flow. A visual diagram might be clearer depending on the viewing platform.)

Prerequisites:

  • Node.js and npm (or yarn) installed.
  • An active Infobip account (a free trial account is sufficient to start).
  • A registered and approved WhatsApp Sender configured within your Infobip account. (Follow Infobip's documentation for this setup; be aware that the approval process via Meta can sometimes be complex and take time).
  • Basic understanding of Node.js, Express, and REST APIs.
  • A tool like Postman or curl for testing API endpoints.
  • ngrok installed for local webhook testing.

1. Setting up the Project

Let's initialize our Node.js project and install the necessary dependencies.

  1. Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.

    bash
    mkdir node-infobip-whatsapp
    cd node-infobip-whatsapp
  2. Initialize npm: Initialize the project using npm. The -y flag accepts default settings.

    bash
    npm init -y
  3. Install Dependencies: Install Express for the web server, the Infobip SDK for API interaction, and dotenv for environment variable management.

    bash
    npm install express @infobip-api/sdk dotenv
  4. Install Development Dependencies: Install nodemon as a development dependency to automatically restart the server when files change.

    bash
    npm install --save-dev nodemon
  5. Create Project Structure: Set up a basic directory structure for organization.

    bash
    mkdir src
    mkdir src/routes
    mkdir src/controllers
    mkdir src/services
    touch src/server.js src/app.js .env .gitignore
    touch src/routes/messageRoutes.js
    touch src/controllers/messageController.js
    touch src/services/infobipClient.js
    • src/server.js: Entry point, starts the HTTP server.
    • src/app.js: Configures the Express application (middleware, routes).
    • src/routes/: Contains route definitions.
    • src/controllers/: Contains logic to handle requests.
    • src/services/: Contains modules for interacting with external services (like Infobip).
    • .env: Stores environment variables (API keys, etc.). Never commit this file.
    • .gitignore: Specifies files/directories ignored by Git.
  6. Configure .gitignore: Add node_modules and .env to your .gitignore file to prevent them from being committed to version control.

    text
    # .gitignore
    
    node_modules/
    .env
    npm-debug.log
  7. Configure .env: Add placeholders for your Infobip credentials and sender information. We'll obtain these values in the next step.

    dotenv
    # .env
    
    PORT=3000
    INFOBIP_API_KEY=YOUR_INFOBIP_API_KEY
    INFOBIP_BASE_URL=YOUR_INFOBIP_BASE_URL
    INFOBIP_WHATSAPP_SENDER=YOUR_INFOBIP_WHATSAPP_SENDER_NUMBER
  8. Add Run Scripts to package.json: Add scripts to your package.json for starting the server in development (with nodemon) and production modes.

    json
    {
      ""name"": ""node-infobip-whatsapp"",
      ""version"": ""1.0.0"",
      ""description"": """",
      ""main"": ""src/server.js"",
      ""type"": ""module"",
      ""scripts"": {
        ""start"": ""node src/server.js"",
        ""dev"": ""nodemon src/server.js"",
        ""test"": ""echo \""Error: no test specified\"" && exit 1""
      },
      ""keywords"": [],
      ""author"": """",
      ""license"": ""ISC"",
      ""dependencies"": {
        ""@infobip-api/sdk"": ""^3.0.0"",
        ""dotenv"": ""^16.0.3"",
        ""express"": ""^4.18.2""
      },
      ""devDependencies"": {
        ""nodemon"": ""^2.0.20""
      }
    }

    (Note: Added other common package.json fields for completeness)

2. Infobip Configuration

Before writing code, configure your Infobip account to get the necessary API credentials and set up your WhatsApp sender.

  1. Log in to Infobip Portal: Access your Infobip account at https://portal.infobip.com/.
  2. Find API Key:
    • Navigate to the ""API Keys Management"" section (often found under your account settings or a dedicated ""Developers"" section).
    • Create a new API key if you don't have one. Give it a descriptive name (e.g., ""Node WhatsApp App"").
    • Important: Copy the generated API key immediately and securely store it. You won't be able to see it again after closing the creation dialog.
    • Add this key to your .env file as INFOBIP_API_KEY.
  3. Find Base URL:
    • Your Base URL is specific to your account. It's often displayed prominently on the API Keys page or the main developer dashboard/homepage after login. It usually looks like xxxxx.api.infobip.com.
    • Add this URL (without https://) to your .env file as INFOBIP_BASE_URL.
  4. Configure WhatsApp Sender:
    • Navigate to the ""Channels and Numbers"" section (or similar) in the Infobip portal.
    • Select ""WhatsApp"".
    • You need to have a registered WhatsApp Sender number approved by Meta and configured through Infobip. Follow Infobip's documentation or support guides for the detailed process of registering a WhatsApp sender.
    • Once you have an active sender, note down the phone number associated with it (including the country code, e.g., 447860099299).
    • Add this sender number to your .env file as INFOBIP_WHATSAPP_SENDER.

Your .env file should now look something like this (with your actual values):

dotenv
# .env (Example with filled values)

PORT=3000
INFOBIP_API_KEY=abc123def456ghi789jkl0mno1pqr2stu3vwx4yz
INFOBIP_BASE_URL=youruniqueid.api.infobip.com
INFOBIP_WHATSAPP_SENDER=14155552671 # Your registered WhatsApp sender number

3. Initializing the Infobip Client

Create a reusable service module to initialize and export the Infobip SDK client instance.

javascript
// src/services/infobipClient.js

import { Infobip, AuthType } from ""@infobip-api/sdk"";
import dotenv from 'dotenv';

// Load environment variables from .env file
dotenv.config();

const apiKey = process.env.INFOBIP_API_KEY;
const baseUrl = process.env.INFOBIP_BASE_URL;

if (!apiKey || !baseUrl) {
  console.error(""Error: Infobip API Key or Base URL not found in environment variables."");
  console.error(""Please ensure INFOBIP_API_KEY and INFOBIP_BASE_URL are set in your .env file."");
  process.exit(1); // Exit if essential config is missing
}

// Create an instance of the Infobip client
// We use ApiKey for authentication as recommended for server-side applications.
const infobipClient = new Infobip({
  baseUrl: `https://${baseUrl}`, // Ensure https is prepended
  apiKey: apiKey,
  authType: AuthType.ApiKey,
});

export default infobipClient;

Why this approach?

  • Centralization: Keeps Infobip configuration and client instantiation in one place.
  • Reusability: Easily import and use the infobipClient instance wherever needed without re-initializing.
  • Security: Loads sensitive keys from environment variables, not hardcoded in the source.
  • Configuration Check: Includes a basic check to ensure essential variables are loaded, preventing runtime errors later.

4. Implementing Core Functionality: Sending Messages

Let's build the API endpoint and controller logic to send a simple WhatsApp text message.

  1. Create the Controller Logic: Define the function that takes request data and uses the Infobip client to send the message.

    javascript
    // src/controllers/messageController.js
    
    import infobipClient from '../services/infobipClient.js';
    import dotenv from 'dotenv';
    
    dotenv.config();
    
    const senderNumber = process.env.INFOBIP_WHATSAPP_SENDER;
    
    if (!senderNumber) {
      console.error(""Error: Infobip WhatsApp Sender number not found in environment variables."");
      console.error(""Please ensure INFOBIP_WHATSAPP_SENDER is set in your .env file."");
      process.exit(1);
    }
    
    export const sendWhatsAppMessage = async (req, res) => {
      // Basic input validation
      const { to, message } = req.body;
      if (!to || !message) {
        return res.status(400).json({ error: 'Missing required fields: ""to"" and ""message"".' });
      }
    
      // Basic phone number format validation (allows 10-15 digits).
      // Infobip generally expects E.164 format (e.g., 447123456789) without leading '+'.
      // This regex is basic and doesn't strictly enforce E.164 (e.g., allows leading zeros).
      // Check Infobip documentation for specific formatting requirements if issues arise.
      if (!/^\d{10,15}$/.test(to)) {
         return res.status(400).json({ error: 'Invalid ""to"" phone number format. Expected E.164 format without leading + (e.g., 447123456789).' });
      }
    
      const payload = {
        type: 'text', // Sending a text message
        from: senderNumber,
        to: to,       // Recipient's phone number from the request body
        content: {
          text: message, // Message content from the request body
        },
        // Add messageId, callbackData, notifyUrl etc. if needed for tracking
      };
    
      console.log(`Attempting to send message from ${senderNumber} to ${to}: ""${message}""`);
    
      try {
        const response = await infobipClient.channels.whatsapp.send(payload);
    
        console.log('Infobip API Response:', JSON.stringify(response.data, null, 2));
    
        // Check for successful sending (structure might vary slightly)
        // Infobip usually returns a 2xx status on acceptance, not final delivery
        if (response.status === 200 || response.status === 202) {
           // The `messages` array usually contains details about the accepted message
           const sentMessageInfo = response.data.messages && response.data.messages[0];
           if (sentMessageInfo) {
             console.log(`Message accepted by Infobip. Message ID: ${sentMessageInfo.messageId}, Status: ${sentMessageInfo.status?.groupName || 'PENDING'}`);
             res.status(200).json({
               message: 'WhatsApp message accepted for delivery.',
               messageId: sentMessageInfo.messageId,
               status: sentMessageInfo.status,
               details: response.data
             });
           } else {
             // Handle cases where the structure might differ or lack details
             console.warn('Message accepted by Infobip, but response structure might be unexpected:', response.data);
             res.status(200).json({
               message: 'WhatsApp message accepted for delivery (structure variance).',
               details: response.data
             });
           }
    
        } else {
          // Handle unexpected success status codes if necessary
          console.warn(`Infobip returned an unexpected success status: ${response.status}`);
          res.status(response.status).json({
            error: 'Received unexpected status from Infobip.',
            details: response.data,
          });
        }
    
      } catch (error) {
        console.error('Error sending WhatsApp message via Infobip:');
    
        // Axios errors (used by the SDK) often have response data
        if (error.response) {
          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));
          // Extract specific error message if available
          const errorMessage = error.response.data?.requestError?.serviceException?.text ||
                               error.response.data?.error?.message ||
                               'Failed to send message.';
          res.status(error.response.status).json({
            error: 'Failed to send WhatsApp message.',
            details: errorMessage,
            infobipError: error.response.data // Include full error details
          });
        } else if (error.request) {
          // The request was made but no response was received
          console.error('Request Error:', error.request);
          res.status(504).json({ error: 'No response received from Infobip API.' });
        } else {
          // Something happened in setting up the request that triggered an Error
          console.error('Error:', error.message);
          res.status(500).json({ error: 'Internal server error preparing the request.' });
        }
      }
    };
    
    // Add handleInfobipWebhook function later
  2. Define the Route: Map the controller function to a specific API endpoint.

    javascript
    // src/routes/messageRoutes.js
    
    import express from 'express';
    import { sendWhatsAppMessage } from '../controllers/messageController.js';
    // Import webhook controller later
    // import { handleInfobipWebhook } from '../controllers/messageController.js';
    
    const router = express.Router();
    
    // POST endpoint to send a message
    router.post('/send-message', sendWhatsAppMessage);
    
    // POST endpoint for Infobip to send incoming messages (we'll implement handleInfobipWebhook later)
    // router.post('/infobip-webhook', handleInfobipWebhook);
    
    export default router;
  3. Configure Express App: Set up the main Express application file to use JSON parsing middleware and mount the message routes.

    javascript
    // src/app.js
    
    import express from 'express';
    import dotenv from 'dotenv';
    import messageRoutes from './routes/messageRoutes.js'; // Import the routes
    
    dotenv.config();
    
    const app = express();
    
    // Middleware to parse JSON request bodies
    app.use(express.json());
    
    // Simple root route for health check or info
    app.get('/', (req, res) => {
      res.send('WhatsApp API Service is running.');
    });
    
    // Mount the message routes under the /api prefix
    app.use('/api', messageRoutes);
    
    // Basic 404 handler for unhandled routes
    app.use((req, res, next) => {
        res.status(404).send(""Sorry, can't find that!"");
    });
    
    // Basic error handler (add more specific handlers later)
    app.use((err, req, res, next) => {
        console.error(err.stack);
        res.status(500).send('Something broke!');
    });
    
    
    export default app;
  4. Create Server Entry Point: Create the main server file that loads the app configuration and starts listening.

    javascript
    // src/server.js
    
    import app from './app.js'; // Import the configured Express app
    import dotenv from 'dotenv';
    
    dotenv.config();
    
    const PORT = process.env.PORT || 3000; // Use port from .env or default to 3000
    
    app.listen(PORT, () => {
      console.log(`Server is running on http://localhost:${PORT}`);
    });
  5. Test Sending:

    • Start the development server: npm run dev
    • Use curl or Postman to send a POST request to your endpoint. Replace YOUR_RECIPIENT_NUMBER with a valid WhatsApp number (in E.164 format, e.g., 14155552672) that you can check for messages.
    bash
    curl -X POST http://localhost:3000/api/send-message \
    -H ""Content-Type: application/json"" \
    -d '{
      ""to"": ""YOUR_RECIPIENT_NUMBER"",
      ""message"": ""Hello from Node.js and Infobip!""
    }'
    • Check your terminal logs for output from the server and the Infobip API response.
    • Verify that the message arrives on the recipient's WhatsApp account. (Note: Initial messages to a number might require using a pre-approved template, depending on WhatsApp rules and if the user has messaged your number within the last 24 hours. Text messages work reliably if the 24-hour window is open).

5. Implementing Core Functionality: Receiving Messages (Webhook)

To receive incoming WhatsApp messages, Infobip needs a publicly accessible URL (a webhook) to send HTTP POST requests to whenever a user messages your WhatsApp Sender number.

  1. Create Webhook Controller Logic: Add a function to handle the incoming data from Infobip. For now, we'll just log the received payload.

    javascript
    // src/controllers/messageController.js
    // ... (keep existing sendWhatsAppMessage function) ...
    
    export const handleInfobipWebhook = (req, res) => {
      console.log(""Received webhook payload from Infobip:"");
      console.log(JSON.stringify(req.body, null, 2)); // Log the entire payload
    
      // Infobip expects a 200 OK response quickly to acknowledge receipt.
      // Process the payload asynchronously if needed, but respond immediately.
      res.sendStatus(200);
    
      // --- Placeholder for processing logic ---
      // You would typically parse req.body here to extract:
      // - Sender's number (results[0].from)
      // - Message content (results[0].message.text, .image, etc.)
      // - Message ID (results[0].messageId)
      // - Timestamp (results[0].receivedAt)
      // And then trigger appropriate actions (e.g., save to DB, reply, etc.)
      // Example parsing (adapt based on actual payload structure):
      /*
      const results = req.body.results;
      if (results && results.length > 0) {
          const messageData = results[0];
          const from = messageData.from;
          const messageContent = messageData.message; // This object contains type and content details
    
          console.log(`Message received from: ${from}`);
          if (messageContent.type === 'TEXT') {
              console.log(`Text content: ${messageContent.text}`);
          } else {
              console.log(`Received message of type: ${messageContent.type}`);
          }
          // Add further processing logic here
      }
      */
      // --- End Placeholder ---
    };
  2. Define the Webhook Route: Uncomment or add the route definition for the webhook endpoint in your routes file.

    javascript
    // src/routes/messageRoutes.js
    import express from 'express';
    // Import both controllers
    import { sendWhatsAppMessage, handleInfobipWebhook } from '../controllers/messageController.js';
    
    const router = express.Router();
    
    router.post('/send-message', sendWhatsAppMessage);
    router.post('/infobip-webhook', handleInfobipWebhook); // Add the webhook route
    
    export default router;
  3. Expose Local Server with ngrok: Since Infobip needs a public URL, we use ngrok during development to tunnel requests to our local machine.

    • Ensure your Node.js server is running (npm run dev). It should be listening on the port specified in .env (e.g., 3000).

    • Open a new terminal window.

    • Run ngrok to create a tunnel to your local port:

      bash
      ngrok http 3000
    • ngrok will display output including a public ""Forwarding"" URL (e.g., https://randomstring.ngrok.io). Copy the https version of this URL. This is your temporary public webhook URL.

  4. Configure Webhook in Infobip Portal:

    • Go back to the Infobip Portal.
    • Navigate to ""Channels and Numbers"" > ""WhatsApp"" > Your Sender Number.
    • Find the ""Webhook"" or ""API Integration"" settings for incoming messages.
    • Paste the https ngrok Forwarding URL you copied, appending your webhook route path. The full URL will look like: https://randomstring.ngrok.io/api/infobip-webhook
    • Save the webhook configuration in the Infobip portal.
  5. Test Receiving:

    • Ensure both your Node.js server (npm run dev) and ngrok are running.
    • Using a regular WhatsApp account on your phone, send a message to your Infobip WhatsApp Sender number.
    • Watch the terminal where your Node.js server is running (npm run dev). You should see the ""Received webhook payload from Infobip:"" log message, followed by the JSON payload of the incoming message.
    • Check the ngrok terminal window as well – it shows incoming requests.

6. Error Handling and Logging

Production applications require more robust error handling and structured logging.

  • Error Handling Strategy:

    • Use try...catch blocks for operations that can fail (API calls, database interactions).
    • Standardize error responses from your API (as shown in sendWhatsAppMessage with status and error/details fields).
    • Create specific error classes for different error types if needed (e.g., ValidationError, InfobipApiError).
    • Implement a global error handling middleware in app.js to catch unhandled errors and provide a generic server error response, logging the details internally.
  • Logging:

    • Replace console.log/console.error with a dedicated logging library like Winston or Pino.
    • Configure log levels (e.g., info, warn, error, debug). Use info for standard operations, warn for potential issues, error for failures, and debug for detailed development tracing.
    • Log in a structured format (like JSON) for easier parsing by log analysis tools (e.g., Datadog, Splunk, ELK stack).
    • Include contextual information in logs (request IDs, user IDs if applicable, function names).

    Example (Conceptual using Winston):

    javascript
    // src/services/logger.js (Example)
    import winston from 'winston';
    
    const logger = winston.createLogger({
      level: process.env.LOG_LEVEL || 'info',
      format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.json() // Log in JSON format
      ),
      transports: [
        // Log to console
        new winston.transports.Console({
          format: winston.format.combine(
            winston.format.colorize(), // Add colors for readability in console
            winston.format.simple() // Use simple format for console
          )
        }),
        // Log errors to a separate file
        new winston.transports.File({ filename: 'error.log', level: 'error' }),
        // Log all levels to another file
        new winston.transports.File({ filename: 'combined.log' })
      ],
    });
    
    export default logger;
    
    // Usage in controller:
    // import logger from '../services/logger.js';
    // logger.info('Message accepted by Infobip.', { messageId: '...', status: '...' });
    // logger.error('Error sending message', { error: error.message, stack: error.stack });
  • Retry Mechanisms:

    • For transient network errors or rate limiting when calling Infobip, implement a retry strategy with exponential backoff. Libraries like async-retry can simplify this.
    • Be cautious retrying non-idempotent requests unless you can uniquely identify them (e.g., using a messageId you generate and pass to Infobip if supported) to avoid sending duplicate messages.

7. Security Considerations

Securing your application and API is critical.

  • API Key Security:

    • NEVER hardcode API keys or sensitive credentials in your source code.
    • Use environment variables (.env locally, secure configuration management in deployment).
    • Ensure .env is in your .gitignore.
    • Rotate API keys periodically.
    • Use separate keys for different environments (dev, staging, prod).
  • Webhook Security:

    • Signature Verification (Highly Recommended): Infobip can sign incoming webhook requests using a secret key. You must verify this signature in your handleInfobipWebhook controller to ensure the request genuinely came from Infobip and wasn't tampered with. Consult the Infobip documentation for details on enabling and verifying webhook signatures (Basic Authentication or potentially HMAC signatures if offered for WhatsApp webhooks). Implementing signature verification involves parsing headers, generating a hash using a shared secret, and comparing it to the provided signature. While the specific code depends on Infobip's exact method (e.g., HMAC-SHA1, Basic Auth header structure), it is a crucial step not detailed here but necessary for production security.
    • HTTPS: Always use HTTPS for your webhook endpoint (ngrok provides this; ensure your deployed environment does too).
  • Input Validation and Sanitization:

    • Rigorously validate all incoming data (request bodies, query parameters). Use libraries like joi or express-validator for schema validation.
    • Sanitize data before using it in database queries or external API calls to prevent injection attacks (though using parameterized queries or SDKs often handles this). The phone number validation in sendWhatsAppMessage is a basic example.
  • Rate Limiting:

    • Protect your /api/send-message endpoint (and potentially the webhook if it triggers expensive operations) from abuse by implementing rate limiting. Use middleware like express-rate-limit.
  • Authentication/Authorization (for /send-message):

    • If your /send-message endpoint is intended for internal use or specific authenticated users, implement proper authentication (e.g., API keys for other services, JWT for users) and authorization checks before allowing messages to be sent.
  • Dependencies:

    • Keep dependencies updated (npm update) and use tools like npm audit or GitHub Dependabot to identify and fix known vulnerabilities in your dependencies.

8. Testing

Thorough testing ensures your integration works correctly and reliably.

  • Manual Testing:

    • Use curl or Postman to hit your /api/send-message endpoint with various valid and invalid inputs (correct numbers, incorrect numbers, missing fields, long messages). Verify responses and message delivery.
    • Send messages from WhatsApp to your Infobip number to test the /api/infobip-webhook endpoint. Check server logs and ngrok inspector. Test different message types if you plan to handle them (images, locations - though this guide focuses on text).
  • Unit Testing:

    • Use frameworks like Jest or Mocha/Chai to test individual functions/modules in isolation.
    • Mocking: Mock the @infobip-api/sdk to simulate successful API calls, error responses (4xx, 5xx), and network errors without actually hitting the Infobip API during tests. Jest provides powerful mocking capabilities.

    Example (Conceptual Unit Test with Jest):

    javascript
    // src/controllers/messageController.test.js (Conceptual)
    import { sendWhatsAppMessage } from './messageController';
    import infobipClient from '../services/infobipClient';
    
    // Mock the Infobip SDK
    jest.mock('../services/infobipClient.js', () => ({
      channels: {
        whatsapp: {
          send: jest.fn(), // Create a mock function for 'send'
        },
      },
    }));
    
    // Mock dotenv config if needed, or ensure env vars are set for tests
    // jest.mock('dotenv', () => ({ config: jest.fn() }));
    
    describe('Message Controller - sendWhatsAppMessage', () => {
      let mockRequest;
      let mockResponse;
      let nextFunction = jest.fn();
      const originalEnv = process.env; // Store original environment variables
    
      beforeEach(() => {
        // Reset mocks and environment before each test
        jest.resetModules() // Important if modules cache process.env
        process.env = { ...originalEnv }; // Restore original env
        infobipClient.channels.whatsapp.send.mockClear();
        nextFunction.mockClear();
    
        // Setup mock request/response objects
        mockRequest = {
          body: {},
        };
        mockResponse = {
          status: jest.fn().mockReturnThis(), // Chainable status
          json: jest.fn(),
          sendStatus: jest.fn(),
        };
    
        // Ensure required env var is set for tests that need it
        process.env.INFOBIP_WHATSAPP_SENDER = '15551234567';
      });
    
      afterAll(() => {
         process.env = originalEnv; // Restore original env after all tests
      });
    
      test('should return 400 if ""to"" or ""message"" is missing', async () => {
        mockRequest.body = { to: '12345' }; // Missing message
        await sendWhatsAppMessage(mockRequest, mockResponse, nextFunction);
        expect(mockResponse.status).toHaveBeenCalledWith(400);
        expect(mockResponse.json).toHaveBeenCalledWith(
          expect.objectContaining({ error: expect.stringContaining('Missing required fields') })
        );
      });
    
      test('should return 400 if ""to"" phone number format is invalid', async () => {
        mockRequest.body = { to: 'invalid-number', message: 'Test' };
        await sendWhatsAppMessage(mockRequest, mockResponse, nextFunction);
        expect(mockResponse.status).toHaveBeenCalledWith(400);
        expect(mockResponse.json).toHaveBeenCalledWith(
          expect.objectContaining({ error: expect.stringContaining('Invalid ""to"" phone number format') })
        );
      });
    
      test('should call Infobip SDK and return 200 on success', async () => {
        mockRequest.body = { to: '14155552671', message: 'Hello Test' };
        const mockSuccessResponse = {
          status: 200,
          data: {
            messages: [{ messageId: 'msg-123', status: { groupName: 'ACCEPTED' } }],
          },
        };
        infobipClient.channels.whatsapp.send.mockResolvedValue(mockSuccessResponse);
    
        await sendWhatsAppMessage(mockRequest, mockResponse, nextFunction);
    
        expect(infobipClient.channels.whatsapp.send).toHaveBeenCalledTimes(1);
        expect(infobipClient.channels.whatsapp.send).toHaveBeenCalledWith(expect.objectContaining({
          from: '15551234567',
          to: '14155552671',
          content: { text: 'Hello Test' },
        }));
        expect(mockResponse.status).toHaveBeenCalledWith(200);
        expect(mockResponse.json).toHaveBeenCalledWith(expect.objectContaining({
          message: 'WhatsApp message accepted for delivery.',
          messageId: 'msg-123',
        }));
      });
    
      test('should handle Infobip API error response', async () => {
        mockRequest.body = { to: '14155552671', message: 'Error Test' };
        const mockErrorResponse = {
          response: { // Simulate Axios error structure
            status: 400,
            data: { requestError: { serviceException: { text: 'Invalid number' } } },
            headers: {},
          },
        };
        infobipClient.channels.whatsapp.send.mockRejectedValue(mockErrorResponse);
    
        await sendWhatsAppMessage(mockRequest, mockResponse, nextFunction);
    
        expect(infobipClient.channels.whatsapp.send).toHaveBeenCalledTimes(1);
        expect(mockResponse.status).toHaveBeenCalledWith(400);
        expect(mockResponse.json).toHaveBeenCalledWith(expect.objectContaining({
          error: 'Failed to send WhatsApp message.',
          details: 'Invalid number',
        }));
      });
    
       test('should handle network or other errors', async () => {
        mockRequest.body = { to: '14155552671', message: 'Network Error Test' };
        const genericError = new Error('Network failed');
        infobipClient.channels.whatsapp.send.mockRejectedValue(genericError);
    
        await sendWhatsAppMessage(mockRequest, mockResponse, nextFunction);
    
        expect(infobipClient.channels.whatsapp.send).toHaveBeenCalledTimes(1);
        expect(mockResponse.status).toHaveBeenCalledWith(500);
        expect(mockResponse.json).toHaveBeenCalledWith(
          { error: 'Internal server error preparing the request.' }
        );
      });
    });
  • Integration Testing:

    • Test the interaction between your components (e.g., route calls controller, controller calls service).
    • Can be done with a running server and a test database/mocked external services. Libraries like supertest are excellent for testing HTTP endpoints without needing a running server instance in the same process.
  • End-to-End (E2E) Testing:

    • Simulate real user flows. For this app, it would involve:
      1. Making an API call to /api/send-message.
      2. Actually checking if the message arrived on a test WhatsApp number (requires automation tools that can interact with WhatsApp, which can be complex or against terms of service, or manual verification).
      3. Sending a message from the test WhatsApp number.
      4. Verifying that your webhook received the message and processed it correctly (e.g., checking logs or a test database).
    • E2E tests are valuable but often more complex and slower to run.

9. Deployment Considerations

When moving from development to production:

  • Environment Variables: Use your hosting provider's mechanism for securely managing environment variables (e.g., AWS Secrets Manager, Azure Key Vault, Heroku Config Vars, Docker secrets). Do not commit .env files with production credentials.
  • Hosting: Choose a suitable hosting platform for Node.js applications (e.g., AWS EC2/ECS/Lambda, Google Cloud Run/App Engine, Azure App Service, Heroku, DigitalOcean App Platform, Vercel/Netlify for serverless functions).
  • Persistent Webhook URL: Replace the temporary ngrok URL with the permanent public URL of your deployed application when configuring the Infobip webhook. Ensure this URL is stable and uses HTTPS.
  • Process Management: Use a process manager like PM2 or rely on your hosting platform's built-in mechanisms (like systemd, Docker orchestration) to keep your Node.js application running reliably, handle crashes, and manage logs.
  • Database (If Needed): If you store message history or user data, set up a production-grade database (e.g., PostgreSQL, MySQL, MongoDB Atlas) with proper backups and security.
  • Monitoring and Alerting: Implement monitoring for application performance (CPU, memory), error rates, API latency, and Infobip API health. Set up alerts for critical issues (e.g., using Datadog, New Relic, Prometheus/Grafana, CloudWatch).
  • Security Hardening: Implement all security measures discussed previously (webhook verification, rate limiting, input validation, dependency scanning).
  • Scaling: Consider how your application will handle increased load. This might involve load balancing across multiple instances or using serverless architectures.

Conclusion

You have now built the foundation for a Node.js Express application capable of sending and receiving WhatsApp messages via the Infobip API. We covered project setup, Infobip configuration, sending messages, handling incoming webhooks, and discussed crucial aspects like error handling, security, testing, and deployment.

Remember that this guide provides a starting point. Real-world applications often require more sophisticated logic, robust error handling, comprehensive monitoring, and stringent security practices (especially webhook signature verification). Use this foundation and Infobip's detailed documentation to build out features specific to your needs.

Frequently Asked Questions

how to send whatsapp message node js infobip

Set up an API endpoint using Express.js and utilize the Infobip SDK's `channels.whatsapp.send()` method with a payload containing the recipient's number, your sender number (from Infobip), and the message content. Validate inputs and handle the API response appropriately within your controller logic.

what is infobip whatsapp api node js

The Infobip WhatsApp API, accessible through their Node.js SDK, allows developers to send and receive WhatsApp messages programmatically. It handles the connection to the WhatsApp Business Platform, simplifying integration with your Node.js applications.

why does infobip need a webhook whatsapp

Infobip uses a webhook to deliver incoming WhatsApp messages to your application. This webhook acts as a public URL where Infobip sends real-time HTTP POST requests containing message data when a user interacts with your WhatsApp Sender.

when should I verify infobip webhook signature

Webhook signature verification is crucial for production environments. Always verify the signature in your webhook handler to ensure the incoming request is authentic and hasn't been tampered with, confirming its origin from Infobip.

can I test infobip whatsapp locally node js

Yes, you can test WhatsApp integration locally with tools like ngrok, which creates a temporary public URL that tunnels requests to your local development server, allowing you to receive webhooks from Infobip for testing.

how to receive whatsapp messages node js express infobip

Create a dedicated webhook endpoint (e.g., `/infobip-webhook`) in your Express.js application to handle incoming POST requests from Infobip. Configure this endpoint's public URL in the Infobip portal. The request body will contain the WhatsApp message data.

what is the infobip whatsapp sender number

The Infobip WhatsApp Sender number is the registered and approved phone number associated with your WhatsApp Business account. Obtain this number from the "Channels and Numbers > WhatsApp" section of your Infobip portal, used for sending messages.

how to handle errors infobip whatsapp api node

Implement robust error handling using `try...catch` blocks around Infobip API calls. Handle both API errors (using error codes and messages from the SDK) and potential network issues. Structured logging and retry mechanisms are recommended.

what are infobip whatsapp best practices security

Key security practices include securing your API key using environment variables, verifying webhook signatures to prevent spoofing, validating and sanitizing all user inputs, and implementing rate limiting to protect against abuse.

how to configure infobip whatsapp api node application

Install the Infobip Node.js SDK (`@infobip-api/sdk`) and create an Infobip client instance using your API key and base URL. Centralize this setup in a service module for better organization.

why use ngrok with infobip whatsapp development

ngrok is essential during development as it provides a public, secure URL for your local server, enabling Infobip to deliver webhooks to your application even when running locally.

when to use nodemon infobip whatsapp project

Use nodemon during development to automatically restart your server when you make code changes, streamlining the development workflow.

how to set up infobip whatsapp webhook url

Get your public application URL and combine it with your webhook route path. Configure this full URL in the "Webhook" or "API Integrations" section for your WhatsApp Sender within the Infobip portal.

what Node.js frameworks are compatible with Infobip

The article specifically mentions and provides guidance on using Express.js, a popular and well-suited framework for creating the API and webhook handling required for the WhatsApp integration.