# How to build a WhatsApp AI bot with Claude, GPT or Gemini

> Connect the official WhatsApp Cloud API to Claude, GPT or Gemini with a small Node.js server. Tested code, Meta's rules, safety limits and costs.

Updated: 2026-11-01

To build a WhatsApp AI bot, connect Meta's official WhatsApp Cloud API to a small server of your own. WhatsApp sends each customer message to the server, which asks the model for a reply and sends it back through the Cloud API. With a 5abeer key, the OpenAI SDK reaches Claude, GPT and Gemini models below official prices.

## What do you need to build a WhatsApp AI bot?

- A Meta developer app with the WhatsApp product, which comes with a test business number.
- A 5abeer API key for the bot's provider. The code uses the default model, [`claude-sonnet-5.5`](https://5abeer.ai/en/models/claude-sonnet-5.5), so it needs a Claude key. See [Create a key](https://5abeer.ai/en/docs/start/keys.md#create) and [How to get an API key for Claude, GPT and Gemini](https://5abeer.ai/en/guides/ai-api-key-guide).
- Node.js 22 or later, with the `express` and `openai` packages.
- A server that stays on, with a public HTTPS address. Meta asks for "a valid TLS or SSL certificate", and "Self-signed certificates are not supported."

## Which way should you connect to WhatsApp?

The three common ways, checked on 2026-11-01:

| Way | What it is | For a business bot |
| --- | --- | --- |
| WhatsApp Cloud API | Meta's own API for business numbers, used here | Yes: official, and the code is yours |
| A Business Solution Provider | A company that connects you to the platform, with its own tools | Yes, for a shared inbox or setup help; check its fees |
| An unofficial WhatsApp Web library | Code that logs in as an ordinary account and automates it | No: it breaks WhatsApp's terms |

WhatsApp's Terms of Service forbid "bulk messaging, auto-messaging, auto-dialing" and "any non-personal use of our Services unless otherwise authorized by us", so a bot on an ordinary account puts the number at risk.

## Does Meta allow AI bots on WhatsApp?

Yes, for a business answering its own customers. Meta's terms for the WhatsApp Business Platform, updated on September 23, 2026, bar AI providers when AI is "the primary (rather than incidental or ancillary) functionality" of what they offer. Meta's AI providers page lets AI providers offer general-purpose AI assistants only "where Meta is legally required to permit this use case."

A store bot answering questions about its own orders and products uses AI as part of its customer service, which is the case here. The terms also limit using your WhatsApp data to train AI systems.

5abeer is only the model API your server calls. It doesn't connect to WhatsApp or to your Meta account. This is general information, not legal advice, so read Meta's terms before you launch.

## How do you connect the server to Claude, GPT or Gemini?

5abeer speaks the OpenAI Chat Completions format, so the server uses the official OpenAI SDK with a different base URL and key. The same setup runs [a website chatbot](https://5abeer.ai/en/guides/website-chatbot-api) and [a Telegram AI bot](https://5abeer.ai/en/guides/telegram-bot-ai-api).

| Format | Base URL | Key header |
| --- | --- | --- |
| OpenAI | `https://api.5abeer.ai/v1` | `Authorization: Bearer <KEY>` |
| Anthropic | `https://api.5abeer.ai` | `x-api-key: <KEY>` |
| Gemini | `https://api.5abeer.ai/v1beta` | `x-goog-api-key: <KEY>` |

Send one test request first to check the key and the model. The format is in [Chat Completions](https://5abeer.ai/en/docs/api/chat-completions.md#quick).

```bash
curl --request POST "https://api.5abeer.ai/v1/chat/completions" \
  --header "Authorization: Bearer $KHABEER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"model":"claude-sonnet-5.5","messages":[{"role":"user","content":"Write a product idea in one sentence."}]}'
```

```python
import os
import requests

response = requests.post(
    "https://api.5abeer.ai/v1/chat/completions",
    headers={
        "Authorization": f"Bearer {os.environ['KHABEER_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
    "model": "claude-sonnet-5.5",
    "messages": [
        {
            "role": "user",
            "content": "Write a product idea in one sentence."
        }
    ]
},
    stream=False,
    timeout=60,
)

response.raise_for_status()
print(response.json())
```

## What does the server code look like?

The bot is one file. Meta checks your address with `GET /webhook` and delivers messages with `POST /webhook`.

```javascript
import crypto from "node:crypto";
import express from "express";
import OpenAI from "openai";

const env = process.env;
const missing = ["WHATSAPP_TOKEN", "WHATSAPP_APP_SECRET", "WHATSAPP_VERIFY_TOKEN", "WHATSAPP_PHONE_NUMBER_ID", "KHABEER_API_KEY"].filter((name) => !env[name]);
if (missing.length) {
  console.error("Set these environment variables first:", missing.join(", "));
  process.exit(1);
}

const client = new OpenAI({ apiKey: env.KHABEER_API_KEY, baseURL: "https://api.5abeer.ai/v1" });
const MODEL = env.CHAT_MODEL || "claude-sonnet-5.5";
const GRAPH = `https://graph.facebook.com/v26.0/${env.WHATSAPP_PHONE_NUMBER_ID}/messages`;

// One business answering its own customers. Replace the example store's facts with yours.
const SYSTEM = `You are the WhatsApp assistant of Example Store.
Answer only questions about the store, in the customer's language, briefly and in plain text.
Opening hours: 9:00 to 21:00, Saturday to Thursday. Shipping takes 2 to 4 days.
If you don't know the answer, say so and suggest the contact page on the store's website.`;

const MAX_TURNS = 10; // the last 5 questions and answers per customer
const MAX_CHARS = 4000; // per customer message
const LIMIT = 20; // messages per customer every 10 minutes
const MEDIA = new Set(["audio", "document", "image", "sticker", "video"]);
const history = new Map(); // in memory: lost when the server restarts
const hits = new Map();
const seen = new Set();
const queues = new Map();
setInterval(() => hits.clear(), 10 * 60 * 1000).unref();

// Meta signs every POST with your app secret: X-Hub-Signature-256 is "sha256=" + the HMAC of the raw body.
function signed(req) {
  if (!Buffer.isBuffer(req.body)) return false;
  const expected = Buffer.from("sha256=" + crypto.createHmac("sha256", env.WHATSAPP_APP_SECRET).update(req.body).digest("hex"));
  const received = Buffer.from(req.get("x-hub-signature-256") ?? "");
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

async function send(customer, text) {
  // A WhatsApp text message holds at most 4,096 characters, so long answers go in parts.
  for (const part of text.match(/[\s\S]{1,4000}/gu)) {
    const res = await fetch(GRAPH, {
      method: "POST",
      headers: { Authorization: `Bearer ${env.WHATSAPP_TOKEN}`, "Content-Type": "application/json" },
      body: JSON.stringify({ messaging_product: "whatsapp", recipient_type: "individual", ...customer, type: "text", text: { body: part } }),
      signal: AbortSignal.timeout(15000),
    });
    if (!res.ok) throw new Error(`WhatsApp API ${res.status}: ${await res.text()}`);
  }
}

async function answer(id, customer, message, overLimit) {
  if (overLimit) return send(customer, "You've sent many messages. Please wait a few minutes, then try again.");
  if (MEDIA.has(message.type)) return send(customer, "Sorry, I can only read text messages.");
  if (message.type !== "text") return; // reactions, locations, system notices...
  const past = history.get(id) ?? [];
  const question = String(message.text?.body ?? "").slice(0, MAX_CHARS);
  let reply;
  try {
    const completion = await client.chat.completions.create({
      model: MODEL,
      messages: [{ role: "system", content: SYSTEM }, ...past, { role: "user", content: question }],
      max_tokens: 800,
    });
    reply = (completion.choices[0]?.message?.content ?? "").trim();
    if (!reply) throw new Error("empty answer");
  } catch (error) {
    console.error("model error:", error.message);
    return send(customer, "Sorry, I can't answer right now. Please try again in a minute.");
  }
  history.delete(id); // re-insert, so the oldest conversation comes first
  history.set(id, [...past, { role: "user", content: question }, { role: "assistant", content: reply }].slice(-MAX_TURNS));
  if (history.size > 10000) history.delete(history.keys().next().value);
  await send(customer, reply);
}

function receive(message) {
  if (seen.has(message.id)) return; // Meta retries failed deliveries, so a message can arrive twice
  seen.add(message.id);
  if (seen.size > 10000) seen.delete(seen.values().next().value);
  // Reply to the phone number, or to the business-scoped user ID when WhatsApp hides the number.
  const id = message.from_user_id ?? message.from;
  if (!id) return;
  const customer = message.from ? { to: message.from } : { recipient: message.from_user_id };
  const count = (hits.get(id) ?? 0) + 1;
  hits.set(id, count);
  if (count > LIMIT + 1) return; // stay silent: every reply is a message Meta may charge for
  // One queue per customer: answers go out in order, and each one sees the previous answer.
  const job = (queues.get(id) ?? Promise.resolve())
    .then(() => answer(id, customer, message, count > LIMIT))
    .catch((error) => console.error("reply error:", error.message))
    .finally(() => queues.get(id) === job && queues.delete(id));
  queues.set(id, job);
}

const app = express();

// Meta calls this once, when you save the callback URL in the App Dashboard.
app.get("/webhook", (req, res) => {
  const ok = req.query["hub.mode"] === "subscribe" && req.query["hub.verify_token"] === env.WHATSAPP_VERIFY_TOKEN;
  if (!ok) return res.sendStatus(403);
  res.type("text/plain").send(String(req.query["hub.challenge"]));
});

// Meta can batch up to 1,000 updates in one POST.
app.post("/webhook", express.raw({ type: "application/json", limit: "5mb" }), (req, res) => {
  if (!signed(req)) return res.sendStatus(401);
  let body;
  try {
    body = JSON.parse(req.body.toString("utf8"));
  } catch {
    return res.sendStatus(400);
  }
  res.sendStatus(200); // answer Meta first; the replies go out afterwards
  for (const entry of body?.entry ?? []) {
    for (const change of entry.changes ?? []) {
      if (change.value?.metadata?.phone_number_id !== env.WHATSAPP_PHONE_NUMBER_ID) continue;
      for (const message of change.value.messages ?? []) receive(message); // status updates have no "messages"
    }
  }
});

app.listen(env.PORT || 3000, () => console.log("WhatsApp bot running on port", env.PORT || 3000));
```

We tested this file on 2026-11-01 with Node.js 22, Express 5.3 and the `openai` SDK 7.47, against local stand-ins for the API and the WhatsApp Cloud API, with signed test webhooks and fake keys. It calls v26.0 of Meta's Graph API, the newest in Meta's changelog that day.

## How do you set up the Meta app?

1. In the Meta App Dashboard, create an app with the use case "Connect with customers through WhatsApp".
2. On the WhatsApp API Setup page, copy the phone number ID. You can add your business number later.
3. Copy the App Secret from your app's settings. The server uses it to check that each delivery comes from Meta.
4. The temporary token on API Setup expires quickly. For a lasting one, add a system user in Business Settings and generate a token with the `business_management`, `whatsapp_business_management` and `whatsapp_business_messaging` permissions.
5. Pick a long random string as your verify token.
6. With the server running, open App Dashboard > Use cases > Customize > Configuration, set the callback URL to your HTTPS address followed by `/webhook`, enter the verify token and subscribe to the `messages` field. Meta then calls the address and the server returns the challenge.

## How do you run it?

Run these commands in the folder with `server.mjs`, with your own values:

```bash
npm install express openai
export KHABEER_API_KEY="YOUR_5ABEER_API_KEY"
export WHATSAPP_TOKEN="YOUR_SYSTEM_USER_TOKEN"
export WHATSAPP_APP_SECRET="YOUR_APP_SECRET"
export WHATSAPP_VERIFY_TOKEN="YOUR_VERIFY_TOKEN"
export WHATSAPP_PHONE_NUMBER_ID="YOUR_PHONE_NUMBER_ID"
node server.mjs
```

```powershell
npm install express openai
$env:KHABEER_API_KEY="YOUR_5ABEER_API_KEY"
$env:WHATSAPP_TOKEN="YOUR_SYSTEM_USER_TOKEN"
$env:WHATSAPP_APP_SECRET="YOUR_APP_SECRET"
$env:WHATSAPP_VERIFY_TOKEN="YOUR_VERIFY_TOKEN"
$env:WHATSAPP_PHONE_NUMBER_ID="YOUR_PHONE_NUMBER_ID"
node server.mjs
```

Meta can't reach `localhost`, so to test on your computer, open an HTTPS tunnel to port 3000, for example with Cloudflare Tunnel, and use its address as the callback URL. To change the model or the port, set `CHAT_MODEL` or `PORT`.

## How does the code work?

- Signatures: Meta signs every `POST` with your App Secret in the `X-Hub-Signature-256` header. The server checks it against the raw body and rejects anything unsigned with a 401.
- Fast answer: the server returns 200 first, then replies. Meta resends updates that don't get a 200, so the server skips message ids it has already seen.
- Memory and order: each new question goes with the customer's last 5 questions and answers, and one queue per customer keeps answers in order. The memory resets on restart.
- Media: images, audio, video, documents and stickers get a note that the bot reads only text.
- Length: each customer message is cut to 4,000 characters. A WhatsApp text holds at most 4,096 characters, so longer answers go out in parts.
- Rate limit: each customer gets 20 messages every 10 minutes. The 21st gets one "please wait" note, and later ones get no reply.
- Errors: the SDK retries the model twice, then the customer gets an apology. `max_tokens: 800` caps Claude and Gemini answers, and GPT models ignore it, as in [Parameters](https://5abeer.ai/en/docs/api/chat-completions.md#params).
- Usernames: when WhatsApp hides a number, the server replies to the business-scoped user ID with `recipient`.

## When can the bot reply?

Meta's pricing page explains: "When a WhatsApp user messages you or calls you, a 24-hour timer called a customer service window starts." It adds: "Businesses can only send non-template messages to users when this window is open." This bot answers within seconds, inside the window. To message a customer first, you need a template that Meta approves.

## How much does a WhatsApp AI bot cost?

There are two bills: the model's tokens, charged to your 5abeer balance, and Meta's message charges. In this example, the bot answers 4,000 messages a month, at about 1,200 tokens in and 250 out each:

Example: 4,800,000 input tokens and 1,000,000 output tokens on Claude Sonnet 5.5 (`claude-sonnet-5.5`) cost $5.88 at our price against $19.60 at the official price (70% lower). Official price checked October 19, 2026.

The price per token for the default model:

Claude Sonnet 5.5 (`claude-sonnet-5.5`) per 1M tokens: input $0.60 (official $2), output $3 (official $10). Official price checked October 19, 2026.

Meta's pricing page, effective October 1, 2026, says:

- "Meta does not charge for messages from a WhatsApp user to a business."
- "Meta provides every business phone number with a free tier of 1,000 service messages per month." ([Pricing](https://developers.facebook.com/docs/whatsapp/pricing), checked 2026-11-01)
- Beyond that, Meta charges per service message, at the rates on its page, except "for eligible government and non-profit organizations".

Each part of a long answer counts as a message. To spend less on the model, pick a smaller one from the [models list](https://5abeer.ai/en/models), keep `SYSTEM` short or lower `MAX_TURNS` and `max_tokens`. Compare providers on the [API page](https://5abeer.ai/en/api) and the [Claude](https://5abeer.ai/en/api/claude), [OpenAI](https://5abeer.ai/en/api/openai) and [Gemini](https://5abeer.ai/en/api/gemini) API pages, and plans on the [pricing page](https://5abeer.ai/en/pricing).

Give the bot its own key with a spend limit, as in [Key controls](https://5abeer.ai/en/docs/start/keys.md#controls). At the limit, requests fail with [`API_KEY_QUOTA_EXHAUSTED`](https://5abeer.ai/en/docs/help/errors.md#API_KEY_QUOTA_EXHAUSTED) and customers get the apology, so a busy day or abuse can't spend more than you set.

## How do you keep the bot safe?

- Keep the 5abeer key, the WhatsApp token and the App Secret in environment variables on the server, never in code or a repository. If one leaks, replace it.
- Keep the signature check. Without it, anyone who finds the address can send fake messages and spend your balance.
- If the server has a fixed address, add an IP allow-list to its key, as in [Key controls](https://5abeer.ai/en/docs/start/keys.md#controls).
- Keep secrets and other customers' data out of `SYSTEM`, since customers can try to talk the assistant out of its instructions.
- Tell customers that an AI assistant answers them, and give them a way to reach a person.

## FAQ

### Can I use a WhatsApp Web library instead of the Cloud API?

No. Those libraries automate an ordinary account, which WhatsApp's terms forbid, and the number can be suspended.

### Can the bot use GPT or Gemini instead of Claude?

Yes. Set `CHAT_MODEL` to a model id from the [models list](https://5abeer.ai/en/models) and use a key for that model's provider. Each key covers one provider, so a GPT model with a Claude key returns [`MODEL_PLATFORM_NOT_ALLOWED`](https://5abeer.ai/en/docs/help/errors.md#MODEL_PLATFORM_NOT_ALLOWED).

### Can it answer voice notes or images?

Not as written. To add them, download the media through the Cloud API and send it to a model that accepts that input, or turn the voice note into text first.

### What happens when my balance runs out?

Requests fail with [`INSUFFICIENT_BALANCE`](https://5abeer.ai/en/docs/help/errors.md#INSUFFICIENT_BALANCE), and customers get the apology until you top up. Meta's charges are separate, on your Meta account.

### How do I pay for the API?

You top up your 5abeer balance with any method in [Payment methods](https://5abeer.ai/en/docs/start/payment.md#methods), and the bot's API calls draw from that balance.

### Is 5abeer part of Meta, OpenAI, Anthropic or Google?

No. 5abeer is an independent service, not affiliated with any of them.

## Sources

Checked on 2026-11-01:

- [WhatsApp Cloud API: Get started](https://developers.facebook.com/docs/whatsapp/cloud-api/get-started)
- [WhatsApp Cloud API: Text messages](https://developers.facebook.com/docs/whatsapp/cloud-api/messages/text-messages)
- [Meta Webhooks: Getting started](https://developers.facebook.com/docs/graph-api/webhooks/getting-started)
- [WhatsApp Cloud API: Set up webhooks](https://developers.facebook.com/docs/whatsapp/cloud-api/guides/set-up-webhooks)
- [Business-scoped user IDs](https://developers.facebook.com/docs/whatsapp/business-scoped-user-ids)
- [Access tokens](https://developers.facebook.com/docs/whatsapp/access-tokens)
- [Pricing on the WhatsApp Business Platform](https://developers.facebook.com/docs/whatsapp/pricing)
- [Meta Terms for WhatsApp Business Platform](https://www.facebook.com/legal/Meta-Terms-for-WhatsApp-Business-Platform) and [AI Providers](https://developers.facebook.com/documentation/business-messaging/whatsapp/pricing/ai-providers)
- [WhatsApp Terms of Service](https://www.whatsapp.com/legal/terms-of-service)
- [Graph API changelog](https://developers.facebook.com/docs/graph-api/changelog)

> 5abeer is an independent service, not affiliated with OpenAI, Anthropic or Google.

## Get started

Create a Claude key, put it in `KHABEER_API_KEY` and connect your WhatsApp number.
