Claude API WhatsApp Bot Integration Tutorial
Building a WhatsApp bot on top of the Claude API means connecting three pieces: WhatsApp's Cloud API (to send and receive messages), a webhook server you control (to relay those messages), and Claude (to generate the replies). This tutorial walks through that pipeline end to end, with working code you can deploy today.
If you're searching for "claude api whatsapp bot integration tutorial," you're probably trying to figure out how messages actually flow between WhatsApp and Claude, since neither service talks to the other directly. The short answer: WhatsApp sends incoming messages to a webhook URL you host, your server calls the Claude API to generate a response, and then your server calls the WhatsApp API to send that response back. Everything below explains how to wire that up correctly, including the parts people usually get wrong — webhook verification, message formatting, and handling Claude's response format for a channel that doesn't support streaming.
Architecture overview
The flow looks like this:
- A user sends a WhatsApp message to your business number.
- Meta's WhatsApp Cloud API POSTs the message payload to your webhook endpoint.
- Your server extracts the text, sends it to Claude, and waits for a complete response.
- Your server calls the WhatsApp Cloud API to send that response back to the user.
WhatsApp messages are not streamed to the end user the way a chat UI would stream tokens, so you'll call Claude in non-streaming mode and send one finished message per turn.
Prerequisites
- A Meta Developer account with a WhatsApp Business App configured (test number is fine for development)
- A publicly reachable HTTPS endpoint for your webhook (ngrok works for local testing)
- Claude API access — either directly through Anthropic or through a proxy like SubToAPI if you want a single HTTPS endpoint, app-scoped API keys, and usage tracking without managing Anthropic billing yourself
Step 1: Set up the webhook endpoint
Meta requires a verification handshake before it will send you messages. Your endpoint needs to respond to a GET request with a challenge token:
const express = require("express");
const app = express();
app.use(express.json());
const VERIFY_TOKEN = process.env.WHATSAPP_VERIFY_TOKEN;
app.get("/webhook", (req, res) => {
const mode = req.query["hub.mode"];
const token = req.query["hub.verify_token"];
const challenge = req.query["hub.challenge"];
if (mode === "subscribe" && token === VERIFY_TOKEN) {
return res.status(200).send(challenge);
}
res.sendStatus(403);
});
Register this URL in the Meta App dashboard under WhatsApp > Configuration > Webhook, and subscribe to the messages field.
Step 2: Receive and parse incoming messages
Once verified, Meta sends a POST request for every incoming message:
app.post("/webhook", async (req, res) => {
res.sendStatus(200); // acknowledge immediately
const entry = req.body.entry?.[0];
const change = entry?.changes?.[0];
const message = change?.value?.messages?.[0];
if (!message || message.type !== "text") return;
const from = message.from; // user's phone number
const text = message.text.body;
const reply = await getClaudeReply(text);
await sendWhatsAppMessage(from, reply);
});
Acknowledge the webhook right away (within a few seconds) — Meta will retry if you don't, which can lead to duplicate replies if your Claude call is slow.
Step 3: Call Claude for a response
This is a standard request/response call. If you're using SubToAPI, the request looks like this:
async function getClaudeReply(userText) {
const res = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-sonnet-4",
max_tokens: 500,
messages: [{ role: "user", content: userText }],
}),
});
const data = await res.json();
return data.content[0].text;
}
Keep max_tokens reasonable — WhatsApp messages are read on a phone screen, and long replies hurt the user experience. 300–500 tokens is a sensible ceiling for a chat bot. See the messages docs for the full request schema if you need system prompts or multi-turn context.
Step 4: Send the reply back through WhatsApp
async function sendWhatsAppMessage(to, text) {
await fetch(
`https://graph.facebook.com/v19.0/${process.env.WHATSAPP_PHONE_ID}/messages`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WHATSAPP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
messaging_product: "whatsapp",
to,
text: { body: text },
}),
}
);
}
That's the full loop. Test it by messaging your WhatsApp test number and watching the server logs.
Handling multi-turn conversations
WhatsApp doesn't give you conversation state — each webhook call is a single message. If you want Claude to remember earlier turns, you need to store conversation history keyed by the user's phone number (Redis, a database table, or even an in-memory map for a prototype) and pass it as the messages array on each request. Trim or summarize history periodically to stay within context limits and to control token costs.
Rate limits and error handling
Claude API calls can fail or get rate limited, and WhatsApp's own API has its own throttling. Wrap your Claude call in a try/catch and send a fallback message ("Give me a second, try again shortly") instead of letting the webhook hang or silently drop the user's message:
try {
const reply = await getClaudeReply(text);
await sendWhatsAppMessage(from, reply);
} catch (err) {
await sendWhatsAppMessage(from, "Sorry, something went wrong. Try again in a moment.");
}
Running this for a team or multiple bots
If more than one person is building or operating the bot, or if you're running several WhatsApp numbers against the same Claude backend, managing raw Anthropic credentials per environment gets messy fast. SubToAPI issues per-application sub_live_... keys so each bot (production, staging, a client's instance) has its own key, usage metadata, and billing visibility, with team seats if multiple developers need access. Check pricing for the Solo, Team, and Scale tiers, or start with the quickstart to get a key in a few minutes.
questions
Do I need Anthropic's API directly, or can I use a proxy? Either works. Anthropic's API works fine for a single developer; a proxy like SubToAPI adds app-scoped keys, usage tracking, and team seats if multiple people or environments need access to the same Claude backend.
Can WhatsApp display streamed Claude responses token by token? No. The WhatsApp Cloud API only accepts complete message payloads, so you should call Claude without streaming and send the finished text in one message.
How do I keep conversation context across multiple WhatsApp messages? Store a short message history per phone number in a database or cache, and include it in the messages array on each Claude API call, trimming older turns to control context size and cost.