article

Anatomy of a WhatsApp booking agent for a Harare clinic

What is actually inside the agent in our demo: five tools, one policy, a confirmation gate, and about two and a half US cents of model tokens per booked appointment.

Sequence diagram with five lifelines: patient on WhatsApp, Meta Cloud API, agent orchestrator, model, and clinic calendar. The patient sends a message; the API delivers a webhook; the orchestrator calls the model; the model requests a patient lookup and an availability search; the orchestrator runs both against the calendar; the model drafts three slots; the API delivers them; the patient replies with a choice; a guardrail check runs; the booking is created; a reminder template is scheduled; a confirmation is sent. patientWhatsApp Cloud APIorchestratormodelclinic calendar "check-up this week, afternoons" webhook (opens 24 h window) context: system + tools + message tool_call lookup_patient lookup · get_availability (12:00–17:00) record + 3 slots message: three options free-form reply (US$0.00, inside window) "2 please" guardrail: confirm + re-check create_booking (idempotent) B-20931 confirmed schedule utility template (US$0.004)
Eight model calls and six tool calls; the only paid WhatsApp message is the next-day reminder template.

The demo on this site shows one conversation. This article shows what had to exist for that conversation to happen: the pieces, the decisions behind each, and the arithmetic. The clinic is illustrative — a small dental practice in Harare with two practitioners, thirty-minute check-ups and a WhatsApp number patients already use — but every constraint is real.

Why a clinic, why WhatsApp

Booking is the best first agent for a service business because the job is bounded, the actions are reversible and the value is legible: a filled slot, an avoided no-show. WhatsApp is the channel because it is where Zimbabwean patients already are — POTRAZ’s Q4 2025 report puts WhatsApp at 20.69% of mobile-app data usage, the largest single application — and because replies inside the 24-hour customer service window cost nothing per message. A phone-based booking agent would pay for every minute of line time; a WhatsApp one pays for tokens and one reminder template.

The five tools

The model can only do what it has a tool for. Five is enough:

ToolReads or writesContract
lookup_patient(phone)readReturns the record for a known number: name, last visit, service history, balance. Unknown numbers return an empty record and the agent asks for a name.
get_availability(service, from, to, window, limit)readReturns up to limit open slots for the service inside the window, with the practitioner. Advisory only — it may be stale by the time the patient replies.
create_booking(patient_id, slot_id, service, channel, idempotency_key)writeCommits a slot. Returns a booking reference or a conflict. The key makes retries harmless.
schedule_template(template, category, to, send_at, params)writeQueues an approved WhatsApp template. Returns the job id and the estimated cost.
create_handoff(patient_id, reason, urgency, summary, channel)writeOpens a ticket in the nurse or reception queue with a summary the human can act on without re-asking.

Two things are deliberately missing. There is no cancel_booking in the first release — cancellations go through handoff until the cancellation policy (cut-off, fees) is written down and evaluated. And the payments tool exists in the system but is disabled on every turn of a check-up booking, because check-ups do not require a deposit. Tools are enabled per turn by the orchestrator, not by the model.

The policy, as the agent reads it

The system prompt is short — about 900 tokens of the 3,700-token cached prefix; the tool schemas are the rest. Its load-bearing lines, paraphrased:

  • Scope: scheduling, directions, prices from the price tool, reminders. Nothing clinical. Never arrange medication or give advice about symptoms; acknowledge, hand off, offer an earlier slot.
  • Offer at most three slots, as a list message, in the patient’s language.
  • Never write without an explicit choice from the patient. Re-check availability at commit time.
  • State the reference, time, practitioner and cancellation window in every confirmation.
  • If the patient asks for a person, hand off immediately.

None of these sentences is a guardrail on its own. Each is backed by code: the scope rule by a classifier that flags clinical keywords before the model sees the message; the write rule by the orchestrator refusing any create_booking call in a turn where the previous customer message was not a slot choice; the “three slots” rule by limit: 3 in the tool schema.

The loop, turn by turn

The sequence diagram above is the whole conversation. In prose:

  1. Inbound. “I need a dental check-up this week, afternoons only if possible.” The webhook opens a 24-hour window. The orchestrator loads the session (none), assembles the context and calls the model.
  2. Identify. The model calls lookup_patient. Known patient, no balance. Cost so far: the cache write of the prefix plus a few hundred uncached tokens.
  3. Search. The model turns “this week, afternoons” into from, to and window values and calls get_availability. Three slots return.
  4. Offer. A list message with three options. Free.
  5. Choice. “2 please.” The orchestrator maps the reply to the list row ID — no parsing of “the Thursday one” needed.
  6. Guardrail, then write. The pre-write check passes (explicit choice, slot re-verified). create_booking returns B-20931.
  7. Reminder. schedule_template queues a utility template for the day before — the only paid message, US$0.004 to a +263 number on the rate card effective 1 July 2026.
  8. Confirm. Reference, time, practitioner, cancellation window. Free.
  9. Out of scope. “Can you also get me antibiotics? My tooth is aching.” The scope guardrail fires before the model replies; the model’s job is now to decline warmly, offer the earlier slot it saw in step 3, and call create_handoff with urgency same_day. It does all three.
  10. Idle. Session closes after the final confirmation; the trace is stored.

Eight model calls, six tool calls, one paid message, zero unsafe actions.

What it costs

Token counts in the demo are estimates for a Sonnet 5-class model with prompt caching. The cached prefix (3,700 tokens) is written once at 1.25× the input price and read seven more times at 0.1×. Across the conversation: roughly 925 uncached input tokens, 35,000 cached input tokens and 635 output tokens.

LineCost (illustrative)
Model, Sonnet 5 with caching≈ US$0.025
Same on Haiku 4.5≈ US$0.012
Same on Sonnet 5 without caching≈ US$0.087
WhatsApp replies inside the window (4)US$0.000
Reminder template, utility, +263US$0.004
Per booked appointment≈ US$0.029

The lesson most vendors skip: caching matters more than model choice. Turning it off costs more than upgrading to the largest model.

Against that, the value side is easy to reason about even without your numbers. If the clinic sees 400 check-ups a month and reminders cut no-shows by even a few percentage points, the recovered appointments pay for a year of the agent’s variable cost in a week. That is an illustrative shape, not a measurement; the business-case arithmetic with your own figures belongs in a calculator, not a paragraph.

Edge cases in the golden set

An agent is only as good as the scenarios it was tested on. The clinic’s golden set has 60 conversations; the ones that catch real bugs:

  • “Tomorrow” sent at 23:55 — date boundary in Africa/Harare.
  • A public holiday inside the requested week.
  • The patient picks “2” after the list was re-sent with different options because the first Thursday slot was taken meanwhile.
  • A returning patient with an unpaid balance where policy says “settle first” — the agent explains and offers a Paynow link, and books only after the poll URL reports paid.
  • Mixed language: “Ndoda check-up nemasikati eThursday.”
  • Two identical webhooks for the same message — the idempotency key must produce one booking.
  • Every clinical phrasing the practice could think of, including ones that look like scheduling (“book me for antibiotics”).

The law, briefly

The clinic is the data controller. Patients’ names, phone numbers and appointment history are personal data; the transcripts are too. The practice needs its POTRAZ licence and a named Data Protection Officer, keeps the trace store access-controlled, and has a breach runbook that can meet the 24-hour notification clock. The agent does not make decisions with significant legal effect on the patient — it books and reminds — which keeps it clear of the automated-decision rule, provided the human handoff on anything clinical is real. The governance page has the full list.

What to copy

Five narrow tools. A policy whose every sentence is backed by code. A confirmation gate on the only write that matters. One paid message. A golden set that includes the ways the agent can be wrong. That is the anatomy; the rest is integration work, and the integrations page lists it.

Sources

  1. Meta — Pricing on the WhatsApp Business Platform (windows, categories, Rest-of-Africa list) — https://developers.facebook.com/documentation/business-messaging/whatsapp/pricing
  2. SleekFlow — WhatsApp per-message rates by market, effective 1 Jul 2026 — https://sleekflow.io/blog/whatsapp-business-price
  3. Anthropic — model pricing, prompt caching multipliers — https://platform.claude.com/docs/en/about-claude/pricing
  4. POTRAZ Q4 2025 abridged sector report via TechnoMag (WhatsApp share of mobile app data) — https://technomag.co.zw/zimbabwes-internet-data-penetration-surges-to-84-55-in-q4-2025/
  5. Paynow Node.js SDK — express checkout and poll URL — https://github.com/paynow/Paynow-NodeJS-SDK
  6. DLA Piper Africa — Quick-start guide to Zimbabwe's data protection regulations — https://www.dlapiperafrica.com/en/zimbabwe/insights/2024/A-Quick-Start-Guide-to-Zimbabwes-Data-Protection-Regulations

All sources accessed 2026-09-14 unless stated. Figures marked illustrative are worked examples, not measurements.