An AI-powered WhatsApp assistant built on Meta's WhatsApp Cloud API. Customers ask questions in plain language and get answered in seconds — no menus to memorise, no one at the keyboard.
Small businesses lose enquiries overnight. Someone messages at 11pm asking whether you build inventory systems, nobody replies until morning, and by then they have gone elsewhere.
Saima Bot answers immediately, and it answers properly. It knows the full portfolio, what each project does, which links are real, and where the boundaries are — so it can hold an actual conversation about a project instead of reciting a fixed menu.
Example exchange
Customer: do you build inventory systems for a small shop? Bot: Yes — that's close to Munshi Jee, a cloud khata built for wholesale traders with stock and billing included. What kind of shop is it, and what do you need to track? Customer: just one shop, around 300 items Bot: That's straightforward. Do you need basic stock tracking and billing, or also purchase orders and supplier management?
The second reply understands "just one" because the bot remembers the question it asked.
Customer sends a WhatsApp message
│
▼
Meta Cloud API
│ POST
▼
Express webhook ──▶ greeting or numbered shortcut? ──yes──▶ instant canned reply
│ │
no │
▼ │
conversation history + business knowledge │
│ │
▼ │
DeepSeek API ──▶ generated reply ──────────────────────────────────┤
▼
Graph API sends it back
- Meta verifies the webhook once through a
GET /webhookchallenge - Incoming messages arrive as
POST /webhook - The server returns
200immediately so Meta does not retry - Greetings and numbered shortcuts are answered from code without an API call
- Everything else goes to the model along with the last few turns of conversation
- The reply is sent back through the Graph API
Conversation
- Natural-language replies generated by DeepSeek, grounded in a detailed business knowledge prompt
- Per-sender conversation memory covering the last five exchanges, so follow-up questions keep their context
- Sessions expire after 30 minutes of silence and are swept from memory automatically
- Sending a greeting starts a clean conversation
- Replies in whatever language the customer writes in, including Roman Urdu
- Numbered catalogue on greeting: six products, each with its real link where one exists
Guardrails
The bot represents a real business, so it is constrained in ways that matter commercially:
- Never invents a URL. Only the links written into the prompt can be shared. Projects without a public link get "Saima will send it directly" rather than a plausible-looking guess.
- Never quotes or agrees to a price. When a customer names a budget, the bot acknowledges it and defers to Saima. It cannot create a commitment on her behalf.
- Never promises a timeline. Same reasoning.
- Never claims a project is live unless a link for it exists in the prompt.
- Speaks as the assistant, not as Saima. "Saima built…", never "I built…".
- Says no where the answer is no. Native mobile, blockchain, and game engines are out of scope and the bot says so plainly instead of agreeing to everything.
Cost control
- Greetings and the six numbered shortcuts are answered from code, never from the model
- Replies capped at 350 tokens — WhatsApp messages should be short anyway
- Conversation history trimmed to the last 10 turns before every call
- Typical cost per AI reply is a fraction of a cent
Reliability
- Immediate
200acknowledgement prevents Meta retry storms - Model failures fall back to a helpful message instead of silence
- Non-text messages (images, documents, voice notes) get a graceful reply rather than crashing
- Malformed webhook payloads are caught and logged
- Timeouts on both outbound APIs
Security
- Every credential lives in an environment variable — nothing is committed
- Webhook verification token checked on every verification attempt
- System instructions are never revealed to the customer
| Layer | Technology |
|---|---|
| Runtime | Node.js 18+ |
| Framework | Express |
| HTTP client | Axios |
| Messaging | WhatsApp Cloud API (Graph API v21.0) |
| AI | DeepSeek (deepseek-chat) |
| Hosting | Railway |
| Method | Route | Purpose |
|---|---|---|
GET |
/ |
Health check |
GET |
/webhook |
Meta verification challenge |
POST |
/webhook |
Receives incoming messages |
| Variable | Where it comes from |
|---|---|
VERIFY_TOKEN |
Any string you choose — must match what you enter in Meta |
WHATSAPP_TOKEN |
Meta dashboard access token |
PHONE_NUMBER_ID |
Meta dashboard, under WhatsApp → API Setup |
DEEPSEEK_API_KEY |
platform.deepseek.com |
PORT |
Set automatically by Railway |
git clone https://github.com/hamidsaima995-bit/SAIMA-BOT.git
cd SAIMA-BOT
npm installCreate a .env file:
VERIFY_TOKEN=your_chosen_string
WHATSAPP_TOKEN=your_meta_access_token
PHONE_NUMBER_ID=your_phone_number_id
DEEPSEEK_API_KEY=your_deepseek_key
PORT=3000
npm startMeta needs a public HTTPS URL to reach the webhook. For local testing, expose the port with a tunnel:
npx ngrok http 3000Then use the ngrok URL plus /webhook as the callback URL in the Meta dashboard.
- Create an app at developers.facebook.com and add the WhatsApp product
- Under WhatsApp → API Setup, copy the access token and phone number ID
- Under WhatsApp → Configuration, set the callback URL to
https://your-domain/webhookand the verify token to whatever you set inVERIFY_TOKEN - Subscribe to the
messageswebhook field - Add your test number under recipients while the app is in development mode
Everything the bot knows lives in one constant near the top of server.js:
const SYSTEM_PROMPT = `You are ... the assistant for ...
=== PRODUCTS AND PROJECTS ===
...
=== LINKS ===
...
=== PRICING ===
...
=== RULES ===
...`;Replace that block and the bot becomes a different business's assistant. The numbered shortcuts in getReply() are the only other place with business-specific text.
To swap DeepSeek for another provider, the model call is isolated in askAI() — one function, one endpoint.
Why memory is a Map rather than a database. Conversations are short and disposable; a WhatsApp enquiry that goes cold after thirty minutes is not worth persisting. A Map with a TTL sweep costs nothing and clears cleanly on redeploy. If sessions ever need to survive a restart, getHistory and remember are the only two functions that would change.
Why shortcuts bypass the model. A greeting has exactly one correct response. Paying for a model call to produce it is waste, and the canned reply arrives faster. The model handles the questions that actually need judgement.
Why the webhook responds before doing any work. Meta retries anything that does not return 200 quickly, which turns one slow reply into several duplicate messages. Acknowledging first and processing after removes that failure mode entirely.
Why links are enumerated rather than described. An earlier version let the model mention project links freely, and it produced a confident, well-formed, entirely fictional URL. Listing the real ones and forbidding construction is the only reliable fix — a model asked to be careful about links will still occasionally invent one.
Why the bot cannot quote prices. The same earlier version was given a budget figure and replied that the amount would cover the full build. That is a commitment a customer can reasonably hold you to. The bot now acknowledges budgets and hands them to a human, every time.
- Persist conversations to Postgres for analytics
- Media message support — read images and documents
- Business hours awareness with an after-hours message
- Handoff to a human when the customer asks for one
- Admin dashboard for editing the knowledge prompt without a deploy
This runs in Meta development mode, which delivers only to numbers registered as test recipients. Production access requires business verification through Meta.
Built by Saima — Ninja Tech