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. See Going live.
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 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.
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.