Skip to main content

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​

ConditionModeSource
WHATSAPP_TOKEN not setdemoReturns MOCK_WA_MESSAGES (5 sample Mumbai reports)
Token set, no messages in queuedemoFalls back to mock data
Token set + messages in webhook queueliveReturns 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):

ParamTypeDescription
hub.modestringMust be subscribe
hub.verify_tokenstringMust match WHATSAPP_VERIFY_TOKEN env var
hub.challengestringRandom 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:

ParamTypeDefaultNotes
countnumber20Clamped 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:

ParamTypeNotes
messageIdstring (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"
}
FieldTypeRequiredConstraints
titlestringβœ…5–200 characters
descriptionstringβœ…10–5000 characters
categorystringβœ…traffic | road | infrastructure | hygiene | healthcare | systemic
citystringβœ…2–100 characters
prioritystringβ€”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)​

  1. Go to developers.facebook.com β†’ Your App β†’ WhatsApp β†’ Configuration
  2. Set Webhook URL to https://your-domain.com/api/whatsapp/webhook
  3. Set Verify Token to match WHATSAPP_VERIFY_TOKEN in your .env
  4. Subscribe to the messages field
  5. Meta will call GET /api/whatsapp/webhook β€” you should see 200 in your logs

Environment Variables​

VariableRequiredDefaultDescription
WHATSAPP_TOKENβ€”(none)Meta access token. Without this, app runs in demo mode
WHATSAPP_VERIFY_TOKENβ€”civicpulse_wa_verifyWebhook verification secret
WHATSAPP_PHONE_NUMBER_IDβ€”demo_phonePhone Number ID from Meta Business dashboard
VITE_WA_CHANNEL_NAMEβ€”Mumbai Civic SenseDisplay 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