← Blog

Chat agents

WhatsApp lead-qualification agent: 11 days, 12 branches

A driving-school operations lead came to us with twelve branches, one WhatsApp inbox per branch, and a backlog of leads going cold by Tuesday. We had eleven days.

Jacob Molkenboer· Founder · A Brand New Company· 5 Jun 2026· 9 min
Brass switchboard with eleven green cloth cables coiled, one chartreuse cable trailing, wax seal and envelope on ivory paper.

Twelve branches, one inbox, one weekend

It is Monday morning at a regional driving school in the Netherlands. Twelve branches across three provinces, twelve managers, and one operations lead trying to keep up with the WhatsApp inbox. By Tuesday afternoon, leads that came in over the weekend are cold. A parent asked about lessons for their seventeen-year-old on Saturday, did not hear back, and booked with a competitor on Sunday night.

The owner described it in different words: "We are losing the people who message us on Saturday and Sunday. By the time someone reads the message on Monday, they have already paid a deposit somewhere else."

The brief: qualify leads on WhatsApp before any human touches them, route the qualified ones to the correct branch, and do it inside two weeks because the summer rush was eleven days away. We finished on day eleven.

One number per branch, not one number total

The first design temptation is the wrong one: a single WhatsApp number that asks "which branch?" up front. We tested that flow on paper for an afternoon and threw it out. Three reasons.

First, the branches already advertise their own local numbers on flyers, on Google Business profiles, and on the doors. Replacing them with a national number means reprinting and re-listing twelve sets of marketing material. Nobody wants that.

Second, local numbers convert better. A parent in Zwolle messaging a Zwolle number trusts the response more than the same parent messaging an 088 service number. We did not have hard data on this for the driving-school sector, but the operations lead believed it strongly enough to make it a constraint.

Third, the branch managers wanted to keep visibility over their own pipeline. One inbox per branch, one queue per branch, one set of stats per branch. A central pool would have triggered a small civil war.

So: twelve Twilio WhatsApp senders, one per branch. Same agent code behind all of them, branch identity passed as context on every webhook.

Provisioning twelve senders without losing eleven days

The slow part of any WhatsApp Business build is not the code. It is the sender approval. Each WhatsApp Business number needs a display name approved by Meta, message templates approved by Meta, and a Twilio sender registration that ties them together. Twilio's WhatsApp documentation is honest about the timing: assume two to five business days per sender, sometimes longer if your display name does not match your verified business name on Facebook Business Manager.

We were not going to wait fifty days for twelve senders in series. We did three things to compress the window:

  • Submitted all twelve display-name requests on day one, before a single line of code existed. The product can wait. Meta cannot be rushed.
  • Pre-registered a single parent business on Facebook Business Manager that owned all twelve branch profiles. Approvals then ran in parallel instead of as twelve separate verification chains.
  • Wrote and submitted the four message templates we knew we needed (initial outreach, missed-window reminder, branch hand-off, lesson confirmation) on day two. Templates take their own approval cycle. Running them alongside the code work saved us a week.

By day six, ten of twelve senders were live. The last two cleared on day nine. Two branches piloted on days nine and ten, the rest went live on day eleven.

The qualification flow we shipped

A lesson booking has more variables than people expect. Dutch driving licences alone have a dozen categories. Most leads for a driving school care about three: B (passenger car), AM (moped), and A (motorcycle, with sub-categories A1 and A2). The qualification flow had to identify the category, the learner's age, the preferred branch, the preferred start window, and whether the lead was the learner or a paying parent.

We did not write a giant prompt and pray. The flow is a state machine with five explicit states, and the agent is only allowed to transition forward when the slot it needs is filled.

// Simplified excerpt from the per-branch handler
type QualState =
  | "greet"
  | "category"      // B, AM, A, A1, A2
  | "age_window"    // 16, 16.5, 17, 17.5, 18+
  | "start_window"  // "this month", "summer", "after exam X"
  | "handoff"

type Lead = {
  branchId: string
  whatsappFrom: string
  state: QualState
  category?: "B" | "AM" | "A" | "A1" | "A2"
  ageMonths?: number
  startWindow?: string
  isParent?: boolean
  notes: string[]
}

async function step(lead: Lead, incoming: string): Promise<Reply> {
  switch (lead.state) {
    case "greet":
      return ask(lead, "category",
        "Hi! Welk rijbewijs wil je halen? B (auto), AM (brommer), of A (motor)?")
    case "category": {
      const cat = parseCategory(incoming)
      if (!cat) return clarify(lead, "Bedoel je B, AM, of A?")
      lead.category = cat
      return ask(lead, "age_window", ageQuestion(cat))
    }
    // ... etc
  }
}

The language model writes the free-form parts: the greeting tone, the clarifications when input is ambiguous, the summary at the end of the conversation. The slot-filling logic is regular code. That split is the most important architectural choice in the build, and it is the reason the agent does not hallucinate lesson prices or invent branch addresses.

What the agent is allowed to say

Containment is the part people skip and then regret. Anthropic published a useful piece this year on the ways they contain Claude across products, and the underlying point applies to anyone shipping an agent into a customer-facing channel: the model is the engine, not the policy. The policy lives outside the model.

For this build, containment meant four things:

  • No price quoting. The agent never states a per-lesson price. Prices vary per branch and per package, and a wrong number on WhatsApp becomes a complaint at the front desk. The agent says "your branch will confirm exact pricing when they call you back" and moves on.
  • No scheduling. The agent does not promise a slot. It promises a callback window. The branch's existing planner handles actual slots, and we did not want to dual-write into two systems.
  • No medical or legal advice. Driving schools get a surprising number of questions from learners with ADHD, dyslexia, or anxiety asking whether they can still get a licence. The agent's instruction is to acknowledge, capture the question as a note, and route to a human. There is a real legal framework for this in the Netherlands (the CBR's Eigen Verklaring) and we do not want a language model paraphrasing it.
  • No off-topic. The agent answers questions about driving lessons. If the conversation drifts (and it does, regularly, into "can you also tutor my son for his theory exam?"), the agent captures the question and hands off.

All four of these are enforced in code wrapping the model call, not in the prompt. A prompt instruction like "never quote prices" is a suggestion. A post-processing check that flags any euro symbol in the draft reply and rewrites it to "[your branch will confirm]" is a guarantee.

Warning

WhatsApp Business has a 24-hour customer service window. Outside that window you can only send pre-approved template messages. If a branch manager replies on Monday to a Sunday lead, the session is already closed. Plan template messages for the re-engagement case before you go live, not after.

The eleven-day timeline

Here is what the calendar actually looked like. Clients always ask how we fit a build like this into two weeks, and the answer is: by being honest about which things wait on humans and which things wait on code.

  • Day 1. Discovery call with the operations lead and two branch managers. Mapped current intake. Submitted twelve display-name requests to Meta.
  • Day 2. Drafted and submitted four message templates. Wrote the state machine on paper.
  • Day 3. Built the Twilio webhook handler and the state machine in TypeScript on a single Cloudflare Worker. Stubbed branch routing.
  • Day 4. Wired branch metadata (address, manager name, business hours) as static config per branch. No CMS, no database for this. Twelve YAML blocks in the repo.
  • Day 5. Built the hand-off email and the simple per-branch dashboard the managers use to see new leads.
  • Day 6. Ten of twelve senders cleared Meta approval. Started end-to-end testing on a sandbox number.
  • Day 7. Walked through the flow with the operations lead. She rewrote three of the agent's greeting lines in plainer Dutch. We kept her wording.
  • Day 8. Last two senders approved. Built the missed-window template message for leads that go quiet for more than four hours.
  • Day 9. Pilot launched at two branches (Zwolle and Apeldoorn). 23 conversations in the first afternoon. Two bugs found, both fixed before end of day.
  • Day 10. Pilot ran a second day. No new bugs. Operations lead approved full rollout.
  • Day 11. Remaining ten branches went live, one number at a time, with a manager on standby for each.

The compressed timeline only worked because we ran the human-blocking work (Meta approvals) in parallel with the code work, and because we made design constraints early that would have been arguments later. One number per branch. No price quoting. No scheduling. Each of those decisions saved us a meeting in week two.

What the first two weeks looked like

We measured four things in the first fortnight after launch. We are not publishing the absolute lead volume because the client asked us not to, but the ratios are useful.

  • Median time to first reply went from "next business day" to under thirty seconds. This was the headline number for the owner.
  • Qualified-lead share (leads where category, age window, branch, and start window were all captured before a human read the conversation) was 71% in week one and 78% in week two. The remainder needed a human nudge somewhere in the flow, usually because the learner asked an off-topic question the agent handed off correctly.
  • Weekend conversion (leads who messaged Friday evening to Sunday night and booked a paid intake within seven days) roughly doubled compared to the prior month's baseline. We have not run this for a full quarter yet, so treat that as a directional signal, not a guarantee.
  • Manager satisfaction is impossible to measure cleanly. The operations lead's note in week two was: "I deleted the Saturday alarm on my phone." We are taking that as a strong signal.

What we would do differently

Two things.

First, we underestimated the volume of voice-note messages. About 14% of incoming WhatsApp messages in the first week were audio. Our v1 agent did not handle them and politely asked the sender to type. That is fine, but it is friction. v1.1 added voice-note transcription before the state machine sees the text. We should have shipped that on day one.

Second, we should have built the per-branch dashboard with sortable columns from the start instead of a flat list. Branch managers at the busier locations were asking for it by day twelve.

Neither would have changed the eleven-day timeline. Both would have made week two calmer.

If you are looking at a similar build at your own company, the smallest thing you can do today is open a sandbox account on Twilio's WhatsApp playground and sketch a state-machine flow on paper. The agent design is the easy part. Provisioning and approval workflows are what make or break the calendar.

When we built this WhatsApp agent for the driving-school chain, what nearly cost us the deadline was a single mismatched display name in Facebook Business Manager, which we fixed by submitting all twelve display names with identical legal-entity casing on day one so any rejection would surface against all twelve at once. The same pattern (state machine in code, language in the model, approvals running in parallel with build) is how we ship most of our AI agents.

Key takeaway

Run Meta approvals in parallel with the code work, keep the model out of the state machine, and an eleven-day WhatsApp agent rollout becomes possible.

FAQ

Why one Twilio number per branch instead of one national number?

Local numbers convert better, branches already advertise them on flyers and Google Business profiles, and branch managers wanted visibility over their own queue rather than a shared pool.

How long does WhatsApp Business sender approval take?

Twilio's docs suggest two to five business days per sender, longer if your display name does not exactly match the verified business name on Facebook Business Manager.

Can the agent quote lesson prices?

No. Prices vary per branch and per package, so the agent always defers to the branch on pricing. A post-processing check rewrites any euro symbol in the draft reply before sending.

What happens when leads message outside business hours?

The agent qualifies the lead immediately and promises a callback in the next business window. A pre-approved template message handles re-engagement once the 24-hour WhatsApp session closes.

Does the model run the conversation logic?

No. A regular state machine controls transitions. The model only writes the free-form parts: greetings, clarifications, and summary. This is why it does not invent prices or addresses.

ai agentschat agentscase studyautomationintegrationsworkflow

Building something?

Start a project