Claude API WhatsApp Business Integration Guide
Can you connect Claude to WhatsApp Business?
Yes. Claude has no native WhatsApp connector, but Anthropic's models work well behind WhatsApp Business because the integration pattern is simple: WhatsApp sends you a webhook when a user messages your business number, your server calls Claude with that message, and you send Claude's reply back through the WhatsApp Cloud API. There's no official bridge between the two — you build a small relay service that sits in the middle.
This guide walks through that relay: registering a WhatsApp Business number, handling inbound webhooks, calling Claude for a response, and sending the reply back, including how to manage conversation history and tool calls so the bot feels coherent across a multi-turn chat.
The architecture
WhatsApp user → Meta Cloud API webhook → your server → Claude API → your server → Meta Cloud API → WhatsApp user
You need three pieces:
- A WhatsApp Business Platform account (via Meta's Cloud API, the free-tier option most developers use instead of self-hosting the On-Premises API).
- A webhook endpoint that receives incoming messages and sends outgoing ones.
- A Claude API client that turns the incoming text into a prompt, keeps track of conversation state, and returns a reply.
Step 1: Set up the WhatsApp Cloud API
- Create a Meta Business app at developers.facebook.com and add the WhatsApp product.
- Get a test phone number (or connect your verified business number) and note the
Phone Number IDand a temporary access token. - Configure a webhook URL pointing at your server, with a verify token you choose yourself.
- Subscribe to the
messagesfield so Meta sends you a POST request every time a user writes to your number.
A minimal webhook verification handler:
app.get("/webhook", (req, res) => {
const { "hub.mode": mode, "hub.verify_token": token, "hub.challenge": challenge } = req.query;
if (mode === "subscribe" && token === process.env.WHATSAPP_VERIFY_TOKEN) {
res.send(challenge);
} else {
res.sendStatus(403);
}
});
Step 2: Receive and parse inbound messages
Meta's webhook payload nests the actual message text a few levels deep. Extract the sender's phone number and text body:
app.post("/webhook", async (req, res) => {
const entry = req.body.entry?.[0];
const change = entry?.changes?.[0]?.value;
const message = change?.messages?.[0];
if (message?.type === "text") {
const from = message.from; // user's WhatsApp number
const text = message.text.body; // user's message
await handleIncomingMessage(from, text);
}
res.sendStatus(200); // acknowledge immediately, Meta expects a fast 200
});
Always return a 200 quickly — WhatsApp retries if you're slow, which can duplicate processing. Do the Claude call asynchronously if your response times are inconsistent.
Step 3: Call Claude for a reply
This is where most of the actual "integration" work happens: deciding how to format the prompt, how much history to keep, and how to call the model reliably. Using SubToAPI as the Claude access layer keeps this part simple because you get a standard HTTPS endpoint, an application API key, and usage metadata without separately managing provider credentials or billing per model.
async function getClaudeReply(from, text) {
const history = getConversationHistory(from); // your own storage, keyed by phone number
const response = 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-5",
max_tokens: 500,
messages: [...history, { role: "user", content: text }]
})
});
const data = await response.json();
return data.content[0].text;
}
Keep max_tokens conservative — WhatsApp messages are short-form by nature, and long Claude replies get awkward when rendered as chat bubbles. If you need Claude to call external functions (checking order status, booking a slot, looking up inventory) before replying, the same endpoint supports tool use; see /docs/tools for the request shape.
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_ACCESS_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
messaging_product: "whatsapp",
to,
type: "text",
text: { body: text }
})
}
);
}
Chain it together: handleIncomingMessage calls getClaudeReply, then sendWhatsAppMessage, then persists both turns to history.
Managing conversation state per contact
WhatsApp doesn't give you sessions — every webhook is a standalone HTTP call. You're responsible for mapping from (the phone number) to a stored conversation. A simple key-value store (Redis, a Postgres table, even a JSON file for a prototype) keyed by phone number works fine. Trim history to the last 10–15 turns to control token usage and latency; Claude doesn't need the full chat history to stay coherent in a support or sales context.
Streaming isn't useful here — but latency still matters
WhatsApp doesn't support partial message streaming the way a web chat UI does, so you don't need Claude's streaming mode for this integration (that's more relevant for /docs/streaming use cases like live chat widgets). What matters instead is total round-trip time: users expect a WhatsApp reply within a few seconds. Keep prompts tight, cap max_tokens, and avoid chaining multiple Claude calls per incoming message unless you're doing something like a tool-use lookup that genuinely requires it.
Production considerations
- Rate limits: Meta enforces per-number messaging limits that scale with your business verification tier. Claude-side limits are separate — check your plan's throughput before launching a high-volume bot.
- 24-hour session window: WhatsApp only lets businesses send free-form messages within 24 hours of the user's last message; outside that window you need pre-approved template messages, which is a WhatsApp policy constraint, not a Claude one.
- Multiple team members: if more than one person maintains the bot or you're running several WhatsApp numbers for different business units, a seat-based plan like /pricing keeps API keys and usage visibility separate per person instead of sharing one secret key.
- Getting started fast: if you're prototyping before wiring up Meta's full verification flow, test the Claude side independently first — see /docs/quickstart to confirm your prompts and response times work before adding WhatsApp's webhook complexity on top.
Questions
Do I need Anthropic API access directly, or can I use a wrapper? Either works. A direct Anthropic key is fine for a single project; a hosted layer like SubToAPI (/signup) gives you a standard REST endpoint, per-key usage tracking, and team seats if more than one person manages the bot.
Can Claude send images or documents through WhatsApp? Claude can generate text describing content, but actually sending media requires WhatsApp's media message types (image, document) — you'd upload the file to Meta's media endpoint separately and reference it in the outgoing message payload.
How do I handle users texting outside the 24-hour window? You must send a pre-approved WhatsApp template message to re-open the conversation; once the user replies, free-form messages (including Claude-generated ones) work again until the window closes.