Skip to content

Ask the docs

Answers come only from these docs and are written by an AI model (Gemini), so check the linked pages. Your question is not kept. Do not include personal details or keys.

WhatsApp

WhatsApp · WhatsApp API for AI agents · WhatsApp AI agent · send WhatsApp message from LLM agent · WhatsApp chatbot API · WhatsApp Business API AI · WhatsApp LLM integration · connect AI agent to WhatsApp

Connect your number, send replies and handle templates outside the reply window.

Connect your own WhatsApp number with a live key, receive messages and deliver your agent’s answer through the Unified API. Use buttons in the reply window and approved templates after it.

TypeScript
// app/api/whatsapp/route.ts (Next.js). npm install @flow-engineer/messaging ai @ai-sdk/openai
import { FlowMessaging, contentText } from "@flow-engineer/messaging";
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
const flow = new FlowMessaging();
export const POST = flow.webhooks.handler({
onEvent: async (event) => {
if (event.type !== "message.received") return;
const question = contentText(event.data.message.content); // text, caption or a tapped button's label
// Answer after the webhook returns: reply() keeps typing on and sends bubbles as they are ready.
void event.conversation.reply(
streamText({ model: openai("gpt-4.1-mini"), system: "You are the assistant for Asha's Bakery.", prompt: question }),
{ idempotencyKey: event.id },
);
},
});

There is no WhatsApp sandbox yet. Run the same code on the Telegram sandbox bot:

  1. Get a test key, with no account: curl -X POST https://api.flow.engineer/v0/sandbox/keys (or npx @flow-engineer/messaging init). Set its key as FLOW_MESSAGING_KEY. See Keys and sign-in.
  2. Join the Telegram sandbox from your phone with the bot’s address.link (see Build a Telegram agent).
  3. Register your webhook, or on your laptop read events from the live stream (GET /v0/stream, see Local development), and send a message.

When the WhatsApp sandbox opens, the sandbox allowance will cover it.

2. Answer fast: reply in the webhook response

Section titled “2. Answer fast: reply in the webhook response”

For short answers, skip the extra API call and return the reply from the webhook handler:

TypeScript
export const POST = flow.webhooks.handler({
onEvent: async (event) => {
if (event.type !== "message.received") return;
return await answer(contentText(event.data.message.content)); // a string, a kind object, or a list of up to 11
},
});
HTTP response body
{ "reply": { "kind": "basic", "text": "Yes, we deliver to 560103. Delivery takes about 40 minutes." } }

Answer within 10 seconds; for anything slower, answer 200 {} at once and send with the kinds’ endpoints, such as POST /v0/conversations/{conversation_id}/basic.

WhatsApp users send a lot of voice notes. Each one arrives as a voice message with the audio file (url, file_id):

{ "kind": "voice", "url": "https://api.flow.engineer/v0/files/file_01JB8ZC3K5M7P9R1T3V5X7Z9B1", "duration_seconds": 4.2 }

Flow does not transcribe voice notes yet, so fetch the audio and transcribe it yourself if your agent needs the words. To send a voice note back, use POST /v0/conversations/{conversation_id}/voice with an audio url or file_id.

WhatsApp shows up to 3 reply buttons (buttons). For 4 to 10 choices, send a list: more than 3 buttons answer 422 unsupported_content with param buttons, and Flow does not turn them into a list for you.

TypeScript
import { buttons } from "@flow-engineer/messaging";
await event.conversation.send(buttons("How would you like to pay?", [
{ id: "upi", label: "UPI" },
{ id: "card", label: "Card" },
{ id: "cod", label: "Cash on delivery" },
]));

A tap on a button or a list row arrives as message.received with a button_reply message: { "kind": "button_reply", "button_id": "upi", "label": "UPI" }. Labels are at most 20 characters.

WhatsApp allows free-form messages only within 24 hours of the person’s last message. After that, and to message someone first, you must send an approved template.

  • Every event carries conversation.window_open_until.
  • A free-form send outside the window fails with 409 outside_window.
  • Subscribe to conversation.window_closing to get a warning 1 hour before it closes.
TypeScript
import { template } from "@flow-engineer/messaging";
// Follow up two days later: only a template may go.
await flow.conversation(conversationId).send(
template("tpl_01JB8ZC3K5M7P9R1T3V5X7Z9B1", "en", { body: ["Asha", "order 1042"] }),
);

Templates are created on your dedicated WhatsApp number with POST /v0/templates, with a realistic sample for each placeholder (examples), and reviewed by WhatsApp; you receive template.status_changed when one is approved, rejected or paused. See Going live.

KindWhatsApp
Text (basic)yes, sent as written; WhatsApp’s app shows its own *bold* and _italic_
Images, video, documents, audioyes
Voice notesyes, both ways
Buttons1 to 3 reply buttons, or one URL button; a list for up to 10 rows
Reactions, read receiptsyes
Typingwithin 24 hours of the person’s last message, once they have written
Locations, contact cards, carouselsyes
Edit or unsendno (422 unsupported_content)
Link previewslink_preview on a basic message (shown by default; false sends links as plain text)

Connect a number in your business’s own WhatsApp Business Account yourself, with POST /v0/senders ("channel": "whatsapp") and a live key, or on the dashboard’s Senders page: see Going live for the steps in Meta Business Manager and the setup to enter in your Meta app. The Flow team can also connect a number for you, either one Flow provides or one in your own account. With your own account, add a payment method in WhatsApp Manager: Meta bills its fees to your business directly. On a number Flow provides, Meta’s per-message fees are passed through (see Pricing). Once the number is connected it appears in GET /v0/senders with your live key, and your agent’s code stays the same.