Production AI agent · n8n
How I built a WhatsApp ordering agent in n8n
By Kushal Sapkota · Panauti, Nepal ·
I'm Kushal Sapkota, a Django developer and AI-automation engineer from Panauti, Nepal, and the founder of Affaiai. This is how I built an AI ordering agent in n8n for a cake shop, a client project that has been running in production, and what I learned shipping it.
The problem
The shop takes orders on WhatsApp and through its website chat. Customers write in Nepali, Roman Nepali and English, often in the same message. Orders arrive as text, voice notes, photos and payment screenshots. The owner is not technical and needs to change the bot's behaviour without calling me. And because money is involved, the AI must never decide that something has been paid. Failures have to be loud, not silent.
The architecture: one message, seven stages
Channels WhatsApp (360dialog API) · website chat Ingest webhook · dedupe · voice-to-text · image fetch Gate chat paused for staff? · owner settings · debounce Agent LLM · memory · 5 SQL tools · structured output Guards feature flags · items valid? · date valid? · fee calculation (code) Actions reply + photos · payment QR · save order · Telegram alert State PostgreSQL · admin panel · shared inbox
The decisions that mattered
- The model proposes. Code decides. The owner's feature switches are re-checked in code after the model answers, so "off" really means off. Totals and delivery fees are computed in SQL, never by the model.
- Structured output, not free text. The agent returns one reply for the customer plus machine-readable fields. A parser fixes the shape before anything downstream runs, and the agent never writes to the database itself.
- Tools instead of memorised facts. Five SQL tools (cakes, occasions, delivery zones, time slots, payment methods) mean a price changed in the admin panel is used on the very next message.
- Reading payment proofs, carefully. A vision model reads the paid amount, and a backup model tries if the first cannot. The result only flags the order for the owner to confirm. The AI cannot mark an order as paid.
- Behaviour as data. The owner flips switches, edits FAQs and adds holiday notices in an admin panel, and a Postgres function composes them into the prompt.
- Humans stay in the loop. One inbox holds WhatsApp and website chats. A staff reply pauses the bot for that customer, and the owner is alerted on Telegram when it needs help.
- Test on a copy, never the live shop. Tests run on a copy of the workflow with every send node disabled and fake phone numbers, and each deploy is diffed node by node.
What broke in production
- Website chat went silent for about an hour. A regenerated workflow was left inactive. I now check every workflow's active flag after each deploy.
- A test left the bot stuck on "always English". A customer writing Roman Nepali got an English reply. Tests now restore the production defaults.
- Meta rejected Nepali-script documents, twice, during WhatsApp business verification. Compliance turned out to be part of the system design, not an afterthought.
A prompt is a request, not a security boundary. Anything that must be true, such as "never mark an order paid", belongs in code that runs after the model.
Tools used
n8n, PostgreSQL, OpenRouter (language and vision models), the 360dialog WhatsApp API, Telegram for owner alerts, and Docker.
See more
The full case study, with the architecture and more screens, is on my portfolio: kushal.aafaiai.com/#case. I'm also on GitHub and LinkedIn.