A Node.js + TypeScript backend that integrates with WhatsApp Cloud API (Meta) to manage ecommerce products via WhatsApp messages. The system receives WhatsApp messages, interprets them as admin commands, and performs operations on a Neon PostgreSQL database.
See QUICK_START.md for common mistakes and correct message format!
Important: Messages must start with the command name (e.g., add_product) and use key=value format, NOT Key: value format.
- ✅ WhatsApp webhook integration (Meta Cloud API)
- ✅ Product management (Add, Update, Delete)
- ✅ Product reports (Top products, Low stock)
- ✅ Excel export for sales data
- ✅ Real-time WhatsApp message replies
- Node.js 18+ and npm
- Neon PostgreSQL database
- Meta WhatsApp Business Account with Cloud API access
- ngrok (for local development)
- Install dependencies:
npm install- Create a
.envfile in the root directory:
DATABASE_URL=postgresql://user:password@host.neon.tech/dbname?sslmode=require
WHATSAPP_TOKEN=your_whatsapp_access_token
WHATSAPP_PHONE_ID=your_phone_number_id
WHATSAPP_VERIFY_TOKEN=your_custom_verify_token
PORT=3000- Build the project:
npm run build- Start the server:
npm start
# or for development
npm run dev📖 See NGROK_SETUP.md for detailed instructions on finding your callback URL!
- Start your server:
npm run dev- In another terminal, start ngrok:
ngrok http 3000- Find your callback URL:
- Look at ngrok output for:
Forwarding https://xxx.ngrok-free.app -> http://localhost:3000 - Or open ngrok dashboard: http://127.0.0.1:4040
- Copy the HTTPS URL (e.g.,
https://abc123.ngrok-free.app) - Add
/webhookat the end:https://abc123.ngrok-free.app/webhook
- Look at ngrok output for:
- Go to Meta for Developers
- Select your WhatsApp app
- Go to Configuration → Webhooks
- Click Edit on the webhook subscription
- Set Callback URL:
https://your-ngrok-url.ngrok.io/webhook - Set Verify Token: (same as
WHATSAPP_VERIFY_TOKENin your.env) - Subscribe to
messagesfield - Save changes
The webhook should verify automatically. Check your server logs for:
✅ Webhook verified successfully
- Add the WhatsApp Business number to your contacts
- Send messages directly from your WhatsApp app
- The system will automatically process and reply
- Go to Graph API Explorer
- Select your app
- Use POST method with endpoint:
/{PHONE_ID}/messages - Add your access token
- Use this body:
{
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "YOUR_PHONE_NUMBER",
"type": "text",
"text": {
"body": "add_product\ntitle=Test Product\nslug=test-product\nprice=999\nstock=10"
}
}curl -X POST "https://graph.facebook.com/v21.0/{PHONE_ID}/messages" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "YOUR_PHONE_NUMBER",
"type": "text",
"text": {
"body": "add_product\ntitle=Test Product\nslug=test-product\nprice=999\nstock=10"
}
}'All commands are case-insensitive. Use newlines to separate command and parameters.
add_product), then use key=value format (NOT Key: value)
Format:
add_product
title=<product-title>
slug=<product-slug>
brand=<brand-name>
material=<material>
price=<price>
stock=<stock-quantity>
image_url=<image-url>
Example (Copy this exact format):
add_product
title=Cotton T-Shirt
slug=cotton-tshirt
brand=Nike
material=Cotton
price=599.99
stock=50
image_url=https://example.com/image.jpg
❌ WRONG Format (Don't do this):
Title: Cotton T-Shirt
Slug: cotton-tshirt
Price: ₹599.99
✅ CORRECT Format:
add_product
title=Cotton T-Shirt
slug=cotton-tshirt
price=599.99
Required Fields:
title- Product titleslug- Unique URL-friendly slugprice- Product price (number)stock- Stock quantity (number)
Optional Fields:
brand- Brand namematerial- Material typeimage_url- Product image URL (can also useimageurlorimage)
Response:
✅ Product Added Successfully
Title: Cotton T-Shirt
Slug: cotton-tshirt
Brand: Nike
Material: Cotton
Price: ₹599.99
Stock: 50
Image: https://example.com/image.jpg
Format:
update_product
slug=<product-slug>
price=<new-price>
or
update_product
slug=<product-slug>
stock=<new-stock>
or
update_product
slug=<product-slug>
image_url=<new-image-url>
or any combination:
update_product
slug=<product-slug>
price=<new-price>
stock=<new-stock>
image_url=<new-image-url>
Example:
update_product
slug=cotton-tshirt
price=699.99
or
update_product
slug=cotton-tshirt
stock=75
or
update_product
slug=cotton-tshirt
image_url=https://example.com/new-image.jpg
Required Fields:
slug- Product slug to update- At least one of:
price,stock, orimage_url
Optional Fields:
price- New product pricestock- New stock quantityimage_url- New product image URL (can also useimageurlorimage)
Response:
✅ Product Updated Successfully
Title: Cotton T-Shirt
Slug: cotton-tshirt
Price: ₹699.99
Stock: 75
Image: https://example.com/new-image.jpg
Format:
delete_product
slug=<product-slug>
Example:
delete_product
slug=cotton-tshirt
Required Fields:
slug- Product slug to delete
Response:
✅ Product with slug "cotton-tshirt" deleted successfully.
Format:
report_top_products
Example:
report_top_products
Response:
📊 *Top Products (Highest Stock)*
1. *Cotton T-Shirt*
Slug: cotton-tshirt
Price: ₹599.99
Stock: 50
Brand: Nike
2. *Denim Jeans*
Slug: denim-jeans
Price: ₹1299.99
Stock: 30
Brand: Levi's
...
Format:
report_low_products
Example:
report_low_products
Response:
⚠️ *Low Stock Products*
1. *Running Shoes*
Slug: running-shoes
Price: ₹2999.99
Stock: 3
Brand: Adidas
2. *Baseball Cap*
Slug: baseball-cap
Price: ₹499.99
Stock: 5
Brand: Nike
...
Note: Shows products with stock ≤ 10 units.
Format:
export_sales_excel
Example:
export_sales_excel
Response:
✅ Sales report generated successfully!
File: sales-report-2024-01-15.xlsx
Size: 45.23 KB
⚠️ Note: Document upload requires media storage configuration.
Please contact administrator to set up document delivery.
Note: Excel file generation is implemented. Document upload via WhatsApp requires additional media storage setup (S3, Cloudinary, etc.).
Copy and paste these messages in WhatsApp to test:
add_product
title=Test Product
slug=test-product-123
brand=Test Brand
material=Cotton
price=999.99
stock=25
image_url=https://example.com/test-image.jpg
update_product
slug=test-product-123
price=1199.99
update_product
slug=test-product-123
stock=50
delete_product
slug=test-product-123
report_top_products
report_low_products
export_sales_excel
The system will send error messages if something goes wrong:
❌ Invalid message format.- Message format is incorrect (sends when message can't be parsed)❌ Invalid message: <error>- Command validation failed❌ Missing required parameter: <field>- Required field is missing❌ Product with slug "<slug>" already exists.- Duplicate slug❌ Product with slug "<slug>" not found.- Product doesn't exist❌ Unknown command: <command>- Command not recognized❌ Error: <error-message>- General error with details
Note: When you send an invalid message, the system will respond with "❌ Invalid message format" and show available commands.
omnivisio/
├── src/
│ ├── config/
│ │ ├── neon.ts # Database connection
│ │ └── whatsapp.config.ts # WhatsApp configuration
│ ├── controllers/
│ │ └── whatsapp.controller.ts # Webhook handlers
│ ├── services/
│ │ ├── whatsapp.service.ts # Message processing & replies
│ │ ├── product.service.ts # Product CRUD operations
│ │ └── report.service.ts # Reports & Excel export
│ ├── routes/
│ │ └── whatsapp.routes.ts # Webhook routes
│ ├── utils/
│ │ └── messageParser.ts # Command parser
│ ├── db/
│ │ └── init.ts # Database initialization
│ ├── app.ts # Express app setup
│ └── server.ts # Server entry point
├── package.json
├── tsconfig.json
└── README.md
The system uses existing tables from your database:
id(UUID)title(VARCHAR)slug(VARCHAR, unique)brand(VARCHAR, nullable)material(VARCHAR, nullable)is_active(BOOLEAN)created_at,updated_at
id(UUID)product_id(UUID, foreign key)sku(VARCHAR, unique)price(DECIMAL)stock_quantity(INTEGER)created_at,updated_at
-- Check product details
SELECT
id,
title,
slug,
brand,
material,
is_active,
created_at,
updated_at
FROM products
WHERE slug = 'cotton-tshirt';-- Check product with its variant details
SELECT
p.id as product_id,
p.title,
p.slug,
p.brand,
p.material,
p.is_active,
pv.id as variant_id,
pv.sku,
pv.price,
pv.stock_quantity,
p.created_at as product_created,
pv.created_at as variant_created
FROM products p
LEFT JOIN product_variants pv ON pv.product_id = p.id
WHERE p.slug = 'cotton-tshirt';-- View all recently added products with variants
SELECT
p.title,
p.slug,
p.brand,
p.material,
pv.price,
pv.stock_quantity,
pv.sku,
p.created_at
FROM products p
LEFT JOIN product_variants pv ON pv.product_id = p.id
ORDER BY p.created_at DESC
LIMIT 10;-- Check variant details for a specific product
SELECT
pv.id,
pv.product_id,
pv.sku,
pv.price,
pv.stock_quantity,
p.title as product_title,
p.slug as product_slug
FROM product_variants pv
JOIN products p ON p.id = pv.product_id
WHERE p.slug = 'cotton-tshirt';-- Complete verification query - should return 1 row if added correctly
SELECT
p.title,
p.slug,
p.brand,
p.material,
p.is_active,
pv.price,
pv.stock_quantity,
pv.sku,
CASE
WHEN p.title = 'Cotton T-Shirt' THEN '✅ Title matches'
ELSE '❌ Title mismatch'
END as title_check,
CASE
WHEN p.slug = 'cotton-tshirt' THEN '✅ Slug matches'
ELSE '❌ Slug mismatch'
END as slug_check,
CASE
WHEN p.brand = 'Nike' THEN '✅ Brand matches'
ELSE '❌ Brand mismatch'
END as brand_check,
CASE
WHEN p.material = 'Cotton' THEN '✅ Material matches'
ELSE '❌ Material mismatch'
END as material_check,
CASE
WHEN pv.price = 599.99 THEN '✅ Price matches'
ELSE '❌ Price mismatch'
END as price_check,
CASE
WHEN pv.stock_quantity = 50 THEN '✅ Stock matches'
ELSE '❌ Stock mismatch'
END as stock_check
FROM products p
LEFT JOIN product_variants pv ON pv.product_id = p.id
WHERE p.slug = 'cotton-tshirt';-- Count total products in database
SELECT COUNT(*) as total_products FROM products;-- Count products that have variants
SELECT
COUNT(DISTINCT p.id) as products_with_variants,
COUNT(pv.id) as total_variants
FROM products p
LEFT JOIN product_variants pv ON pv.product_id = p.id;- Check ngrok is running and URL is correct
- Verify webhook URL in Meta Developer Console
- Check
WHATSAPP_VERIFY_TOKENmatches in both places - Ensure server is running and accessible
- Verify
DATABASE_URLin.envis correct - Check Neon database is accessible
- Ensure SSL mode is set correctly for Neon
- Check server logs for errors
- Verify WhatsApp token has correct permissions
- Ensure phone number format is correct (with country code, no +)
- Excel generation works, but document upload requires media storage setup
- For now, the system sends a message with file details
- To enable document upload, implement media storage (S3/Cloudinary) and update
uploadMediaToWhatsAppfunction
npm run devnpm run build
npm startAll operations are logged to the console with detailed information. Watch your backend console to verify operations:
📦 [ADD PRODUCT] Starting product creation: { title, slug, brand, ... }
✅ [ADD PRODUCT] Product created with ID: <uuid>
✅ [ADD PRODUCT] Variant created with ID: <uuid>, SKU: <sku>
✅ [ADD PRODUCT] Image added: <url> (if image provided)
✅ [ADD PRODUCT] Transaction committed successfully for product: <title> (<slug>)
🔄 [UPDATE PRODUCT] Starting product update: { slug, price, stock, ... }
✅ [UPDATE PRODUCT] Product found: <title> (ID: <uuid>)
🔄 [UPDATE PRODUCT] Updating price to: <price>
🔄 [UPDATE PRODUCT] Updating stock to: <stock>
✅ [UPDATE PRODUCT] Variant updated successfully
✅ [UPDATE PRODUCT] Image updated: <url> (if image provided)
✅ [UPDATE PRODUCT] Transaction committed successfully for product: <slug>
🗑️ [DELETE PRODUCT] Attempting to delete product with slug: <slug>
📋 [DELETE PRODUCT] Found product: <title> (ID: <uuid>)
✅ [DELETE PRODUCT] Product deleted successfully: <title> (<slug>)
❌ [ADD PRODUCT] Error: <error message>
❌ [UPDATE PRODUCT] Product with slug "<slug>" not found
❌ Invalid message format - sending error response
All operations are logged to console with emoji indicators:
- ✅ Success
- ❌ Error
⚠️ Warning- 📊 Report/Data
- 📦 Add Product
- 🔄 Update Product
- 🗑️ Delete Product
- Never commit
.envfile to version control - Keep
WHATSAPP_TOKENsecure - Use strong
WHATSAPP_VERIFY_TOKEN - Validate all inputs before database operations
- Use HTTPS in production (ngrok provides this for development)
For issues or questions:
- Check server logs for detailed error messages
- Verify all environment variables are set correctly
- Ensure database tables exist and are accessible
- Test webhook with Meta's webhook tester
ISC