How to: WhatsApp E-commerce Flow — Starter Kit
How to: WhatsApp E-commerce Flow — Starter Kit
Full technical series: iZND Technical Blog
Parts 1–6 — WhatsApp Webhooks ▸ Idempotency ▸ Orders ▸ Stripe ▸ Docker ▸ Netlify Serverless
This repository contains a production-ready WhatsApp E-commerce Backend designed to bridge the gap between the WhatsApp Business Cloud API and automated order fulfillment. It covers every layer — from webhook security to payment confirmation messages sent back to the customer.
🚀 Key Features
- Robust Webhook Handling: Processes WhatsApp text messages and interactive messages (Product List Selection, Product detail clicks).
- Security First: Cryptographic
x-hub-signature-256verification on all WhatsApp and Stripe webhooks. - Idempotency: Redis-backed event deduplication prevents double-orders if WhatsApp retries a message delivery.
- Zero-Oversell Inventory:
- Atomic Reservations: Redis Lua script (
scripts/reserve.lua) for concurrency-safe, TTL-controlled stock holds. - Reconciliation Cron:
scripts/cleanup-reservations.jsscans expired reservations and marks pending orders as expired.
- Atomic Reservations: Redis Lua script (
- Payment Integration: Stripe Checkout session created per order; confirmation WhatsApp template sent automatically on
checkout.session.completed. - Dual Netlify Mode:
create-order-proxysupportsBACKEND_MODE=proxy(forward to dedicated server) orBACKEND_MODE=inline(run DB logic directly in function). - Flexible Deployment: Docker, Netlify Functions, or cPanel/VPS — all covered.
📂 Repository Structure
/
├── netlify/
│ └── functions/
│ ├── webhook.js # Serverless WhatsApp entry point (Part 5)
│ ├── payment-callback.js # Stripe webhook handler (Part 5)
│ └── create-order-proxy.js # Dual-mode order proxy (Part 6)
├── src/
│ └── index.js # Core Express app — full implementation (Part 4)
├── migrations/
│ └── migration.sql # UUID & JSONB-optimised Postgres schema (Part 4)
├── scripts/
│ ├── cleanup-reservations.js # Redis reconciliation & stock release cron (Part 5)
│ └── reserve.lua # Atomic Redis Lua reservation script (Part 5)
├── templates/
│ └── whatsapp_templates.md # Pre-formatted message templates
├── Dockerfile # Alpine-based production image (Part 5)
├── docker-compose.yml # Full-stack local environment (Part 5)
├── postman_collection.json # End-to-end test suite
├── .env.example # All required environment variables
└── README.md
🛠️ Quick Start
1. Local Development (Docker Compose)
git clone <repo-url>
cd "WhatsApp E-commerce Flow"
# Copy and fill environment variables
cp .env.example .env
# Build and launch all services (Express app + Postgres + Redis)
docker compose up --build
# Apply the database schema
docker compose exec db psql -U postgres -d wa -f /app/migrations/migration.sql
Server available at http://localhost:3000.
2. Run Cleanup Script (manual or cron)
node scripts/cleanup-reservations.js
Schedule this via system cron or Netlify Scheduled Functions to automatically release expired stock holds.
3. Test with Postman
- Import
postman_collection.jsoninto Postman. - Set environment variables:
ACCESS_TOKEN,PHONE_NUMBER_ID,CATALOG_ID,TO_WA,BASE_URL,VERIFY_TOKEN. - Run requests in order:
- WhatsApp — Send Product List → triggers the shopping flow.
- Backend — Webhook Verification (
GET /webhook) → confirm token. - Backend — Create Order (
POST /create-order) → provisional order + Stripe session. - Backend — Payment Callback (
POST /payment-callback) → simulate Stripe success. - Backend — Catalog Sync (
POST /catalog-sync) → placeholder for future catalog push.
🔑 Environment Variables
Copy .env.example to .env. Never commit secrets.
# Server
PORT=3000
BASE_URL=https://your-app-url.com
# Postgres
DATABASE_URL=postgres://user:pass@host:5432/db
# Redis
REDIS_URL=redis://redis:6379
# WhatsApp Cloud API
WHATSAPP_PHONE_NUMBER_ID=1234567890
WHATSAPP_ACCESS_TOKEN=EAA...
WHATSAPP_APP_SECRET=your_whatsapp_app_secret
VERIFY_TOKEN=your_webhook_verify_token
# Stripe
STRIPE_SECRET=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Netlify Function Mode
BACKEND_MODE=proxy # 'proxy' or 'inline'
BACKEND_URL=https://your-main-backend.com # only if BACKEND_MODE=proxy
# Optional
LOG_LEVEL=info
🔒 Security & Idempotency
| Mechanism | Implementation |
|---|---|
| WhatsApp Signature | x-hub-signature-256 verified via crypto.timingSafeEqual on raw body. |
| Stripe Signature | stripe.webhooks.constructEvent with raw body required. |
| Idempotency | Every wamid.* and Stripe event ID stored in Redis for 24 h to prevent re-processing. |
| Stock Safety | Redis Lua script atomically checks and decrements stock; TTL auto-expires stale holds. |
📦 Deployment Options
Option A — Netlify (Serverless)
Connect this repo to your Netlify site.
Go to Site settings → Environment variables and set all keys from
.env.example.Set
BACKEND_MODE:proxy— recommended for production; Netlify functions forward to a dedicated Express server.inline— all logic runs inside the function; suitable for low traffic, accepts function time limits.
Register your Stripe webhook endpoint:
https://<site>.netlify.app/.netlify/functions/payment-callbackFor the reservation cleanup, use Netlify Scheduled Functions or an external cron to run
cleanup-reservations.jsperiodically.
Option B — cPanel / VPS (Traditional)
Upload files via SFTP or Git.
Set environment variables in cPanel Node App Manager or via
.env.Entry point:
src/index.js.Use
pm2(if available) for process management:pm2 start src/index.js --name whatsapp-commerceEnsure your Postgres and Redis instances are reachable from the cPanel server.
Option C — Docker (Self-hosted / VPS)
docker compose up --build -d
All services (app, db, redis) are wired together via docker-compose.yml. Redis is configured with persistence (--save 60 1).
🗄️ Database Schema
The schema uses UUID primary keys and JSONB for flexible item and metadata storage:
| Table | Purpose |
|---|---|
products | Product catalogue with SKU, price, currency |
variants | Product variants (size, colour) with price delta |
orders | All orders with payment & fulfillment statuses |
reservations | (Optional) Long-term reservation tracking |
Run migrations/migration.sql against your Postgres database to initialise.
🔄 Order Flow
Customer → WhatsApp message
→ Webhook verified (signature + idempotency)
→ Product selected (text intent or interactive reply)
→ Provisional order created in Postgres
→ Stock reserved in Redis (10-minute TTL)
→ Stripe Checkout session created
→ Customer receives payment link via WhatsApp
→ Customer completes payment
→ Stripe fires checkout.session.completed
→ Order marked 'paid'; stock hold released
→ Order confirmation template sent via WhatsApp
❓ FAQ
Why a proxy function?
Keeps heavy database queries off Netlify serverless functions to avoid the 10-second execution limit. Set BACKEND_MODE=proxy and point BACKEND_URL to your Express server.
How do you prevent overselling?
Redis reservations with a short TTL (10 min) act as a hold. The atomic reserve.lua Lua script checks and decrements stock in a single Redis transaction — no race conditions.
How do I submit WhatsApp message templates?
Pre-submit order_confirmation and shipping_update templates in the Meta Business Suite under WhatsApp → Message Templates. Allow 24–48 h for approval before going live.
How do I clean up expired orders?
Run scripts/cleanup-reservations.js on a schedule. It scans reservations_by_sku:* sets in Redis, finds expired keys, removes them from the index, and marks the corresponding pending orders as expired in Postgres.
📋 Checklist Before Go-Live
- [ ] WhatsApp App Secret set in
WHATSAPP_APP_SECRET - [ ] Stripe webhook registered with raw-body endpoint
- [ ]
order_confirmationtemplate approved by Meta - [ ] Redis and Postgres reachable from your deploy target
- [ ] Cleanup cron configured (system cron, Netlify Scheduled Function, or external)
- [ ]
BACKEND_MODEset correctly for your deployment model - [ ]
.envnot committed to version control
📜 License
MIT — fork freely for client projects. Open a PR or issue for contributions.
Technical documentation based on the iZND WhatsApp E-commerce series (Parts 1–6).