WhatsApp catalog bot
A bot that shows your products on WhatsApp and captures orders into a database.
- Node.js
- Express
- WhatsApp Cloud API
- Postgres
Telugu lo nerchuko · Watch in Telugu
The menu project sent orders as a one-way message. This project goes the other way: a real bot on the WhatsApp Cloud API that replies to customers, shows a catalog, and writes each order into a database. It is the first project with a server you actually run.
Overview
You will build a webhook server that receives WhatsApp messages, replies with product listings, walks the customer through picking items, and stores the finished order. This teaches the webhook pattern, conversational state, and persisting structured data — the backbone of every messaging product.
The problem
Customers already live in WhatsApp; making them install an app loses most of them. But a human replying to every "what do you have?" does not scale and is slow after hours. A bot answers instantly, shows the catalog, and records the order so nothing is lost in a busy chat.
Architecture
WhatsApp sends incoming messages to your webhook. Your server interprets the message, looks up products, updates the conversation state, replies through the Cloud API, and writes confirmed orders to Postgres.
Customer --> WhatsApp Cloud API --> webhook (Express)
| read/write
Postgres (products, sessions, orders)
|
reply <-- Cloud API send-message endpointTech stack
- Node.js + Express for the webhook server
- WhatsApp Cloud API (Meta) for sending and receiving
- Postgres for products, session state, and orders
- A hosting box or platform with a public HTTPS URL for the webhook
Build steps
- Verify the webhook. Meta sends a GET challenge you must echo back.
import express from "express";
const app = express();
app.use(express.json());
const VERIFY_TOKEN = process.env.WA_VERIFY_TOKEN;
app.get("/webhook", (req, res) => {
const { "hub.mode": mode, "hub.verify_token": token, "hub.challenge": challenge } = req.query;
if (mode === "subscribe" && token === VERIFY_TOKEN) return res.send(challenge);
res.sendStatus(403);
});- Receive messages and route by intent.
app.post("/webhook", async (req, res) => {
res.sendStatus(200); // ack fast; process after
const msg = req.body.entry?.[0]?.changes?.[0]?.value?.messages?.[0];
if (!msg) return;
const from = msg.from;
const text = (msg.text?.body ?? "").trim().toLowerCase();
if (text === "menu" || text === "hi") return sendCatalog(from);
if (/^\d+$/.test(text)) return addToOrder(from, Number(text));
if (text === "done") return confirmOrder(from);
});- Send a reply via the Cloud API.
async function sendText(to, body) {
await fetch(`https://graph.facebook.com/v21.0/${process.env.WA_PHONE_ID}/messages`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WA_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ messaging_product: "whatsapp", to, text: { body } }),
});
}- Persist the confirmed order.
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
wa_number TEXT NOT NULL,
items JSONB NOT NULL,
total_inr INTEGER NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);Source code
Starter with schema, webhook, and a seed catalog: AnythingWithSandy/wa-catalog-bot.
Demo
Live demos need a verified WhatsApp Business number, so there is no public demo. Use the Cloud API test number from the Meta dashboard to try it before going live.
Deployment
Deploy the Express app to any host that gives you a public HTTPS URL, set the webhook URL in the Meta app dashboard, and store WA_TOKEN, WA_PHONE_ID, and WA_VERIFY_TOKEN as environment secrets. Keep the access token server-side only.
Bonus challenge
Add an LLM layer (Week 2's API lesson) so free-text messages like "do you have anything under 200 rupees" get parsed into a catalog query, with structured output (Week 2) mapping the reply back to real products. Add an owner dashboard listing the day's orders.