Build a Next.js App with Infobip WhatsApp Integration
This guide provides a complete walkthrough for integrating Infobip's WhatsApp Business API into a Next.js application. You will learn how to set up your environment, build an API endpoint to send WhatsApp messages using the Infobip Node.js SDK, create a basic frontend to interact with the API, and configure webhooks to receive incoming messages.
By the end of this tutorial, you will have a functional Next.js application capable of sending text messages via WhatsApp through Infobip and a basic setup for handling inbound messages, laying the groundwork for more complex conversational applications.
Project Overview and Goals
What We're Building:
We are building a Next.js application that includes:
- An API route (
/api/send-whatsapp
) that accepts a phone number and message text, then uses the Infobip SDK to send a WhatsApp message. - A simple frontend page with a form to trigger the API route.
- An API route (
/api/whatsapp-webhook
) to receive incoming message notifications from Infobip.
Problem Solved:
This integration enables developers to programmatically send and receive WhatsApp messages, automating notifications, customer support interactions, alerts, or other communication workflows directly from their Next.js application.
Technologies Used:
- Next.js: A React framework for building full-stack web applications. We'll use its API routes feature for backend logic.
- Node.js: The runtime environment for our Next.js backend.
- TypeScript: For static typing and improved developer experience.
- Infobip: The Communications Platform as a Service (CPaaS) provider offering the WhatsApp Business API.
@infobip-api/sdk
: The official Infobip Node.js SDK for interacting with their APIs.dotenv
: To manage environment variables securely.
System Architecture:
<!-- Mermaid diagram visualizing system architecture was removed for standard Markdown compatibility -->
(Ensure your rendering environment supports Mermaid diagrams for the above visualization)
Prerequisites:
- Node.js (v16 or later recommended) and npm/yarn installed.
- An active Infobip account (create a free trial account here).
- An approved WhatsApp Sender number configured within your Infobip account.
- Basic understanding of React, Next.js, and REST APIs.
ngrok
or a similar tunneling service (likelocaltunnel
or Cloudflare Tunnels) for testing webhooks locally. Alternatively, deploy to a staging environment with a public URL.
1. Setting Up the Project
Let's initialize a new Next.js project and install the necessary dependencies.
-
Create a Next.js App: Open your terminal and run the following command to create a new Next.js project with TypeScript:
npx create-next-app@latest infobip-whatsapp-nextjs --typescript
When prompted, accept the defaults or configure ESLint, Tailwind CSS,
src/
directory, and App Router according to your preferences. This guide assumes the use of thesrc/
directory and App Router. -
Navigate to Project Directory:
cd infobip-whatsapp-nextjs
-
Install Dependencies: We need the Infobip SDK and
dotenv
for managing environment variables.npm install @infobip-api/sdk dotenv
or using yarn:
yarn add @infobip-api/sdk dotenv
-
Configure Environment Variables: Create a file named
.env.local
in the root of your project. This file will store your Infobip credentials securely. Never commit this file to version control.# .env.local # Get from Infobip Portal -> API Keys Management INFOBIP_API_KEY=YOUR_INFOBIP_API_KEY # Get from Infobip Portal -> API Keys Management (or main dashboard) INFOBIP_BASE_URL=YOUR_INFOBIP_BASE_URL # Your registered WhatsApp sender number from Infobip (e.g., 447860099299) INFOBIP_WHATSAPP_SENDER=YOUR_WHATSAPP_SENDER_NUMBER # A secret token you define for verifying incoming webhooks (CRITICAL for production) INFOBIP_WEBHOOK_SECRET=YOUR_CUSTOM_SECRET_STRING
- Finding
INFOBIP_API_KEY
andINFOBIP_BASE_URL
: Log in to your Infobip account. Navigate to the API Keys section (usually accessible from the developer settings or dashboard homepage). Generate a new API key if you don't have one. Your Base URL will also be displayed here. It typically looks likexxxxx.api.infobip.com
. (Note: UI navigation in the Infobip portal may change over time.) - Finding
INFOBIP_WHATSAPP_SENDER
: This is the phone number you registered and connected to the WhatsApp Business API through the Infobip portal. Find it under the ""Channels and Numbers"" or ""WhatsApp"" section. (Note: UI navigation in the Infobip portal may change over time.) INFOBIP_WEBHOOK_SECRET
: Choose a strong, random string. You must use this later when configuring the webhook in the Infobip portal and implement signature verification in your webhook handler (Section 4 & 7) to ensure requests are genuinely from Infobip.
- Finding
-
Update
.gitignore
: Ensure.env.local
is listed in your.gitignore
file to prevent accidentally committing your secrets. The default Next.js.gitignore
usually includes it.# .gitignore # ... other entries .env*.local
2. Implementing Core Functionality: Sending Messages
We'll create an API route that handles sending WhatsApp messages via the Infobip SDK.
-
Create the API Route File: Create a new file at
src/app/api/send-whatsapp/route.ts
. -
Implement the Sending Logic: Paste the following code into
src/app/api/send-whatsapp/route.ts
. This code sets up an API endpoint that listens for POST requests, extracts the recipient number and message, and uses the Infobip SDK to send the WhatsApp message.// src/app/api/send-whatsapp/route.ts import { NextRequest, NextResponse } from 'next/server'; import { Infobip, AuthType } from '@infobip-api/sdk'; // Load environment variables 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; // Basic check for environment variables if (!INFOBIP_API_KEY || !INFOBIP_BASE_URL || !INFOBIP_WHATSAPP_SENDER) { console.error(""Infobip environment variables are missing!""); // In a real app, you might want more robust handling or prevent startup } // Instantiate Infobip client (consider memoization in high-traffic/serverless environments // to avoid redundant initializations on subsequent requests and improve performance) let infobip: Infobip | null = null; if (INFOBIP_API_KEY && INFOBIP_BASE_URL) { infobip = new Infobip({ baseUrl: INFOBIP_BASE_URL, apiKey: INFOBIP_API_KEY, authType: AuthType.ApiKey, }); } export async function POST(request: NextRequest) { if (!infobip) { return NextResponse.json( { success: false, error: ""Infobip client not initialized. Check environment variables."" }, { status: 500 } ); } try { const body = await request.json(); const { to, message } = body; // Basic input validation if (!to || !message) { return NextResponse.json( { success: false, error: 'Missing ""to"" or ""message"" in request body' }, { status: 400 } ); } // Validate phone number format - aiming for E.164 compatibility // E.164 format: Optional '+' followed by country code and number, 11-15 digits total. // Examples: +14155552671, 447123456789 const e164Regex = /^\+?\d{11,15}$/; if (!e164Regex.test(to)) { return NextResponse.json( { success: false, error: 'Invalid ""to"" phone number format. Use E.164 format (e.g., +447123456789 or 447123456789).' }, { status: 400 } ); } console.log(`Sending WhatsApp message to: ${to} from: ${INFOBIP_WHATSAPP_SENDER}`); const response = await infobip.channels.whatsapp.send({ type: 'text', from: INFOBIP_WHATSAPP_SENDER, // Your registered Infobip WhatsApp sender to: to, // Recipient phone number (E.164 format) content: { text: message, // The message text }, // Optional: Add messageId for tracking, callbackData, etc. // messageId: `my-unique-msg-id-${Date.now()}`, // notifyUrl: 'YOUR_DELIVERY_REPORT_WEBHOOK_URL' // For delivery reports }); console.log('Infobip API Response:', response); // Check Infobip's response structure for success indication if needed // The SDK might throw an error for failures, caught below. // A successful request usually returns details like messageId and status. return NextResponse.json({ success: true, data: response }); } catch (error: any) { console.error('Error sending WhatsApp message:', error); // Provide more specific error feedback if possible const errorMessage = error.response?.data?.requestError?.serviceException?.text || error.message || 'Failed to send message'; const statusCode = error.response?.status || 500; return NextResponse.json( { success: false, error: errorMessage }, { status: statusCode } ); } } // Optional: Add a GET handler or other methods if needed export async function GET() { return NextResponse.json({ message: ""Send POST request to /api/send-whatsapp with { to: 'phoneNumber', message: 'your message' }"" }); }
Explanation:
- We import necessary modules from
next/server
and@infobip-api/sdk
. - Environment variables are loaded using
process.env
. We include basic checks to ensure they are present. - An
Infobip
client instance is created using the API Key and Base URL. The comment explains why memoization might be beneficial. - The
POST
handler function receives theNextRequest
. - It parses the JSON body to get the
to
phone number andmessage
text. - Basic validation ensures the required fields are present.
- The phone number (
to
) is validated against a regex (e164Regex
) that checks for E.164 format (optional leading+
, followed by 11-15 digits). The error message guides the user on the expected format. infobip.channels.whatsapp.send()
is called with the required parameters:type
,from
,to
, andcontent
.- The response from the Infobip API is logged and returned in the API response.
- A
try...catch
block handles potential errors during the API call, logging the error and returning a JSON error response with an appropriate status code.
- We import necessary modules from
3. Building a Simple Frontend
Let's create a basic React component to interact with our API endpoint.
-
Modify the Homepage: Open
src/app/page.tsx
and replace its content with the following:// src/app/page.tsx 'use client'; // Required for components with hooks like useState, useEffect import React, { useState } from 'react'; export default function HomePage() { const [phoneNumber, setPhoneNumber] = useState(''); const [message, setMessage] = useState(''); const [status, setStatus] = useState(''); // To display success/error messages const [isLoading, setIsLoading] = useState(false); const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); setIsLoading(true); setStatus(''); // Clear previous status try { const response = await fetch('/api/send-whatsapp', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ to: phoneNumber, // Send raw phone number, backend will validate E.164 message: message, }), }); const result = await response.json(); if (response.ok && result.success) { // Using optional chaining for safety, in case the response structure varies unexpectedly const messageId = result.data?.messages?.[0]?.messageId || 'N/A'; setStatus(`Message sent successfully! Message ID: ${messageId}`); setPhoneNumber(''); // Clear fields on success setMessage(''); } else { setStatus(`Error: ${result.error || 'Failed to send message'}`); } } catch (error: any) { console.error('Frontend fetch error:', error); setStatus(`Error: ${error.message || 'An unexpected error occurred.'}`); } finally { setIsLoading(false); } }; // Note: This example uses inline styles for simplicity. // If you selected Tailwind CSS during setup, consider using Tailwind classes // for consistency with the rest of your application. return ( <div style={{ maxWidth: '500px'_ margin: '50px auto'_ padding: '20px'_ border: '1px solid #ccc'_ borderRadius: '8px' }}> <h1>Send WhatsApp Message via Infobip</h1> <form onSubmit={handleSubmit}> <div style={{ marginBottom: '15px' }}> <label htmlFor=""phoneNumber"" style={{ display: 'block'_ marginBottom: '5px' }}> Recipient Phone Number (E.164 format): </label> <input type=""tel"" id=""phoneNumber"" // ID matches htmlFor value={phoneNumber} onChange={(e) => setPhoneNumber(e.target.value)} required placeholder=""e.g., +14155552671 or 447123456789"" style={{ width: '100%', padding: '8px', boxSizing: 'border-box' }} /> </div> <div style={{ marginBottom: '15px' }}> <label htmlFor=""message"" style={{ display: 'block'_ marginBottom: '5px' }}> Message: </label> <textarea id=""message"" // ID matches htmlFor value={message} onChange={(e) => setMessage(e.target.value)} required rows={4} style={{ width: '100%', padding: '8px', boxSizing: 'border-box' }} /> </div> <button type=""submit"" disabled={isLoading} style={{ padding: '10px 20px'_ cursor: isLoading ? 'not-allowed' : 'pointer' }} > {isLoading ? 'Sending...' : 'Send Message'} </button> </form> {status && ( <p style={{ marginTop: '20px'_ color: status.startsWith('Error') ? 'red' : 'green' }}> {status} </p> )} </div> ); }
Explanation:
'use client'
directive marks this as a Client Component, necessary for using hooks likeuseState
.- State variables (
phoneNumber
,message
,status
,isLoading
) manage the form inputs and feedback. handleSubmit
is triggered on form submission. It prevents the default form action, sets loading state, and makes afetch
request to our/api/send-whatsapp
endpoint.- The raw phone number input is sent; the backend API route handles validation.
- The
htmlFor
attributes on labels now correctly match theid
attributes of the corresponding input/textarea. - Optional chaining (
?.
) is used when accessing themessageId
from the response data as a defensive measure against potential variations in the success response structure. - The response from the API is checked, and the
status
message is updated accordingly. - Basic error handling for the
fetch
call is included. - A simple form allows users to input the phone number and message.
- A note is added regarding the use of inline styles versus Tailwind CSS.
4. Integrating with Infobip: Receiving Messages (Webhook)
To receive incoming WhatsApp messages sent to your Infobip number, you need to set up a webhook. Infobip will send an HTTP POST request to a URL you provide whenever a message arrives.
-
Create the Webhook API Route File: Create a new file at
src/app/api/whatsapp-webhook/route.ts
. -
Implement the Webhook Handler: Paste the following code into
src/app/api/whatsapp-webhook/route.ts
. This handler includes essential signature verification logic.// src/app/api/whatsapp-webhook/route.ts import { NextRequest, NextResponse } from 'next/server'; import crypto from 'crypto'; // Needed for signature verification // Load your webhook secret for verification const INFOBIP_WEBHOOK_SECRET = process.env.INFOBIP_WEBHOOK_SECRET; // --- IMPORTANT: Webhook Signature Verification Function --- // This is a conceptual example. You MUST adapt it based on Infobip's // specific documentation regarding signature calculation (header name, algorithm, encoding). // DO NOT use this function in production without verifying Infobip's exact requirements. async function verifyInfobipSignature(request: NextRequest, secret: string): Promise<boolean> { const signatureHeader = request.headers.get('X-Infobip-Signature'); // Check Infobip docs for the correct header name! if (!signatureHeader) { console.warn('Missing signature header from Infobip webhook.'); return false; } try { const requestBody = await request.text(); // Read body ONCE as text const hash = crypto .createHmac('sha256', secret) // Check Infobip docs for correct algorithm (sha256? sha1?) .update(requestBody) .digest('hex'); // Check Infobip docs for correct encoding (hex? base64?) // Compare calculated hash with the signature from the header // Note: Use timing-safe comparison in production if possible const isValid = crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(signatureHeader)); if (!isValid) { console.warn('Invalid webhook signature.'); } return isValid; } catch (error) { console.error('Error during signature verification:', error); return false; } } // --- End Verification Function --- export async function POST(request: NextRequest) { console.log('Received Infobip WhatsApp Webhook POST request'); // --- CRITICAL: Webhook Signature Verification --- // You MUST enable and correctly implement this for production security. if (INFOBIP_WEBHOOK_SECRET) { // Clone the request to read the body for verification without consuming it for the main logic const clonedRequest = request.clone(); const isValid = await verifyInfobipSignature(clonedRequest, INFOBIP_WEBHOOK_SECRET); if (!isValid) { console.error('Webhook signature verification failed!'); return NextResponse.json({ error: 'Invalid signature' }, { status: 403 }); } console.log('Webhook signature verified successfully.'); } else { console.warn('INFOBIP_WEBHOOK_SECRET is not set. Skipping signature verification. THIS IS INSECURE FOR PRODUCTION.'); } // --- End Verification --- try { // Now read the JSON body from the original request stream const payload = await request.json(); console.log('Webhook Payload:', JSON.stringify(payload, null, 2)); // --- Process the Message --- // The payload structure depends on the message type (text, media, location, etc.) // Typically, you'll find messages under `payload.results` which is an array. if (payload.results && Array.isArray(payload.results)) { for (const message of payload.results) { // Example: Log sender and text content if (message.from && message.message?.content?.text) { console.log(`Message from ${message.from}: ${message.message.content.text}`); // Add your logic here: save to DB, trigger replies, etc. } // TODO: Add handling for other message types (media, location, etc.) // TODO: Implement idempotency checks (e.g., store message IDs) } } // --- End Processing --- // Important: Return a 200 OK response quickly to acknowledge receipt. // Infobip may retry if it doesn't receive a timely 200 status. return NextResponse.json({ success: true, message: ""Webhook received"" }); } catch (error: any) { console.error('Error processing webhook:', error); // Return 500 but avoid sending detailed errors back to Infobip return NextResponse.json({ success: false, error: 'Internal Server Error' }, { status: 500 }); } } // Infobip might send a GET request for webhook validation during setup (rare) export async function GET(request: NextRequest) { console.log('Received Infobip WhatsApp Webhook GET request (Validation?)'); // Handle potential validation challenges if required by Infobip setup. // Often not needed for WhatsApp webhooks, more common for other platforms. return NextResponse.json({ message: ""Webhook endpoint is active. Use POST for messages."" }); }
Explanation:
- The
POST
handler receives the incoming request from Infobip. - Crucially, it now includes a placeholder
verifyInfobipSignature
function and logic to call it. This function uses Node.jscrypto
to perform HMAC SHA256 hashing (this needs verification against Infobip's specific documentation). It reads the raw request body, calculates the hash using yourINFOBIP_WEBHOOK_SECRET
, and compares it to the signature provided in theX-Infobip-Signature
header (header name also needs verification). - This verification step is mandatory for production. The code includes warnings if the secret is missing or verification fails. You must consult Infobip's documentation for the exact header name, hashing algorithm (e.g., SHA256, SHA1), encoding (e.g., hex, base64), and implementation details.
- The request body is read after potential verification (using
request.json()
which consumes the body stream). Verification reads the body from a cloned request to avoid consuming the stream prematurely. - The entire payload is logged for inspection.
- The code includes a basic example of iterating through
payload.results
to extract sender (from
) and text message content. You'll need to adapt this based on the actual payload structure documented by Infobip for different message types. - It's critical to return a
200 OK
response promptly to Infobip to acknowledge receipt. Failure to do so might cause Infobip to retry sending the webhook, leading to duplicate processing. Implement idempotency (e.g., checking message IDs) in your processing logic. - Error handling logs issues but returns a generic 500 error.
- The
-
Expose Local Endpoint (e.g., with
ngrok
): To test the webhook locally, Infobip needs a publicly accessible URL.-
Start your Next.js development server:
npm run dev
(usually runs on port 3000). -
In a new terminal window, run
ngrok
or your chosen tunneling tool:# Example using ngrok ngrok http 3000
-
The tool will provide a public HTTPS URL (e.g.,
https://<random-string>.ngrok-free.app
). Copy this HTTPS URL. -
Production/Staging Note: For persistent testing or production, use a deployed URL (e.g., from Vercel, Netlify) or a more permanent tunneling solution if required during staging (like Cloudflare Tunnels).
-
-
Configure Webhook in Infobip Portal:
- Log in to your Infobip account.
- Navigate to the ""Channels and Numbers"" section, find your WhatsApp sender, and look for ""Inbound Messages"" or ""Webhook Settings"". (Note: UI navigation in the Infobip portal may change over time.)
- Paste the public HTTPS URL (from
ngrok
or your deployment), appending your API route path:https://<your-public-url>/api/whatsapp-webhook
. - Find the field for the ""Secret"" or ""Signature Key"". Paste the exact value of your
INFOBIP_WEBHOOK_SECRET
from your.env.local
file here. This is required for the signature verification to work. - Save the configuration.
5. Verification and Testing
Now, let's test both sending and receiving messages.
-
Start the Application: If it's not already running, start your Next.js app:
npm run dev
-
Test Sending:
- Open your browser to
http://localhost:3000
. - Enter a valid WhatsApp phone number (in E.164 format, e.g.,
+14155552671
or447123456789
) in the ""Recipient Phone Number"" field. You can use your own number for testing. - Enter a message in the ""Message"" field.
- Click ""Send Message"".
- Check the status message on the webpage.
- Verify that the message arrives on the recipient's WhatsApp account.
- Check your terminal running
npm run dev
for logs from the/api/send-whatsapp
route.
- Open your browser to
-
Test Receiving (Webhook):
- Using the WhatsApp account associated with the number you sent to in the previous step, reply to the message you received from your Infobip sender number.
- Watch the terminal window where
npm run dev
is running. You should see logs from the/api/whatsapp-webhook
route:- ""Received Infobip WhatsApp Webhook POST request""
- Potentially ""Webhook signature verified successfully."" (if secret is set and verification passes) or warnings/errors related to signature.
- The JSON payload of the incoming message.
- The log ""Message from [sender_number]: [message_text]"".
- You can also check the
ngrok
web interface (usuallyhttp://localhost:4040
) or your tunneling tool's interface to inspect the incoming HTTP request details, including headers likeX-Infobip-Signature
.
6. Error Handling, Logging, and Retries
- Error Handling: The provided API routes include basic
try...catch
blocks. For production:- Implement more specific error handling based on Infobip's error codes/responses.
- Use a dedicated error tracking service (e.g., Sentry, Datadog).
- Validate inputs more robustly using libraries like
zod
for schema validation.
- Logging:
console.log
is used for simplicity. For production:- Use a structured logging library (e.g., Pino, Winston) to output logs in JSON format.
- Adjust log levels (debug, info, warn, error).
- Ensure sensitive data (like full message content, if subject to privacy rules) is potentially masked or omitted from logs depending on requirements.
- Retries:
- Sending: The Infobip SDK might handle some transient network errors. For critical messages, consider implementing an application-level retry mechanism with exponential backoff, possibly using a job queue (e.g., BullMQ, or external queue systems) to handle failures gracefully without blocking the API response.
- Receiving (Webhook): Ensure your webhook handler is idempotent (processing the same message multiple times doesn't cause negative side effects), as Infobip will retry delivery if it doesn't receive a timely
200 OK
or encounters network issues. Store message IDs (message.messageId
from the payload) and check for duplicates before processing critical actions.
7. Security Features
- Environment Variables: Keep API keys and secrets out of your codebase using
.env.local
and ensure this file is in.gitignore
. Use platform-specific environment variable management for deployment (e.g., Vercel Environment Variables, AWS Secrets Manager). - Input Validation: Sanitize and validate all inputs from the frontend (
to
,message
) and from webhooks. Use libraries likezod
for schema validation on API routes. - Webhook Security: Implementing and verifying the webhook signature is CRITICAL. The example code in Section 4 provides a conceptual implementation, but you must adapt it based on Infobip's specific documentation for the correct header name, algorithm, and encoding. Consult the Infobip documentation (search for webhook security or signature verification) for the precise method. Failure to do this leaves your webhook endpoint vulnerable.
- Rate Limiting: Protect your API endpoints (
/api/send-whatsapp
,/api/whatsapp-webhook
) from abuse by implementing rate limiting. Tools like@upstash/ratelimit
or platform features (e.g., Vercel's IP blocking/rate limiting) can be used. - HTTPS: Always use HTTPS for your application and webhook URLs. Tunneling tools like
ngrok
provide this locally, and deployment platforms like Vercel handle it automatically.
8. Handling Special Cases (WhatsApp Specific)
- 24-Hour Window: WhatsApp imposes a 24-hour limit for free-form messages initiated by the business. After 24 hours since the user's last message, you can only send pre-approved Template Messages. The code above sends a standard text message (
type: 'text'
), which only works within this window or as a reply to an incoming user message. Refer to Infobip's documentation on sending Template Messages for sending messages outside the window. You'll need to use a different payload structure (type: 'template'
) and provide the registered template name and placeholders. - Message Types: This guide focuses on text messages (
type: 'text'
). Infobip supports various types (images, documents, audio, video, location, interactive messages like buttons and lists). Refer to the Infobip SDK documentation and API reference for payload structures for different types. - Opt-ins: Ensure you have proper user consent (opt-in) before initiating conversations or sending promotional messages via WhatsApp, complying with WhatsApp policies and local regulations (like GDPR, TCPA).
- Number Formatting: Always strive to use and validate the E.164 format for phone numbers (e.g.,
+14155552671
,447123456789
). This typically includes an optional+
followed by the country code and the subscriber number, without spaces or dashes. Our validation regex (^\+?\d{11,15}$
) enforces this structure.
9. Deployment and CI/CD
- Platform Choice: Vercel is a natural choice for deploying Next.js applications. Other options include Netlify, AWS Amplify, Render, or self-hosting with Node.js.
- Environment Variables: Configure your
INFOBIP_API_KEY
,INFOBIP_BASE_URL
,INFOBIP_WHATSAPP_SENDER
, andINFOBIP_WEBHOOK_SECRET
in your chosen platform's environment variable settings. Do not hardcode them. - Build Command: Typically
npm run build
oryarn build
. - Webhook URL Update: Once deployed, update the webhook URL in the Infobip portal to point to your production URL (e.g.,
https://your-app-domain.com/api/whatsapp-webhook
). Ensure the secret key is also correctly configured there. - CI/CD: Platforms like Vercel, Netlify, GitHub Actions, GitLab CI offer seamless Git integration for automatic deployments on push/merge to specific branches. Configure build steps and environment variables within the CI/CD pipeline.
- Rollback: Familiarize yourself with your deployment platform's rollback procedures (e.g., Vercel allows instantly promoting a previous deployment).
10. Troubleshooting and Caveats
- Missing Env Vars: Error ""Infobip client not initialized"" usually means
.env.local
is missing, not loaded correctly, or the environment variables (INFOBIP_API_KEY
,INFOBIP_BASE_URL
) are misspelled or undefined in the current environment (especially check deployment environment variables). - Invalid Phone Number Format: Ensure the
to
number in the send request strictly follows E.164 format (e.g.,+14155552671
,447123456789
). The API route includes validation, but double-check the input. - Webhook Not Receiving:
- Verify the public URL (
ngrok
or deployed URL +/api/whatsapp-webhook
) is correctly configured in the Infobip portal. - Ensure your local server (
npm run dev
) or deployment is running. - Check
ngrok
's web interface (http://localhost:4040
) or your tunneling tool's logs for incoming requests. - Confirm your firewall isn't blocking incoming connections (less common with tunneling/deployment platforms).
- Verify the public URL (
- Webhook Signature Failure:
- Ensure the
INFOBIP_WEBHOOK_SECRET
in your.env.local
(and deployment environment) exactly matches the secret configured in the Infobip portal. - Double-check the header name (
X-Infobip-Signature
?), hashing algorithm (sha256
?), and encoding (hex
?) used in theverifyInfobipSignature
function against Infobip's official documentation. The provided function is a template.
- Ensure the
- 400 Bad Request (Sending): Often due to missing
to
ormessage
in the request body, or invalid phone number format. Check the API route's logs for specific error messages. - Infobip API Errors: Check the error response (
error.response?.data
) logged in thecatch
block of the/api/send-whatsapp
route for specific error details from Infobip (e.g., authentication failure, insufficient funds, invalid sender). - 24-Hour Window Exceeded: If sending fails outside the 24-hour window, you likely need to use pre-approved WhatsApp Template Messages instead of standard text messages.