Skip to content
See all notes
whatsappaiautomationarchitecture

A WhatsApp chatbot on the official API with an LLM behind it

What nobody tells you before building a WhatsApp bot with AI: the 24-hour window and its per-message billing from October 2026, why the webhook cannot wait for the model, and how to stop the assistant inventing answers.

· 11 min read

Almost every "WhatsApp chatbot" tutorial starts by hooking up a personal number with an unofficial library. It works in ten minutes. And it keeps working right up until Meta blocks the number, which tends to happen once the bot is handling real customers.

This note is about the other path: the official WhatsApp Business API, with a language model behind it. Slower to start, and it does not fall over.

What Meta requires before you write a line#

This is the part that catches everyone out, because none of it is code:

  1. A Meta Business account with the business verified — real company documents.
  2. A phone number not registered on WhatsApp. Not your personal one, not the one the team already uses. If it is registered, you must delete it first and wait.
  3. An app on Meta for Developers with the WhatsApp product added.
  4. Approved message templates, so you can write first.

None of that gets sorted in an afternoon. I budget one to two weeks of paperwork alone, and I put it in the timeline from day one: it is the number one reason a project like this "runs late" when it was actually going fine.

The 24-hour window#

This is the concept that breaks the most expectations, and it is worth explaining to the client before signing:

You can reply with free text only within 24 hours of the person's last message. After that, you can only start a conversation with a template approved by Meta.

The practical consequences are significant:

  • An assistant answering questions: no problem, it always replies inside the window.
  • Reminding someone of tomorrow's appointment: needs a template, approved in advance, with fixed text and limited variables.
  • "Have the bot message customers who haven't bought in a month": template, and it is marketing too, with its own category and cost.

When someone asks for "a bot that messages people", they are almost always asking for templates without knowing it. Clearing that up early avoids an awkward conversation halfway through.

What changes in October 2026: every bubble costs#

The window is not going away: you can still reply with free text inside those 24 hours without approved templates. What is going away is it being free.

Until October 2026From October 2026
Billing unitThe conversationEach message sent
Free text in the windowIncludedCharged per unit
PricePer conversationFixed per message, by recipient country

And it applies to everything that goes out: written by a human, a bot, or a third-party AI. There is no separate rate for being an automated reply.

It sounds like a billing footnote and it is an architecture decision, because it changes what counts as good conversation design.

What suddenly gets expensive#

These patterns were free inside the window and from October are billed one by one:

  • Splitting a long answer into several bubbles "so it reads better". Three bubbles are now three charges.
  • Intermediate acknowledgements: "Sure, one moment…", "Let me check…", "Done!". Each costs the same as a useful answer.
  • Faking typing by sending short messages in a row.
  • Multi-step menus asking one thing per message.

An assistant that resolves a query in five bubbles costs five times what one that resolves it in one. At low volume it makes no difference; at thousands of conversations a month, it is the gap between a reasonable cost and an invoice nobody saw coming.

How I design now#

One answer, one message. Well structured, with line breaks and lists if needed, but complete. If the model tends to split, the system prompt forbids it explicitly.

No conversational filler. No "I'll help you right away" followed by the help: send the help. Politeness goes inside the same message.

Ask once, and properly. If three pieces of data are missing to process an order, ask for all three together, not one per message.

Buttons and interactive lists instead of chaining questions: a single message can offer several options and saves the whole back and forth.

It is a rare and pleasant case: what lowers the invoice is also the better experience. Nobody wants six notifications in a row from a business.

The webhook: why you cannot call the model there#

Meta sends every incoming message to your webhook and expects a fast 200. If you are slow, it retries; if it retries, you process the same message twice and the customer gets two replies.

And an LLM is slow. Between retrieving context and generating, several seconds go by.

So the webhook does not answer: it queues.

@Controller('webhooks/whatsapp')
export class WhatsappWebhookController {
  constructor(
    @InjectQueue('whatsapp') private readonly queue: Queue,
    private readonly signature: SignatureVerifier,
  ) {}

  @Post()
  @HttpCode(200)
  async receive(
    @Headers('x-hub-signature-256') signature: string,
    @Body() payload: WhatsappWebhookPayload,
    @Req() req: RawBodyRequest<Request>,
  ): Promise<void> {
    // 1. Verify it came from Meta, using the RAW body
    if (!this.signature.isValid(req.rawBody, signature)) {
      throw new UnauthorizedException();
    }

    // 2. Queue it and return 200 immediately
    for (const message of extractMessages(payload)) {
      await this.queue.add('incoming', message, {
        // The same message retried by Meta is not processed twice
        jobId: message.id,
      });
    }
  }
}

Two details that cost a day each if you miss them:

The signature is validated against the raw body. If your framework already parsed the JSON and you re-serialise it, the hash will not match: JSON.stringify guarantees neither key order nor spacing. In NestJS you have to enable rawBody explicitly.

The jobId is the message id. Meta retries, and the queue drops the duplicate by itself. Without it, a latency spike turns into repeated replies.

How I stop the assistant inventing things#

An assistant that makes up a price or a policy in front of a customer does more damage than no assistant at all. Trust is lost in a single message.

Three decisions I always apply:

1. It answers from documents, not from memory. The model does not "know" the business: it receives fragments retrieved from the client's real documents — catalogue, policies, opening hours — and answers only from those.

async function answer(question: string, businessId: string): Promise<Answer> {
  const context = await retriever.search(question, { businessId, limit: 5 });

  // No context means no improvising: hand off
  if (context.length === 0) {
    return { type: 'handoff', reason: 'no_context' };
  }

  const reply = await llm.complete({
    system: SYSTEM_PROMPT,   // "answer ONLY from the given context"
    context,
    question,
  });

  return reply.confident ? { type: 'answer', text: reply.text } : { type: 'handoff' };
}

2. "I don't know" is a valid and desirable answer. The system prompt states explicitly that if the context does not contain the answer, it must say so and offer to pass the conversation to a person. It takes some convincing — it looks less impressive in the demo — and it is what makes the assistant usable in production.

3. No irreversible actions without confirmation. An agent can create an order or book an appointment, but the final step is confirmed with the user in the same chat. A model that misreads "cancel that" should not be able to cancel anything on its own.

Human handoff is not optional#

Every assistant needs a way out to a human, and that exit needs state. When a conversation is handed off, the bot stops replying in that thread until the agent gives it back. Otherwise the customer ends up talking to both at once.

SituationWhat the bot does
No context to answerHands off and says so
User asks for a personHands off immediately
Complaint or cancellationHands off without trying to resolve
Handoff already activeComplete silence until returned

What cost more than expected#

The paperwork, not the code. Business verification and template approval took more calendar time than building the entire bot.

Voice notes. People send audio to businesses constantly. If you do not handle it, the bot goes mute with exactly the customers who message most. Transcribing and treating it as text is not hard, but it has to be decided up front.

The variable cost, and that it moves. You pay Meta for traffic and the model provider per token, and the rules change: the move to per-message billing in October 2026 makes expensive exactly the pattern many bots use without thinking. Check the pricing policy before every proposal and put it in the contract — a billing assumption that ages badly is an awkward conversation six months later.

When I would not build one#

If the volume is low, a person answers better, cheaper, and with more judgment. Automation starts paying off when there is real repetition: the same twenty questions every day, or after-hours messages that are currently being lost.

And if the business data is a mess — outdated catalogues, policies nobody wrote down — the assistant will answer badly because the source is bad. Sorting that out is the first job, and it often solves half the problem without any bot at all.

Got a system with this kind of problem?

Tell me what you are working on and I will tell you how I would approach it.

Let's talk about your system