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.
// app/api/whatsapp/route.ts (Next.js). npm install @flow-engineer/messaging ai @ai-sdk/openaiimport { 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 }, ); },});1. Build it on Telegram today
Section titled “1. Build it on Telegram today”There is no WhatsApp sandbox yet. Run the same code on the Telegram sandbox bot:
- Get a test key, with no account:
curl -X POST https://api.flow.engineer/v0/sandbox/keys(ornpx @flow-engineer/messaging init). Set itskeyasFLOW_MESSAGING_KEY. See Keys and sign-in. - Join the Telegram sandbox from your phone with the bot’s
address.link(see Build a Telegram agent). - 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:
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 },});{ "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.
3. Voice notes
Section titled “3. Voice notes”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.
4. Buttons and lists
Section titled “4. Buttons and lists”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.
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.
5. The 24-hour window and templates
Section titled “5. The 24-hour window and templates”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
409outside_window. - Subscribe to
conversation.window_closingto get a warning 1 hour before it closes.
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. Templates made in Meta’s WhatsApp Manager are synced into Flow by themselves (source whatsapp_manager), or at once with POST /v0/templates/sync; a named variable such as {{listing}} takes its value by position, in the order the body first names it. See Going live and Templates made in WhatsApp Manager.
6. What WhatsApp supports
Section titled “6. What WhatsApp supports”| Kind | |
|---|---|
Text (basic) | yes, sent as written; WhatsApp’s app shows its own *bold* and _italic_ |
| Images, video, documents, audio | yes |
| Voice notes | yes, both ways |
| Buttons | 1 to 3 reply buttons, or one URL button; a list for up to 10 rows |
| Reactions, read receipts | yes |
| Typing | within 24 hours of the person’s last message, once they have written |
| Locations, contact cards, carousels | yes |
| Edit or unsend | no (422 unsupported_content) |
| Link previews | link_preview on a basic message (shown by default; false sends links as plain text) |
7. Going live on WhatsApp
Section titled “7. Going live on WhatsApp”Connect a number in your business’s own WhatsApp Business Account yourself, with POST /v0/senders ("channel": "whatsapp") and a live key, or in the dashboard under Connections › WhatsApp: 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.
Related
Section titled “Related”- The send gate: windows, new-contact limits and quality.
- Streaming replies and Content types.
- Frameworks: Vercel AI SDK, OpenAI Agents SDK, LangChain.
Thanks, we read every comment.