June 11, 2026

Build an AI agent on WhatsApp for your small business (Laravel + OpenAI)

Photo of Marco Orta Marco Orta | 11 min read
Compartir
AI agent replying on WhatsApp, connected to a Laravel backend and the OpenAI API

In Latin America, the first contact with a business doesn’t happen on its website or over a phone call: it happens on WhatsApp. The catch is that the message usually arrives at 11 p.m., on a Sunday, or while you’re with another customer — and a lead that doesn’t get a reply within minutes goes cold. An AI agent on WhatsApp solves exactly that: it replies instantly, qualifies the prospect, and, when needed, hands you the conversation already warmed up.

In this tutorial you’ll build an AI-powered WhatsApp chatbot using the WhatsApp Cloud API, Laravel as the brain, and the OpenAI API to understand and write the replies. It’s not a generic widget: it’s an agent that knows your catalog, remembers the thread of the conversation, and behaves with your brand’s personality.

If you haven’t integrated OpenAI into Laravel yet, start with the guide to integrating the OpenAI API in Laravel; here we assume the PHP client is already installed.

Why WhatsApp is the #1 channel for small businesses in Latin America

WhatsApp isn’t “just another channel.” In Mexico, Colombia, or Argentina it’s the channel: people ask about prices, book appointments, and close purchases there before they ever visit your site. For a small business that has two sides:

  • The opportunity: open rates above 90% and replies in seconds. No email comes close.
  • The bottleneck: someone has to be on the other end. And “someone” is rarely available 24/7.

In February 2026 Meta began rolling out native Business AI across more than 15 countries in the region, which normalized the idea of being served by an automated assistant. The psychological barrier is already gone: your customers expect an immediate reply, whether it’s human or not. The question is no longer “should I automate?” but “do I build it custom or settle for a black box I don’t control?”.

What an AI agent actually solves

Before writing a single line of code, it’s worth being clear about the job the agent will do. The three that move the needle for a small business:

  1. Qualify leads. It asks just enough (what they need, budget, location) and tags the prospect before you even join the conversation.
  2. Answer the repetitive stuff 24/7. Hours, prices, availability, “do you ship?”. 80% of the messages are the same 10 questions.
  3. Sell and book. It suggests products from your catalog, shares a payment link, or reserves a slot on your calendar.

The key is that an AI agent doesn’t respond with a rigid button tree: it understands natural language, keeps the context (“and do you have that one in blue?”), and knows when to escalate to a human. That’s what sets it apart from the chatbots of five years ago.

Architecture: WhatsApp Cloud API + Laravel + OpenAI

The full flow is simpler than it looks:

Customer on WhatsApp
        │  (sends a message)
        ▼
WhatsApp Cloud API (Meta)
        │  webhook POST → your server
        ▼
Laravel  ──►  fetches the conversation history
        │     builds the prompt (system + catalog + thread)
        ▼
OpenAI (gpt-4o-mini)  ──►  writes the reply
        │
        ▼
Laravel  ──►  sends the reply via the Graph API
        ▼
WhatsApp Cloud API  ──►  the customer receives the message

You’ll use two endpoints of the Cloud API:

OperationMethodWhat for
Webhook verificationGET /webhookMeta confirms the endpoint is yours
Receiving messagesPOST /webhookMeta notifies you of every incoming message
Sending repliesPOST /{phone-number-id}/messagesYour backend answers the customer

Prerequisites

  • A Meta Business account with a number registered on the WhatsApp Business Platform (Cloud API). The free test sandbox is enough for this entire tutorial.
  • The Phone Number ID and an access token (ideally permanent, via a System User).
  • A working Laravel 11 or 12 project. If you’re coming from an older version, check out how to upgrade Laravel 9 to Laravel 10.
  • PHP 8.2+ — if you don’t have it yet, see how to install PHP on Windows.
  • The OpenAI client installed following the OpenAI integration guide for Laravel.
  • A public HTTPS URL for the webhook. In development, a tunnel like ngrok or expose is enough.

Step 1: Environment variables

Never put tokens in your code. Open your .env:

WHATSAPP_TOKEN=EAAG...your-permanent-token
WHATSAPP_PHONE_NUMBER_ID=1234567890
WHATSAPP_VERIFY_TOKEN=a-secret-string-you-make-up
WHATSAPP_API_VERSION=v21.0

OPENAI_API_KEY=sk-your-openai-key

You make up the WHATSAPP_VERIFY_TOKEN yourself: it’s the secret word Meta will send back when you register the webhook, to confirm the endpoint is yours.

Centralize the config in config/services.php:

// config/services.php

return [
    // ... other configuration

    'whatsapp' => [
        'token' => env('WHATSAPP_TOKEN'),
        'phone_number_id' => env('WHATSAPP_PHONE_NUMBER_ID'),
        'verify_token' => env('WHATSAPP_VERIFY_TOKEN'),
        'version' => env('WHATSAPP_API_VERSION', 'v21.0'),
    ],

    'openai' => [
        'api_key' => env('OPENAI_API_KEY'),
    ],
];

Step 2: The webhook routes

Meta does two things against the same endpoint: it verifies it once (GET) and then notifies you of every message (POST). In routes/web.php:

use App\Http\Controllers\WhatsAppWebhookController;
use Illuminate\Support\Facades\Route;

Route::get('/webhook/whatsapp', [WhatsAppWebhookController::class, 'verify']);
Route::post('/webhook/whatsapp', [WhatsAppWebhookController::class, 'handle']);

Important: the WhatsApp webhook arrives without a CSRF token. In Laravel 11+, add the route to the exceptions in bootstrap/app.php with $middleware->validateCsrfTokens(except: ['webhook/whatsapp']), or place it in routes/api.php.

Step 3: Verify the webhook

When you register the URL in the Meta dashboard, it will make a GET request with three parameters. Your job is to return the hub.challenge only if the verify_token matches:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Services\WhatsAppAgent;

class WhatsAppWebhookController extends Controller
{
    public function verify(Request $request)
    {
        $mode = $request->query('hub_mode');
        $token = $request->query('hub_verify_token');
        $challenge = $request->query('hub_challenge');

        if ($mode === 'subscribe'
            && $token === config('services.whatsapp.verify_token')) {
            // Meta expects the challenge as plain text, not JSON.
            return response($challenge, 200);
        }

        return response('Forbidden', 403);
    }
}

Step 4: Receive the message and reply

Here’s the heart of the agent. The POST arrives with a nested structure; we extract the text and the sender’s number, and reply quickly: Meta retries the webhook if you take more than a few seconds, so the heavy lifting goes into a queue.

public function handle(Request $request, WhatsAppAgent $agent)
{
    $entry = $request->input('entry.0.changes.0.value');
    $message = $entry['messages'][0] ?? null;

    // Status updates (delivered, read) also arrive here: we ignore them.
    if (! $message || ($message['type'] ?? null) !== 'text') {
        return response()->json(['status' => 'ignored']);
    }

    $from = $message['from'];                 // customer's number
    $text = $message['text']['body'];         // what they wrote

    // We process in the background to acknowledge Meta immediately.
    ProcessIncomingMessage::dispatch($from, $text);

    return response()->json(['status' => 'received']);
}

If queues still feel out of reach, the Laravel performance optimization guide covers when and how to move work to queues.

Step 5: The agent’s brain

All of the AI logic lives in a dedicated service. It does three things: it fetches the recent history, builds the prompt, and calls OpenAI.

<?php

namespace App\Services;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use OpenAI;

class WhatsAppAgent
{
    public function reply(string $from, string $userText): string
    {
        // 1. Fetch the thread of the latest messages (short-term memory).
        $history = Cache::get("wa:history:{$from}", []);

        // 2. Build the prompt: persona + catalog + conversation.
        $messages = array_merge(
            [['role' => 'system', 'content' => $this->systemPrompt()]],
            $history,
            [['role' => 'user', 'content' => $userText]],
        );

        // 3. Call OpenAI.
        $client = OpenAI::client(config('services.openai.api_key'));

        $result = $client->chat()->create([
            'model' => 'gpt-4o-mini',
            'temperature' => 0.4,
            'messages' => $messages,
        ]);

        $answer = $result->choices[0]->message->content;

        // 4. Store the exchange for the next round (TTL 24 h).
        $history[] = ['role' => 'user', 'content' => $userText];
        $history[] = ['role' => 'assistant', 'content' => $answer];
        Cache::put("wa:history:{$from}", array_slice($history, -12), now()->addDay());

        return $answer;
    }

    private function systemPrompt(): string
    {
        return <<<PROMPT
        You are the sales assistant for "Muebles Roble", a small Mexican business.
        Tone: friendly, clear, and brief (no more than 3 sentences per reply).
        Your goal: answer questions, suggest products, and schedule visits.
        If they ask for something not in the catalog or a special discount, reply
        that a human advisor will confirm it and ask for their name and city.
        Never make up prices or stock that aren't in the catalog.
        PROMPT;
    }
}

Three details that make the difference between a decent bot and one that sells:

  1. Per-conversation memory. Without history, the agent forgets what the customer said two messages ago. Here we use Cache for simplicity; in production, store it in a conversations table so you have traceability.
  2. Low temperature (0.3–0.5). In customer support you want consistent replies, not creativity.
  3. The system prompt is your product. That’s where you define the personality, the limits (“never make up prices”), and when to escalate to a human. Invest time in it: it’s what the customer perceives as “your brand.”

Step 6: Send the reply to WhatsApp

The queued job ties the pieces together: it asks the agent for the reply and sends it via the Graph API.

<?php

namespace App\Jobs;

use App\Services\WhatsAppAgent;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Facades\Http;

class ProcessIncomingMessage implements ShouldQueue
{
    use Dispatchable, Queueable;

    public function __construct(
        public string $from,
        public string $text,
    ) {}

    public function handle(WhatsAppAgent $agent): void
    {
        $answer = $agent->reply($this->from, $this->text);

        $config = config('services.whatsapp');

        Http::withToken($config['token'])
            ->post("https://graph.facebook.com/{$config['version']}/{$config['phone_number_id']}/messages", [
                'messaging_product' => 'whatsapp',
                'to' => $this->from,
                'type' => 'text',
                'text' => ['body' => $answer],
            ])
            ->throw();
    }
}

With this you’ve got the full loop: the customer writes, OpenAI drafts, and your WhatsApp number replies. Try it by sending a message to your test number — you should see the reply within seconds.

Step 7: Connect your catalog and CRM

An agent that only “chats” doesn’t sell. The leap in quality comes from giving it access to real data. Two approaches depending on your volume:

  • Small catalog (a few dozen products): inject it straight into the system prompt as a list. Simple and good enough for most small businesses.
  • Large catalog or changing stock: use function calling. You define a buscar_producto function and let the model invoke it when needed, querying your database in real time.
$result = $client->chat()->create([
    'model' => 'gpt-4o-mini',
    'messages' => $messages,
    'tools' => [[
        'type' => 'function',
        'function' => [
            'name' => 'buscar_producto',
            'description' => 'Searches available products by name or category.',
            'parameters' => [
                'type' => 'object',
                'properties' => [
                    'query' => ['type' => 'string', 'description' => 'Text to search for'],
                ],
                'required' => ['query'],
            ],
        ],
    ]],
]);

// If the model decides to call the function, we query the DB and
// return the result so it can write the final reply.

This pattern — exposing your business logic as tools the model can invoke — is exactly the idea behind MCP. If you want to take it to the next level, check out how to build an AI agent with Laravel and MCP, where the model executes real actions across your systems using an open standard.

To record every qualified lead in your CRM, all you need to do is persist the conversation and fire an event when the agent detects purchase intent.

Meta rules you must follow (2026)

WhatsApp is not a free-for-all broadcast channel. Ignore these and they’ll suspend your number:

  • 24-hour window. You can only respond with free-form messages within 24 hours of the customer’s last message. Outside that window you need a template approved by Meta.
  • Mandatory opt-in. The user must have initiated contact or agreed to receive messages. No purchased lists.
  • Number quality. Meta scores your number based on blocks and reports. An agent that spams lowers your quality rating and limits how many messages you can send per day.
  • Transparency. In several countries it’s good practice (and sometimes a requirement) to disclose that this is an automated assistant and to offer a handoff to a human.

Treat these rules as part of the design, not as a final formality. If you handle customers’ personal data, also take a look at passwordless authentication in Laravel for modern identity flows.

Custom-built or a chatbot SaaS?

The inevitable question. The honest answer depends on how standard your operation is:

Chatbot SaaSCustom agent (this tutorial)
Time to launchMinutesDays
Monthly costFixed per agent/conversationJust the OpenAI API + your hosting
CustomizationLimited to what the dashboard allowsTotal (catalog, CRM, your own logic)
DataOn the provider’s serverOn your own infrastructure
Business logicHard to integrateNative to your Laravel app

A SaaS is perfect for validating the idea this week. But the moment you need the agent to know your inventory, write to your system, and follow rules that only exist in your business, the cost of fighting a closed dashboard outweighs building it custom — and you stay the owner of your data.

Frequently asked questions

Do I need the official API, or can I use an unofficial library? Always use the official WhatsApp Cloud API. Libraries that automate WhatsApp Web violate Meta’s terms and end with the number banned. The Cloud API has a generous free tier to get started.

How much does it cost to run the agent? Two costs: WhatsApp messages (Meta charges per conversation depending on the country, with a free monthly volume) and the OpenAI API. With gpt-4o-mini each reply costs fractions of a cent, so for a small business the bulk of the spend is usually WhatsApp, not the AI.

Can the agent hand the conversation off to a human? Yes, and it should be able to. The standard practice is to detect intent (“I want to talk to someone,” or a hot lead) and flag the conversation so an advisor can pick it up, muting the bot on that thread.

Does it work with models other than OpenAI? Yes. The architecture is the same; you only swap the client in the service. You can use Claude, Gemini, or a self-hosted open source model without touching the rest of the WhatsApp flow.

Conclusion

An AI agent on WhatsApp stops being a luxury once you realize that your next customer’s first message has probably already arrived, and nobody answered it in time. With Laravel orchestrating the Cloud API and OpenAI drafting, you have an assistant that qualifies, answers, and sells while you do something else — and, unlike a closed SaaS, it knows your catalog and lives on your infrastructure.

If you want to keep building:

What would be the first thing you’d let your agent answer for you on WhatsApp?

Compartir

Search

Tags