code examples

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

Infobip SMS Webhooks with Next.js: Receive & Process Inbound Messages

Complete guide to building a Next.js webhook endpoint for receiving inbound SMS messages via Infobip. Includes authentication, error handling, Prisma storage, and production deployment.

How to Receive Inbound SMS Messages with Infobip and Next.js

This guide provides a complete walkthrough for building a Next.js application capable of receiving and processing inbound SMS messages sent via the Infobip platform. By implementing a webhook endpoint, your application can handle replies, commands, or any incoming SMS traffic directed to your Infobip number, enabling true two-way messaging conversations.

We'll cover setting up your Next.js project, creating the API route to act as a webhook receiver, configuring Infobip to forward messages, handling basic security, processing incoming messages, storing them (optionally), and deploying the application.

Project Goals:

  • Create a Next.js application with a dedicated API endpoint to receive incoming SMS messages from Infobip.
  • Securely handle Infobip credentials and webhook secrets.
  • Configure Infobip to route inbound messages (Mobile Originated – MO) to the Next.js webhook.
  • Implement basic security validation for incoming webhook requests.
  • Process and log incoming SMS data.
  • (Optional) Store received messages in a database using Prisma.
  • Provide instructions for local testing, deployment, and verification.

Technologies Used:

  • Next.js: React framework for building the web application and API endpoint. Chosen for its ease of development, built-in API routes, and robust ecosystem. (Requires Node.js 18.18 or later as of Next.js 15)
  • Node.js: The runtime environment for Next.js.
  • Infobip: The CPaaS provider used for sending and, crucially here, receiving SMS messages via webhooks. Official Documentation
  • Prisma (Optional): Modern ORM for database interaction. Chosen for its developer experience and type safety. (Prisma 5.x recommended)
  • Ngrok (for testing): Tool to expose local development servers to the internet for webhook testing.

System Architecture:

<!-- Replace this block with a static image or a textual description of the architecture: End User --(Sends SMS)--> Infobip Platform --(Forwards MO SMS via HTTP POST)--> Next.js App / Webhook Endpoint --(Processes Request)--> [Optional Database] The Next.js App sends a 200 OK response back to the Infobip Platform. -->

Prerequisites:

  • An active Infobip account with a number capable of receiving SMS messages.
  • Node.js (v18.18 or later recommended for Next.js 15) and npm/yarn installed.
  • Basic understanding of JavaScript, Node.js, Next.js, and REST APIs.
  • Access to a terminal or command prompt.
  • (Optional) A database (e.g., PostgreSQL, MySQL, SQLite) if implementing the Prisma storage layer.
  • (Optional) Ngrok installed for local webhook testing.

1. Setting Up Your Next.js Project for SMS Webhooks

Let's start by creating a new Next.js project and setting up the basic structure and environment variables.

  1. Create a Next.js App: Open your terminal and run the following command, replacing infobip-inbound-app with your desired project name. We'll use TypeScript for better type safety.

    bash
    npx create-next-app@latest infobip-inbound-app --typescript --eslint --tailwind --src-dir --app --import-alias ""@/*""

    Follow the prompts (you can accept the defaults). This command sets up a new Next.js project using the App Router (--app), TypeScript, ESLint, and Tailwind CSS (optional but common).

  2. Navigate to Project Directory:

    bash
    cd infobip-inbound-app
  3. Set up Environment Variables: Create a file named .env.local in the root of your project. This file will store sensitive information like API keys and secrets. It's crucial not to commit this file to version control (it's included in the default .gitignore created by create-next-app).

    plaintext
    # .env.local
    
    # Infobip Configuration (Obtain from Infobip Portal - see Section 4)
    # NOTE: BASE_URL and API_KEY are primarily needed for *sending* messages via the API.
    # They are not strictly required just to *receive* webhooks if you only implement inbound handling.
    # Include them if you plan to extend this app to send replies.
    INFOBIP_BASE_URL=your_infobip_api_base_url.api.infobip.com
    INFOBIP_API_KEY=YourInfobipApiKey
    
    # Webhook Security (Choose a strong, unique secret)
    # This MUST match the 'Password' you configure in Infobip's webhook Basic Auth settings.
    INFOBIP_WEBHOOK_SECRET=a_very_strong_and_secret_string_you_generate
    
    # (Optional) Database Connection (If using Prisma - see Section 6)
    # Example for PostgreSQL:
    DATABASE_URL=""postgresql://user:password@host:port/database?schema=public""
    • INFOBIP_BASE_URL & INFOBIP_API_KEY: Obtain these from your Infobip account dashboard. Needed if you extend the app to send SMS replies.
    • INFOBIP_WEBHOOK_SECRET: This is a secret you define. It acts as the password for Basic Authentication to secure your webhook endpoint. Configure this exact value in the Infobip portal (Section 4).
    • DATABASE_URL: Add this only if you plan to implement the database layer (Section 6). Adjust the format based on your chosen database.
  4. Project Structure: The create-next-app command with the --app flag sets up the App Router structure. Our primary focus will be within the src/app/api/ directory where we'll create the webhook endpoint.

2. Implementing the Core Functionality (Webhook Endpoint)

The core of this application is the API route that listens for incoming POST requests from Infobip containing Mobile Originated (MO) SMS messages. Infobip MO SMS Documentation

  1. Create the API Route File: Create a new file at src/app/api/infobip-webhook/route.ts. Next.js automatically maps files named route.ts within the app directory structure to API endpoints. This file will handle requests made to /api/infobip-webhook.

  2. Implement the Basic Handler: Add the following code to src/app/api/infobip-webhook/route.ts:

    typescript
    // src/app/api/infobip-webhook/route.ts
    
    import { NextRequest, NextResponse } from 'next/server';
    import logger from '@/lib/logger'; // Assuming logger setup (Section 5)
    
    // Define the expected structure of an incoming Infobip SMS message
    // IMPORTANT: This interface is illustrative. Always verify the exact 'SMS MO'
    // payload structure against the current official Infobip documentation:
    // https://www.infobip.com/docs/api/channels/sms/receive-inbound-sms
    interface InfobipIncomingMessage {
        messageId?: string;
        from?: string;
        to?: string;
        text?: string;
        keyword?: string;
        receivedAt?: string; // ISO 8601 format (e.g., "2023-10-27T10:30:00.123Z")
        cleanText?: string;
        smsCount?: number;
        // Add other fields like 'price', 'mccMnc', 'callbackData' as needed based on docs.
    }
    
    interface InfobipWebhookPayload {
        results: InfobipIncomingMessage[];
        messageCount: number;
        pendingMessageCount: number;
    }
    
    export async function POST(req: NextRequest) {
        logger.info('Received request on /api/infobip-webhook');
    
        try {
            // --- Security Validation (Basic Authentication) ---
            // Reference: https://www.infobip.com/docs/essentials/api-authentication
            const expectedSecret = process.env.INFOBIP_WEBHOOK_SECRET;
            const authHeader = req.headers.get('authorization');
    
            if (!expectedSecret) {
                logger.warn('INFOBIP_WEBHOOK_SECRET is not set in environment variables. Skipping webhook authentication.');
                // Consider returning 500 or denying requests if auth is mandatory
            } else if (!authHeader || !authHeader.toLowerCase().startsWith('basic ')) {
                logger.error('Missing or invalid Authorization header');
                return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
            } else {
                // Decode Basic Auth: base64(username:password)
                const encodedCreds = authHeader.split(' ')[1];
                if (!encodedCreds) {
                     logger.error('Invalid Authorization header format (missing credentials)');
                     return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
                }
                const decodedCreds = Buffer.from(encodedCreds, 'base64').toString('utf-8');
                const [username, password] = decodedCreds.split(':');
    
                // Validate the password (which is our shared secret)
                if (password !== expectedSecret) {
                     logger.error('Invalid webhook secret provided');
                     return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
                }
                // NOTE: Infobip's required 'username' for Basic Auth can vary.
                // It might be your API Key ID, a fixed string like 'InfobipWebhook',
                // or potentially ignored if the password matches.
                // Check the Infobip portal configuration for the exact requirement.
                logger.info(`Webhook authentication successful for user: ${username}`);
            }
            // --- End Security Validation ---
    
            const body: InfobipWebhookPayload = await req.json();
            logger.debug({ payload: body }, 'Webhook payload received'); // Use debug level for potentially large/sensitive data
    
            if (!body.results || !Array.isArray(body.results)) {
                logger.warn('Received invalid payload structure (missing or non-array results).');
                // Acknowledge receipt but indicate potential issue.
                return NextResponse.json({ message: 'Acknowledged invalid payload structure' }, { status: 200 });
            }
    
            if (body.results.length === 0) {
                 logger.info('Received webhook with empty results array.');
                 // Acknowledge receipt of the ping/empty payload.
                 return NextResponse.json({ message: 'Acknowledged empty payload' }, { status: 200 });
            }
    
            // Process each incoming message
            // IMPORTANT: Respond within 2-3 seconds to avoid Infobip retries
            // Consider asynchronous processing if logic is complex/slow
            for (const message of body.results) {
                logger.info({ from: message.from, msgId: message.messageId }, `Processing message`);
    
                // Implement your message processing logic here:
                // - Store the message (See Section 6)
                // - Check for keywords
                // - Trigger replies (Requires sending capabilities)
                // - Enqueue for background processing
            }
    
            // CRITICAL: Always return a 2xx response to Infobip promptly (< 2-3 seconds)
            // to acknowledge successful receipt of the webhook.
            // Failure may cause Infobip to retry delivery unnecessarily.
            return NextResponse.json({ message: 'Webhook received successfully' }, { status: 200 });
    
        } catch (error) {
            // Handle potential errors during processing
            if (error instanceof SyntaxError) {
                 // Error parsing JSON body - likely a malformed request
                 logger.error('Failed to parse incoming JSON payload.', { error });
                 return NextResponse.json({ error: 'Invalid JSON payload' }, { status: 400 });
            }
    
            // Handle other errors (e.g., database errors during save, validation errors)
            logger.error('Error processing Infobip webhook', { error });
    
            // Recommendation: Return 200 OK even for internal processing errors *after*
            // successful receipt and authentication. This acknowledges the message
            // was received and prevents Infobip retries for issues within your system
            // that a retry might not fix. Log the error thoroughly for investigation.
            // Only return 500 if the server is fundamentally unable to process requests
            // *before* even validating/parsing.
            return NextResponse.json({ message: 'Acknowledged, but internal processing error occurred.' }, { status: 200 });
            // Alternative (if retries are desired for specific internal errors):
            // return NextResponse.json({ error: 'Internal server error during processing' }, { status: 500 });
        }
    }
    
    // Optional: Handle GET requests or other methods if needed
    export async function GET(req: NextRequest) {
      logger.warn(`Received unsupported GET request on webhook endpoint.`);
      return NextResponse.json({ message: 'Method Not Allowed' }, { status: 405 });
    }
    • InfobipWebhookPayload / InfobipIncomingMessage: Interfaces defining the expected data structure. Added citation to check official Infobip documentation.
    • POST Function: Handles POST requests from Infobip. Next.js Route Handlers Documentation
    • Security Validation: Implements Basic Authentication per Infobip Authentication Methods, comparing the provided password against INFOBIP_WEBHOOK_SECRET.
    • Parsing: req.json() parses the incoming request body. Includes validation for body.results.
    • Processing Loop: Iterates through the results array.
    • Response Timing: Must respond within 2-3 seconds to prevent Infobip retries.
    • Logging: Uses structured logging (Section 5 setup).
    • Response Codes: Returns 200 OK on successful receipt/processing, 401 for auth failures, 400 for invalid JSON. Recommended 200 OK for internal processing errors after receipt to avoid unnecessary retries.

3. Understanding the Webhook API Contract

The API route src/app/api/infobip-webhook/route.ts serves as the API layer for receiving inbound SMS. Here's a summary of its contract:

  • Endpoint: POST /api/infobip-webhook

    • This is the URL you will configure in the Infobip platform.
    • Only the POST method is expected to receive message data. GET or other methods should return 405 Method Not Allowed.
  • Authentication: Basic Authentication Infobip API Authentication

    • The endpoint expects an Authorization: Basic <credentials> header.
    • The <credentials> part is a Base64 encoded string of username:password.
    • The password MUST be the value you set for INFOBIP_WEBHOOK_SECRET.
    • The required username MUST be verified in your Infobip webhook configuration.
  • Request Body (Expected from Infobip):

    • Content-Type: application/json
    • Structure (verify against Infobip MO SMS Documentation):
      json
      {
        "results": [
          {
            "messageId": "ABC-123-XYZ",
            "from": "15551234567",
            "to": "15559876543",
            "text": "Hello there!",
            "cleanText": "Hello there!",
            "keyword": "HELLO",
            "receivedAt": "2023-10-27T10:30:00.123Z",
            "smsCount": 1
            // ... potentially other fields like price, mccMnc, callbackData, etc.
          }
          // ... potentially more messages if batched
        ],
        "messageCount": 1,
        "pendingMessageCount": 0
      }
  • Response Body (Sent back to Infobip):

    • Success (200 OK): Acknowledges receipt. Must be sent within 2-3 seconds.
      json
      { "message": "Webhook received successfully" }
    • Authentication Error (401 Unauthorized): Incorrect or missing credentials.
      json
      { "error": "Unauthorized" }
    • Bad Request Error (400 Bad Request): Malformed JSON or invalid payload structure.
      json
      { "error": "Invalid JSON payload" }
  • Webhook Retry Behavior: Infobip will retry webhook delivery if it doesn't receive a 2xx response within timeout (typically a few seconds). Design your endpoint to respond quickly and use asynchronous processing for complex operations.

  • Security Requirements:

    • HTTPS is required for production webhook URLs
    • Basic Authentication credentials must be configured in Infobip portal
    • Consider implementing request signature validation for additional security

4. Configuring Infobip SMS Forwarding to Your Webhook

This is where you configure Infobip to send incoming SMS messages to your newly created webhook endpoint.

  1. Obtain Infobip Credentials (If Sending Replies):

    • Log in to your Infobip Portal.
    • Navigate to the API key management section (often under "Developers" or account settings).
    • Copy your API Key and Base URL. API Authentication Documentation
    • If you plan to send replies from your app, add these to your .env.local file (INFOBIP_API_KEY, INFOBIP_BASE_URL).
    • These are not strictly required just for receiving webhooks.
  2. Configure Inbound Message Routing (Webhook): The exact steps can vary slightly based on Infobip's UI updates, but generally involve:

    • Navigate to the "Numbers" section in your Infobip portal.
    • Select the specific number for receiving SMS.
    • Look for "Messaging Settings", "Forwarding Rules", or "Application Settings".
    • Find the section for Incoming Messages or Mobile Originated (MO) Messages.
    • Choose the option to forward messages to a URL (Webhook).
    • Enter Your Webhook URL: The publicly accessible URL of your deployed Next.js application (e.g., https://your-app-domain.com/api/infobip-webhook).
      • IMPORTANT: Must use HTTPS in production for security
      • During local development, use an ngrok URL or similar tunneling service
    • Configure Authentication (Critical for Security):
      • Select Basic Authentication.
      • Enter the Username as required by Infobip (verify in portal).
      • Enter the Password – use the exact same secret as INFOBIP_WEBHOOK_SECRET in your .env.local.
    • Save the configuration.
  3. Webhook Testing Checklist:

    • HTTPS URL configured (required for production)
    • Basic Auth credentials match between Infobip portal and .env.local
    • Webhook endpoint returns 200 OK within 2-3 seconds
    • Logging captures incoming requests for debugging
    • Error handling prevents crashes from malformed requests

5. Implementing Production-Ready Error Handling and Logging

Robust error handling and logging are vital for production applications.

  1. Error Handling Strategy:

    • The primary goal is to reliably acknowledge receipt to Infobip (200 OK) quickly after successful authentication (within 2-3 seconds).
    • Use try...catch blocks to capture errors during request processing.
    • Bad Requests (4xx): Return 400 Bad Request for malformed JSON, 401 Unauthorized for failed authentication, 405 Method Not Allowed for incorrect HTTP methods.
    • Server Errors (Internal Processing): For errors occurring after successful authentication and validation, log the error thoroughly. Recommended: Return 200 OK to acknowledge receipt and prevent retries for potentially persistent internal issues.
    • Infobip Retry Policy: Infobip retries webhook delivery on non-2xx responses. Design for quick acknowledgment and asynchronous processing.
  2. Logging: Use a structured logging library like pino or winston for production.

    • Install Pino:
      bash
      npm install pino pino-pretty # pino-pretty for development
      # or
      yarn add pino pino-pretty
    • Setup Logger: Create src/lib/logger.ts:
      typescript
      // src/lib/logger.ts
      import pino from 'pino';
      
      const logger = pino({
        level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
        transport: process.env.NODE_ENV !== 'production'
          ? { target: 'pino-pretty', options: { colorize: true } }
          : undefined,
      });
      
      export default logger;
    • Use Logger in Webhook:
      • Use appropriate levels (debug, info, warn, error).
      • Log key events: request received, auth success/failure, message processing, errors.
      • Avoid logging full payloads at info level in production (PII concerns); use debug if necessary.
  3. Response Timing Best Practices:

    • Respond to Infobip within 2-3 seconds with 200 OK
    • Use asynchronous processing (message queues, background jobs) for slow operations
    • Monitor webhook response times in production
    • Implement timeout handling to prevent hanging requests

6. Storing SMS Messages with Prisma (Optional Database Layer)

Storing incoming messages enables history tracking, analysis, and stateful conversations. We'll use Prisma 5.x.

  1. Install Prisma:

    bash
    npm install prisma --save-dev
    npm install @prisma/client
    # or
    yarn add prisma -D && yarn add @prisma/client
  2. Initialize Prisma:

    bash
    npx prisma init --datasource-provider postgresql # Or mysql, sqlite, etc.

    This creates a prisma directory with a schema.prisma file. Ensure your .env.local has the correct DATABASE_URL.

  3. Define Schema: Edit prisma/schema.prisma:

    prisma
    // prisma/schema.prisma
    
    generator client {
      provider = "prisma-client-js"
    }
    
    datasource db {
      provider = "postgresql" // Or your chosen provider
      url      = env("DATABASE_URL")
    }
    
    // Model to store incoming Infobip messages
    model IncomingMessage {
      id            String    @id @default(cuid())
      infobipMessageId String?   @unique // Infobip's message ID, prevents duplicates
      sender        String    // 'from' number
      recipient     String    // 'to' number (your Infobip number)
      messageText   String?   // SMS content
      keyword       String?   // Detected keyword, if any
      receivedAt    DateTime  // Timestamp when Infobip received it (UTC)
      processedAt   DateTime  @default(now()) // When your app processed it
      createdAt     DateTime  @default(now())
      updatedAt     DateTime  @updatedAt
    
      @@index([sender])
      @@index([receivedAt])
      @@index([infobipMessageId])
    }
  4. Apply Schema Migrations:

    bash
    npx prisma migrate dev --name init
    npx prisma generate
  5. Implement Data Access in Webhook:

    • Create a Prisma client instance (e.g., src/lib/prisma.ts):
      typescript
      // src/lib/prisma.ts
      import { PrismaClient } from '@prisma/client';
      
      declare global {
        // allow global `var` declarations
        // eslint-disable-next-line no-var
        var prisma: PrismaClient | undefined;
      }
      
      export const prisma =
        global.prisma ||
        new PrismaClient({
          log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
        });
      
      if (process.env.NODE_ENV !== 'production') {
        global.prisma = prisma;
      }
      
      export default prisma;
    • Use the client in your API route:
      typescript
      // src/app/api/infobip-webhook/route.ts
      import { NextRequest, NextResponse } from 'next/server';
      import logger from '@/lib/logger';
      import prisma from '@/lib/prisma'; // Import Prisma client
      // Import Prisma types if needed for detailed error handling
      import { Prisma } from '@prisma/client';
      
      // ... interfaces ...
      
      export async function POST(req: NextRequest) {
         // ... pre-processing, authentication, logging ...
          logger.info('Received request on /api/infobip-webhook');
      
          try {
              // ... Authentication logic ...
              logger.info(`Webhook authentication successful.`);
      
              const body: InfobipWebhookPayload = await req.json();
              // ... basic payload validation ...
              logger.debug({ payload: body }, 'Webhook payload received');
      
              if (body.results && body.results.length > 0) {
                  // Process messages and attempt to store them
                  try {
                      // Use Prisma transaction for atomicity when saving multiple messages
                      await prisma.$transaction(async (tx) => {
                          for (const message of body.results) {
                              logger.info({ from: message.from, msgId: message.messageId }, `Processing message for storage`);
      
                              // Map Infobip payload to your schema
                              const messageData = {
                                  infobipMessageId: message.messageId,
                                  sender: message.from ?? 'unknown', // Handle potential nulls
                                  recipient: message.to ?? 'unknown',
                                  messageText: message.text,
                                  keyword: message.keyword,
                                  // Ensure receivedAt is parsed correctly into a Date object
                                  receivedAt: message.receivedAt ? new Date(message.receivedAt) : new Date(),
                              };
      
                              // Create record in the database
                              await tx.incomingMessage.create({
                                  data: messageData,
                              });
                              logger.info({ msgId: message.messageId }, `Message stored successfully.`);
      
                              // Add keyword handling or other logic here if needed
                          }
                      });
                       logger.info(`Successfully processed and stored ${body.results.length} message(s).`);
                  } catch (dbError) {
                      logger.error({ err: dbError }, ""Database transaction failed during message storage"");
      
                      // Decide response based on strategy:
                      // Option 1 (Recommended): Acknowledge receipt (200 OK) as auth succeeded,
                      // but log the internal storage failure. Prevents retries for DB issues.
                      return NextResponse.json({ message: 'Acknowledged, but failed to store message data.' }, { status: 200 });
      
                      // Option 2: Signal server error (500), potentially triggering Infobip retry.
                      // Use if you believe a retry might succeed (e.g., transient DB issue).
                      // return NextResponse.json({ error: 'Failed to store message data' }, { status: 500 });
                  }
              } else {
                  logger.info('Received webhook with empty or invalid results array.');
                  return NextResponse.json({ message: 'Acknowledged empty/invalid payload' }, { status: 200 });
              }
      
              // If storage (or other processing) succeeded
              return NextResponse.json({ message: 'Webhook received and processed successfully' }, { status: 200 });
      
          } catch (error) {
              // Handle errors occurring before or during initial processing (auth, JSON parsing)
              if (error instanceof SyntaxError) {
                  logger.error('Failed to parse incoming JSON payload.', { error });
                  return NextResponse.json({ error: 'Invalid JSON payload' }, { status: 400 });
              }
               // Check if it was an auth error re-thrown or similar logic needed
               // Example: if (error instanceof AuthError) { ... }
               // For simplicity, checking the status if it was set previously might work,
               // but robust error types are better.
               // Assuming auth errors resulted in a 401 being thrown/returned earlier:
               // if ((error as any)?.status === 401) { ... } - This is fragile.
               // Better to handle specific auth errors where they occur.
      
              logger.error('Unhandled error processing Infobip webhook', { error });
              // Follow recommendation: Acknowledge receipt if possible, log internal error
              return NextResponse.json({ message: 'Acknowledged, but unexpected internal error occurred.' }, { status: 200 });
              // Or return 500 for catastrophic failure:
              // return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
          }
      }

7. Testing Your Infobip Webhook Locally with Ngrok

Before deploying to production, test your webhook endpoint locally using ngrok to expose your development server to the internet.

  1. Install and Start Ngrok:

    bash
    # Install ngrok (if not already installed)
    npm install -g ngrok
    
    # Start your Next.js development server
    npm run dev
    
    # In a new terminal, expose port 3000
    ngrok http 3000
  2. Configure Infobip with Ngrok URL:

    • Copy the HTTPS forwarding URL from ngrok (e.g., https://abc123.ngrok.io)
    • Update your Infobip webhook configuration with https://abc123.ngrok.io/api/infobip-webhook
    • Ensure Basic Auth credentials are configured
  3. Send Test SMS:

    • Send an SMS to your Infobip number
    • Monitor your terminal for incoming webhook requests
    • Verify 200 OK responses and proper message processing

8. Deploying Your SMS Webhook to Production

Deploy your Next.js application to a production environment with HTTPS support:

Recommended Platforms:

  • Vercel: Native Next.js support, automatic HTTPS, serverless functions
  • Railway: Full-stack deployment with database support
  • AWS/GCP/Azure: Enterprise-grade infrastructure with scalability

Deployment Checklist:

  • Environment variables configured (INFOBIP_WEBHOOK_SECRET, DATABASE_URL)
  • HTTPS enabled (required for production webhooks)
  • Database migrations applied (npx prisma migrate deploy)
  • Webhook URL updated in Infobip portal
  • Monitoring and logging configured
  • Error alerting setup for failed webhooks

Frequently Asked Questions (FAQ)

How quickly must my webhook respond to Infobip?

Your webhook endpoint must return a 200 OK response within 2-3 seconds to prevent Infobip from retrying the delivery. For complex processing, acknowledge receipt immediately and use background jobs or message queues for asynchronous processing.

What happens if my webhook is down?

Infobip will retry webhook delivery on non-2xx responses according to their retry policy. Implement proper error handling and monitoring to minimize downtime and ensure you don't lose messages during outages.

Can I use API Keys instead of Basic Auth for webhooks?

For webhook authentication, Infobip requires Basic Authentication as configured in the portal. API Keys are used when your application makes outbound API calls to Infobip (e.g., sending SMS replies). Reference: Infobip API Authentication

How do I handle duplicate webhook deliveries?

Use the infobipMessageId field as a unique identifier and implement idempotency checks. In the Prisma schema provided, this field has a @unique constraint to prevent duplicate message storage.

Do I need HTTPS for local testing?

While HTTPS is required for production webhooks, you can use ngrok or similar tunneling services for local development. Ngrok automatically provides HTTPS endpoints that forward to your local server.

What's the difference between MO and MT messages?

MO (Mobile Originated) messages are inbound SMS sent from end users to your number - these arrive via webhooks. MT (Mobile Terminated) messages are outbound SMS you send from your application to end users via the Infobip Send SMS API.

How do I scale my webhook to handle high volumes?

Consider these approaches:

  • Use serverless functions (Vercel, AWS Lambda) for automatic scaling
  • Implement message queues (Redis, RabbitMQ) for asynchronous processing
  • Deploy multiple webhook instances behind a load balancer
  • Monitor response times and optimize database queries
  • Implement rate limiting to prevent abuse

Summary and Next Steps

You've now built a production-ready Next.js webhook endpoint for receiving inbound SMS messages via Infobip. Your application can:

✅ Receive and authenticate webhook requests from Infobip
✅ Process Mobile Originated (MO) SMS messages
✅ Store message data in a database (optional)
✅ Handle errors gracefully and log events
✅ Scale to production traffic

Next Steps:

  • Implement automated SMS reply logic based on keywords or commands
  • Add conversation tracking for multi-turn dialogues
  • Integrate with CRM systems or notification services
  • Set up monitoring dashboards (Datadog, New Relic, Sentry)
  • Implement message queueing for high-volume scenarios

For sending outbound SMS replies, refer to the Infobip Send SMS API documentation.

Frequently Asked Questions

How to set up Infobip inbound SMS webhook?

First, create a Next.js app and an API route file at 'src/app/api/infobip-webhook/route.ts'. Then, set up environment variables in '.env.local', including your Infobip credentials and a webhook secret. This API route will handle incoming SMS messages from Infobip.

What is the Infobip webhook URL format?

The webhook URL format is your deployed app's public URL plus the API route path. For example: 'https://your-app-domain.com/api/infobip-webhook'. Use ngrok for local testing.

Why does Infobip webhook need Basic Authentication?

Basic Authentication secures your webhook by requiring a username and password. This prevents unauthorized access and ensures only Infobip can send data to your endpoint. You'll set the password to match your 'INFOBIP_WEBHOOK_SECRET'.

When should I use Prisma with the Infobip webhook?

Use Prisma if you need to store incoming SMS messages for later analysis, history tracking, or to manage stateful conversations. The article provides a schema example for storing message data.

Can I test Infobip webhook locally?

Yes, you can use ngrok to create a temporary public URL that forwards requests to your local development server. This allows you to test the integration before deployment.

How to handle Infobip webhook security?

Secure your webhook with Basic Authentication by setting the 'Authorization' header in your requests. The username is often your API Key ID or a designated string, while the password is your 'INFOBIP_WEBHOOK_SECRET'. Verify the exact username Infobip requires.

What is the expected request body for Infobip webhook?

The webhook expects a JSON payload with 'results', 'messageCount', and 'pendingMessageCount'. 'results' is an array of incoming messages, each containing details like 'messageId', 'from', 'to', 'text', and 'receivedAt'.

How to process incoming Infobip SMS messages?

The provided code example iterates through the 'results' array in the webhook payload. Inside the loop, you can implement your logic to store the message, check for keywords, trigger replies, or enqueue for background processing.

Why is returning 200 OK important for Infobip webhook?

Returning a 200 OK response promptly acknowledges successful receipt of the webhook to Infobip, preventing unnecessary retries. Do this even if internal processing encounters errors after authentication, but log such errors thoroughly.

What is the purpose of INFOBIP_WEBHOOK_SECRET?

The 'INFOBIP_WEBHOOK_SECRET' environment variable stores a secret string you generate. It acts as the password for Basic Authentication, securing your webhook. This secret must match the password configured in your Infobip webhook settings.

How to implement error handling for Infobip webhook?

Implement robust error handling using try-catch blocks. For client-side errors, return 4xx status codes. For internal errors after successful authentication, return 200 OK to acknowledge receipt, but log the error for later investigation.

What technologies are used for this Infobip integration?

This integration uses Next.js for the app and API route, Node.js as the runtime, Infobip as the CPaaS provider, and optionally Prisma for database interaction and ngrok for local testing.

What are the prerequisites for setting up this Infobip integration?

You'll need an active Infobip account, Node.js and npm/yarn installed, basic understanding of JavaScript and related technologies, and optionally a database and ngrok.

How to create a database schema for Infobip SMS messages?

The article provides a Prisma schema example that includes fields for the sender, recipient, message text, timestamp, and more. You can adjust this schema based on your specific needs.