walid@portfolio:~/lab/whatsapp-sales-agent$
cd../lab
03ideaSep 2026

A sales agent that stays up

WhatsApp in, AI reply out — with memory, lead scoring, human handoff and a VPS that keeps running

Not a chatbot script — a small production system with separate jobs for messaging, reasoning, memory, lead state, deployment and optional voice. The separation is the whole point: it makes the thing debuggable, and it means a front-end change cannot take down your customer messaging. Ten steps from a local clone to a worker that survives you closing the laptop.

WhatsAppSupabaseNode.jsVPSVoiceLead scoringMeta API examplesVercel ChatbotTwilio realtime voiceEvolution APIBaileysChatwoot
i
What you are actually building

The instinct is to write one script that listens and replies. That version works in a demo and falls over the moment you need to know why it said something. This splits the work: a webhook that receives, a model that drafts, a database that remembers, a scorer that tracks intent, a host that stays up, and a voice layer you can ignore until you need it. Each piece can be debugged and replaced on its own.

The parts

Six jobs, six components

ComponentJob in the system
WhatsApp Cloud APIReceives and sends messages through the official Business Platform.
Node.js workerRuns the webhook, calls the model, scores leads, coordinates the flow.
AI modelDrafts the reply from your business context and recent conversation history.
DatabaseStores contacts, lead state and conversation memory.
VPSKeeps the worker alive after you close your laptop.
Voice layer (optional)Bridges phone audio to a realtime voice model over WebSocket.
Web host (optional)A front-end or dashboard. Explicitly not the persistent worker.
i
The architecture decision worth understanding

It talks to the official Cloud API directly rather than driving WhatsApp Web through browser automation. The popular automation libraries are genuinely good and widely used, but they drive the consumer web client, and their own documentation warns that accounts can be blocked. For something a business depends on, the official transport is the one that does not put the number at risk.

Prior art

What the design borrows from, and what it does not

Star counts re-checked against GitHub on 16 September 2026 rather than carried over. Five of the six matched the source; the customer-desk figure had drifted and is corrected here.

ProjectStarsHow it informed the build
Meta API examplesOfficialThe safest reference for Cloud API webhook and messaging behaviour. This is the transport.
vercel/chatbot20,946AI app structure and deployment patterns.
whatsapp-web.js22,575Very popular, but automates WhatsApp Web and warns accounts can be blocked. Reviewed, not the default transport.
Baileys11,062Well-liked socket library for WhatsApp Web. Same reasoning — reviewed, not the production default.
evolution-api9,627Strong reference for multi-channel WhatsApp integration architecture.
Twilio realtime sample362A clear MIT-licensed pattern for realtime phone voice.
Chatwoot36,870Product inspiration for the CRM and human-handoff side. No code taken from it.
Step 1

Get it running locally

Node 20+, TypeScript, Express. The worker listens on port 8080 by default — confirm the process is alive before wiring anything external to it.

terminal8 lines
git clone YOUR_REPO_URL
cd ai-sales-agent
cp .env.example .env
npm install
npm run dev

# prove it is alive before connecting anything
curl http://localhost:8080/health
!
If you are building this with a coding agent

Change one module at a time, and keep the webhook, database, model client and voice layer separate in your instructions. The separation is not academic: it is what stops a cosmetic UI change from breaking live customer messaging. Also — do not commit the .env file.

Step 2

The memory layer

Run the schema in a fresh database project. The starter creates two tables — contacts and messages. Every inbound and outbound message is stored, and the worker reloads recent history for that contact before the model drafts anything. That is what gives it short-term memory without stuffing an ever-growing conversation into one prompt.

.env2 lines
SUPABASE_URL=https://YOUR_PROJECT.supabase.co
SUPABASE_SERVICE_ROLE_KEY=YOUR_SERVER_ONLY_KEY
!
The service-role key is server-only

It bypasses row-level security by design. It belongs in the worker and nowhere else — never in a browser app, never in the front-end, never in anything shipped to a client. Growing past the starter means conversation summaries, embeddings for product knowledge, staff authentication and a real handoff queue; none of that changes where this key lives.

Step 3

Connect WhatsApp properly

Create a Meta developer app, add the WhatsApp product, connect a business number, and point the webhook at https://YOUR_PUBLIC_DOMAIN/webhooks/whatsapp.

.env5 lines
WHATSAPP_VERIFY_TOKEN=make-a-random-string
WHATSAPP_APP_SECRET=YOUR_META_APP_SECRET
WHATSAPP_ACCESS_TOKEN=YOUR_PRODUCTION_TOKEN
WHATSAPP_PHONE_NUMBER_ID=YOUR_PHONE_NUMBER_ID
META_GRAPH_VERSION=v23.0

How the webhook behaves

The GET route handles the platform’s verification handshake.The POST route validates the x-hub-signature-256 signature whenever the app secret is set.The worker acknowledges quickly, then processes the message — slow acknowledgement causes retries and duplicate handling.Replies go out through the Graph API using the phone-number ID and access token.Keep the Graph version in an environment variable. Dashboard labels and supported versions change, and a pinned version in code is a future outage.
Step 4

The AI brain

The model needs exactly three things: a strong system instruction, verified business context, and recent history. Everything else is decoration.

.env6 lines
AI_API_STYLE=responses
AI_API_BASE_URL=https://api.openai.com/v1
AI_API_KEY=YOUR_KEY
AI_MODEL=your-model
BUSINESS_NAME=Your Business
BUSINESS_CONTEXT=Products, pricing, policies, FAQs, availability, booking rules.
i
The single most important prompt rule

The system instruction explicitly forbids inventing stock, prices, delivery dates, discounts, policies or guarantees. When the data is missing, the correct behaviour is to say so and offer a human. An agent that confidently makes up a delivery date is worse than no agent, because a customer will hold you to it. Put product facts in structured data or a retrieval layer rather than a sprawling “remember everything about my company” prompt.

Step 5

Lead scoring — deliberately simple

A sales agent should do more than answer questions; it should preserve intent. This is transparent on purpose so you can replace it once you know which signals actually correlate with revenue.

Signal in the messageScore impact
Buy, order, book, pay+25
Price, quote, discount+15
Demo, call, appointment+15
Stock, availability, delivery+10
Urgency+10

Funnel stages and handoff

Four stages: new, engaged, warm, hot.The handoff module watches for explicit requests for a person, and for sensitive situations — complaints, refunds, legal language.Once a contact is handed off, the agent stops. It does not carry on impersonating the human rep.
Step 6

Voice, only if you need it

Telephony streams PCMU audio to your VPS over WebSocket, the realtime model returns audio, the worker streams it back to the caller. Point your voice number at https://YOUR_DOMAIN/voice/incoming.

.env4 lines
VOICE_ENABLED=true
OPENAI_API_KEY=YOUR_KEY
OPENAI_REALTIME_MODEL=your-realtime-model
OPENAI_REALTIME_VOICE=your-voice
i
What “voice” means here, precisely

This gives the business a normal phone channel. WhatsApp messaging stays on the Cloud API — the two are separate integrations. If your account has access to WhatsApp calling features, treat that as its own piece of work with its own eligibility rules, not as something this turns on.

Step 7

Put the worker online 24/7

A webhook worker must not depend on your laptop being open. This is the step that separates a demo from something a business can point a phone number at.

ubuntu vps15 lines
sudo apt update
sudo apt install -y git curl
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
sudo npm i -g pm2

git clone YOUR_REPO_URL
cd ai-sales-agent
cp .env.example .env
nano .env
npm ci
npm run build
pm2 start ecosystem.config.cjs
pm2 save
pm2 startup

Deployment rules that matter

Put a reverse proxy in front of port 8080.HTTPS is required for the platform webhooks.Voice additionally requires WSS, because it holds long-lived WebSocket audio streams.Monitor the health endpoint and restart on failure.Never host the persistent voice or WebSocket worker as a short-lived serverless function — it will be killed mid-call.
Step 8

Where a web host fits

Good for an optional page or a future dashboard. Not required for the messaging agent at all, and not the persistent worker. Add real staff authentication before any browser page displays customer data.

terminal2 lines
cd web
vercel
Step 9

Acceptance test before you show anyone

“It deployed” and “it works” are different claims. Run the eight.

#TestPasses when
1Webhook verifiesThe platform accepts the callback URL and verify token.
2Inbound message savesA new contact and inbound message appear in the database.
3Reply sendsThe user actually receives a WhatsApp response.
4Memory worksA follow-up that depends on the previous message is answered correctly.
5Lead score movesMentioning price or purchase intent changes the score.
6Handoff worksAsking for a human flags the contact.
7Restart survivesReboot the VPS and the worker comes back on its own.
8Voice worksIf enabled, calling the number gives two-way audio.
Step 10

Production guardrails — do not skip these

Use a permanent system-user token for production, not a temporary test token.Never put service-role keys, platform tokens or model keys in client-side code.Validate webhook signatures.Add rate limits and structured logs before volume, not after.Use verified catalogue data for pricing and stock.Keep an explicit human-handoff path at all times.Follow the platform’s opt-in, template, commerce and messaging policies.Never let the agent execute refunds, payments or other irreversible actions without deterministic rules and authorisation.Add retention and deletion rules for customer conversation data.Test prompt injection: customer text must never override the system prompt or reveal secrets.
For your coding agent

Adaptation prompt

Use it after cloning, with the bracketed details filled in. The important constraint is the last one — do not ask it to rewrite everything at once.

adapt-prompt.txt22 lines
Read the entire repository first. Do not change architecture yet.

Goal: adapt this 24/7 WhatsApp AI sales agent for [BUSINESS].

Business facts:
- products/services: [INSERT]
- pricing: [INSERT]
- customer FAQs: [INSERT]
- delivery/booking rules: [INSERT]
- refund/cancellation rules: [INSERT]
- human handoff contact/process: [INSERT]

Tasks:
1. Update BUSINESS_CONTEXT and the sales prompt.
2. Add only the business tools we actually need.
3. Never invent stock, prices, policies or dates.
4. Keep the messaging API, database and VPS worker separated.
5. Keep secrets in env vars.
6. Run typecheck after every meaningful change.
7. Explain every file you change and why.

Do not add unnecessary frameworks.
The real test

One complete sales loop, end to end

The goal is not “make the bot reply”. Anyone can get a reply.

Connect your own WhatsApp Business number.Load one real product or service with its exact facts.Get the agent to answer a question about it correctly.Ask a follow-up and prove memory actually works.Trigger a warm or hot lead score.Ask for a human and prove handoff fires.Close your laptop, message the number again, and confirm it still answers from the VPS.
When it breaks

Troubleshooting

SymptomCheck first
Webhook will not verifyThe callback URL must be public HTTPS, and the verify token must match exactly.
Messages arrive but no replyWorker logs, the AI key, the WhatsApp token, and the phone-number ID.
Memory is emptyDid the schema actually run? Then the service-role key and the project URL.
Duplicate processingYou are probably acknowledging too slowly — check retries in the webhook logs.
Voice connects but is silentWSS reachability first, then the realtime key, model, and media-stream logs.
Works locally, dies laterIt is not running under a process manager. Verify it starts on reboot.
!
A starter is not a finished product

This is deliberately small enough to read and understand in an afternoon, which is exactly why it is useful. Real production scale needs authentication, observability, queues, retries, access controls, compliance work and business-specific tooling. Knowing which of those you still owe is the difference between shipping and being surprised.

i
Provenance

Commands, environment variable names and the acceptance tests are as given in the source guide. The star counts for every referenced project were re-checked against GitHub on 16 September 2026: five of six matched, and the customer-desk figure was corrected upward. Model and voice identifiers are left as placeholders rather than pinned, since those move faster than anything else here.

← previous
Seven repos worth the disk space
next →
Website in, React app out
← all experiments