Projects / Demo
WhatsApp AI Support Agent
Demo · All data is synthetic
I built this demo around the messages a small payments business gets on WhatsApp all day: where's my refund, what are your fees, the payment failed but money was deducted. The business is made up (Lantern Pay, payment links for small shops), and so are its six customers. The server speaks the real WhatsApp Cloud API format: the signed webhook, the verify handshake, and outbound message bodies, sent to a local mock because I didn't connect a real WhatsApp number or Meta app. From working on card programs, starting as a software engineer at i2c, I didn't want the model making any decision that touches money or customer data. A small local model (Qwen 2.5 3B on a CPU) only picks the intent. Plain code pulls the reference out of the message, checks that the payment belongs to the number asking, writes the reply from fixed templates, and handles STOP, pasted card numbers, duplicate webhooks, and WhatsApp's rule that after 24 hours of silence a business can only send approved templates. I replayed 35 scripted steps and checked 40 outcomes. My first prompt passed 36. It sent a payouts question and a loan request to the payment lookup, and missed two "do you accept this card" questions, one of them in Roman Urdu. Adding topic descriptions didn't help, and in one case the bot answered a card question with that customer's earlier refund. Adding seven worked examples brought it to 39 of 40, the same in four runs. The one left is "can you lend me 50,000 rupees", which goes to a person instead of a polite no. With the model switched off or unreachable, the rules fallback passes all 40. But I wrote those rules and examples after seeing these same messages, so the numbers are optimistic. Nothing here has touched real WhatsApp, real templates or real customers. It's a demo on synthetic data, not a production system.
How it works
Signed webhook (X-Hub-Signature-256) → duplicate message id? ignore → hard rules: STOP / START, opted out, card number, hand-off → intent from qwen2.5:3b (rules fallback) → reference by regex + ownership check → reply from a fixed template or FAQ entry → POST /messages (local mock of the Cloud API)Behaviour
Model picks the intent onlyqwen2.5:3b via Ollama on a CPU returns an intent and FAQ topic as JSON. It doesn't write replies, pull out the payment reference or see an unmasked card number.
Ownership checkThe payment reference comes from the message by regex, or from the chat's memory, and status is only shared with the number that made or received the payment.
Card-number guardAnything that passes the Luhn check is refused with a "please don't send card numbers" reply and only stored masked.
Opt-out and duplicatesSTOP and START are honoured, opted-out customers get nothing, and a webhook delivered twice with the same message id gets no second reply.
24-hour windowBusiness-initiated updates send free-form text only within 24 hours of the customer's last message. After that it's a template message (illustrative, not registered with Meta).
Test set35 scripted steps across 6 fictional customers, 40 checks. With the model: 39 of 40 in four runs. Rules only: 40 of 40. I wrote the rules and examples after seeing these messages, so these numbers are optimistic.
Demo
A short walkthrough of the demo, made from the screenshots below, not a screen recording. Messages go to a local mock of the WhatsApp Cloud API; no real number was connected.
Screenshots






Stack
Node 20 with no npm dependencies (built-in http, crypto and fetch), Ollama running qwen2.5:3b on a CPU, and Python 3 for the check script. An OpenAI adapter uses the same request format but is untested, since no key was available.
This is a demo on synthetic data, not a production system. Lantern Pay and its customers are made up, nothing here has touched real WhatsApp, real templates or real customers, and no API key or paid service is used.