Claude API WhatsApp Integration Guide
Connecting the Claude API to WhatsApp means wiring together three things: the WhatsApp Business API (which receives and sends messages), a webhook server you control, and a call to Claude that generates the reply text. There's no native "WhatsApp connector" shipped by Anthropic — you build a thin relay layer between Meta's messaging infrastructure and Claude's completion API.
This guide walks through that relay layer end to end: registering a WhatsApp Business number, building a webhook that receives incoming messages, calling Claude to generate a response, and sending that response back through WhatsApp. It also covers the parts people usually get wrong — webhook verification, conversation context, and per-user rate limiting.
Architecture Overview
The flow looks like this:
- A user sends a WhatsApp message to your business number.
- Meta's WhatsApp Cloud API POSTs that message to your webhook URL.
- Your server extracts the message text, builds a prompt (optionally with prior conversation history), and calls the Claude API.
- Claude returns a response.
- Your server sends that response back to the user via the WhatsApp Cloud API's "send message" endpoint.
There's no persistent connection and no streaming to the end user — WhatsApp messages are discrete request/response pairs, so you wait for Claude's full completion before replying.
Prerequisites
- A Meta for Developers account with a WhatsApp Business app created
- A verified WhatsApp Business phone number (test numbers work fine for development)
- A publicly reachable HTTPS endpoint (ngrok for local testing, a real domain for production)
- Claude API access — either directly through Anthropic or through a gateway like SubToAPI if you want a single API key with usage tracking and team billing instead of managing Anthropic console access per developer
Step 1: Set Up the WhatsApp Business API
In the Meta for Developers dashboard, create a WhatsApp product under your app, note the Phone Number ID and generate a temporary (or permanent, via a system user) access token. You'll also set a webhook verify token — an arbitrary string Meta uses to confirm you own the endpoint.
Meta will send a GET request to your webhook URL during setup:
GET /webhook?hub.mode=subscribe&hub.verify_token=YOUR_TOKEN&hub.challenge=12345
Your server must echo back hub.challenge if the token matches:
app.get("/webhook", (req, res) => {
const { "hub.mode": mode, "hub.verify_token": token, "hub.challenge": challenge } = req.query;
if (mode === "subscribe" && token === process.env.WA_VERIFY_TOKEN) {
return res.status(200).send(challenge);
}
res.sendStatus(403);
});
Step 2: Receive Incoming Messages
Incoming messages arrive as POST requests to the same webhook path. The payload is nested, so extract the text and sender ID carefully:
app.post("/webhook", async (req, res) => {
const entry = req.body.entry?.[0];
const message = entry?.changes?.[0]?.value?.messages?.[0];
if (message?.type === "text") {
const from = message.from; // sender's WhatsApp number
const text = message.text.body; // message content
await handleIncomingMessage(from, text);
}
res.sendStatus(200); // acknowledge immediately, respond to WhatsApp async
});
Always return a 200 quickly — Meta retries if your webhook doesn't acknowledge within a few seconds, which can cause duplicate processing if your Claude call is slow.
Step 3: Call Claude and Generate a Reply
Once you have the message text, send it to Claude. If you're using SubToAPI, the call looks like this:
async function handleIncomingMessage(from, text) {
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-3-5-sonnet-20241022",
max_tokens: 500,
messages: [{ role: "user", content: text }]
})
});
const data = await response.json();
const reply = data.content[0].text;
await sendWhatsAppMessage(from, reply);
}
The /v1/messages endpoint mirrors the standard message format — see /docs/messages for the full schema, and /docs/quickstart if this is your first integration.
Step 4: Send the Reply Back Through WhatsApp
async function sendWhatsAppMessage(to, text) {
await fetch(`https://graph.facebook.com/v19.0/${process.env.WA_PHONE_ID}/messages`, {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.WA_ACCESS_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
messaging_product: "whatsapp",
to,
text: { body: text }
})
});
}
That's a working loop: message in, Claude response out.
Handling Conversation History
WhatsApp doesn't track conversation state for you. For multi-turn context, store recent messages per phone number (Redis or a simple database table works fine) and replay them as the messages array on each call:
const history = await getHistory(from); // array of { role, content }
history.push({ role: "user", content: text });
const data = await callClaude(history);
history.push({ role: "assistant", content: data.content[0].text });
await saveHistory(from, history.slice(-20)); // cap history length
Trim history aggressively — WhatsApp conversations run long and token costs add up fast if you replay the full thread every message.
Production Considerations
- Rate limiting per user: throttle requests per WhatsApp number to avoid one user burning through your Claude quota.
- Media messages: images and voice notes need separate handling (download the media, transcribe or describe it, then feed that into the prompt).
- Tool use: if your bot needs to look things up (order status, inventory, bookings) rather than just chat, structure the integration around Claude's tool calling — see
/docs/toolsfor the request format. - Monitoring: track latency and error rates per message; WhatsApp users expect replies within a few seconds, and Claude calls that exceed that will feel broken even if they eventually succeed.
- Billing visibility: if multiple team members or environments hit the same bot, a dashboard showing usage per key avoids surprise costs — this is one of the reasons teams route through something like SubToAPI rather than a single shared Anthropic key. Check
/pricingfor plan details and/signupto get a key.
questions
Does Claude have a built-in WhatsApp integration? No. Anthropic doesn't offer a native WhatsApp connector. You build a webhook server that receives messages from the WhatsApp Business API and forwards them to Claude's messages endpoint, then sends the response back through WhatsApp's send-message API.
Can I stream Claude's response into WhatsApp? No — WhatsApp messages are sent as complete units, not token-by-token. You wait for Claude's full response before calling the send-message API. Streaming (see /docs/streaming) is useful for web or app UIs, not WhatsApp.
How do I handle multiple users chatting at once? Key your conversation state (history, rate limits) by WhatsApp phone number, not globally. Each incoming webhook payload includes the sender's number — use it to look up and update that user's context independently.