WhatsApp Business API
CivicPulse integrates with the Meta WhatsApp Business Cloud API to receive civic reports sent to the "Mumbai Civic Sense" WhatsApp Business number and convert them into trackable issues.
Demo vs Live Modeβ
| Condition | Mode | Source |
|---|---|---|
WHATSAPP_TOKEN not set | demo | Returns MOCK_WA_MESSAGES (5 sample Mumbai reports) |
| Token set, no messages in queue | demo | Falls back to mock data |
| Token set + messages in webhook queue | live | Returns real messages from in-memory queue |
Endpointsβ
GET /api/whatsapp/webhookβ
Meta webhook verification handshake. Called once by Meta when you register the webhook URL.
Query params (sent by Meta):
| Param | Type | Description |
|---|---|---|
hub.mode | string | Must be subscribe |
hub.verify_token | string | Must match WHATSAPP_VERIFY_TOKEN env var |
hub.challenge | string | Random string Meta expects back |
Response 200: Plain text β the value of hub.challenge
Response 403: { "error": "Webhook verification failed" } β token mismatch
Example (Meta calls this automatically):
GET /api/whatsapp/webhook?hub.mode=subscribe&hub.verify_token=civicpulse_wa_verify&hub.challenge=abc123
β 200 abc123
POST /api/whatsapp/webhookβ
Receives incoming WhatsApp messages from Meta Cloud API.
Authentication: None required β Meta calls this directly. When WHATSAPP_TOKEN is set, the x-hub-signature-256 header is validated (HMAC-SHA256 of the payload body using the token as secret). Requests with an invalid or missing signature are rejected with 403.
Request body: Standard Meta WhatsApp Business Cloud API webhook payload:
{
"object": "whatsapp_business_account",
"entry": [{
"changes": [{
"field": "messages",
"value": {
"messages": [{ "id": "wamid.xx", "type": "text", "text": { "body": "Pothole on SV Road" }, "from": "919876543210", "timestamp": "1709654400" }],
"contacts": [{ "wa_id": "919876543210", "profile": { "name": "Ramesh K." } }]
}
}]
}]
}
Response 200: { "status": "ok" } β always sent immediately (Meta requires < 5 s)
Response 403: { "error": "Invalid signature" } β HMAC mismatch (only when WHATSAPP_TOKEN is set)
Supported message types: text, image (stores mediaId), video (stores mediaId), location
GET /api/whatsapp/messagesβ
Returns recent WhatsApp messages for the Social Wall WhatsApp tab.
Authentication: None required
Query params:
| Param | Type | Default | Notes |
|---|---|---|---|
count | number | 20 | Clamped to 10β50 |
Response 200:
{
"messages": [
{
"id": "wa1",
"text": "Massive pothole on SV Road near Andheri East. #MumbaiRoads",
"from": "+919876543210",
"fromName": "Ramesh K.",
"timestamp": "3 min ago",
"category_hint": "road",
"city_hint": "Mumbai",
"images": [{ "id": "wi1", "url": "...", "type": "image", "source": "whatsapp", "caption": "..." }],
"videos": [],
"location": null
}
],
"mode": "demo",
"phoneNumberId": "demo_phone"
}
Headers: Cache-Control: no-store
POST /api/whatsapp/messages/:messageId/convertβ
Convert a WhatsApp message into a CivicPulse issue.
Authentication: Bearer token required (Authorization: Bearer <accessToken>)
Path params:
| Param | Type | Notes |
|---|---|---|
messageId | string (1β100 chars) | WA message ID to associate with the issue |
Request body:
{
"title": "Pothole on SV Road near Andheri East",
"description": "Large pothole causing danger to vehicles. Needs urgent repair.",
"category": "road",
"city": "Mumbai",
"priority": "high"
}
| Field | Type | Required | Constraints |
|---|---|---|---|
title | string | β | 5β200 characters |
description | string | β | 10β5000 characters |
category | string | β | traffic | road | infrastructure | hygiene | healthcare | systemic |
city | string | β | 2β100 characters |
priority | string | β | low | medium (default) | high |
Response 201:
{
"issueId": "ISS-042",
"title": "Pothole on SV Road near Andheri East",
"status": "open",
"source": "whatsapp",
"messageId": "wa1",
"messageUrl": null
}
Response 401: Missing or invalid Bearer token
Response 422: Validation error β { "errors": [...] }
Webhook Setup (Meta Developer Portal)β
- Go to developers.facebook.com β Your App β WhatsApp β Configuration
- Set Webhook URL to
https://your-domain.com/api/whatsapp/webhook - Set Verify Token to match
WHATSAPP_VERIFY_TOKENin your.env - Subscribe to the messages field
- Meta will call
GET /api/whatsapp/webhookβ you should see200in your logs
Environment Variablesβ
| Variable | Required | Default | Description |
|---|---|---|---|
WHATSAPP_TOKEN | β | (none) | Meta access token. Without this, app runs in demo mode |
WHATSAPP_VERIFY_TOKEN | β | civicpulse_wa_verify | Webhook verification secret |
WHATSAPP_PHONE_NUMBER_ID | β | demo_phone | Phone Number ID from Meta Business dashboard |
VITE_WA_CHANNEL_NAME | β | Mumbai Civic Sense | Display name shown in Social Wall tab |
Dev Test Pageβ
Navigate to /test-wa (or /test-whatsapp) for a standalone test environment:
- Live message feed from
GET /api/whatsapp/messages - Inline convert panel per message
- ManualInput with real-time category/city detection
- Converted Issues session log
- API endpoint reference + env var cheatsheet