# CRM Step 11 — Integration: WhatsApp Business API ## Context Read `.claude/crm-build-plan.md` for full context and IMPORTANT NOTES. Steps 01–10 must be complete. ## Prerequisites (manual setup required before this step) - A Meta Business account with WhatsApp Business API enabled - A dedicated phone number registered to WhatsApp Business API (NOT a personal number) - A Meta App with webhook configured to point to: `https://yourdomain.com/api/crm/whatsapp/webhook` - The following values ready: `WHATSAPP_PHONE_NUMBER_ID`, `WHATSAPP_ACCESS_TOKEN`, `WHATSAPP_VERIFY_TOKEN` ## Task Receive inbound WhatsApp messages via webhook and send outbound messages, all logged to crm_comms_log. ## Backend changes ### 1. Add to `backend/config.py` ```python whatsapp_phone_number_id: str = "" whatsapp_access_token: str = "" whatsapp_verify_token: str = "change-me" # you set this in Meta webhook config ``` ### 2. Create `backend/crm/whatsapp.py` ```python async def send_whatsapp(to_phone: str, message: str) -> str: """ POST to https://graph.facebook.com/v19.0/{phone_number_id}/messages Headers: Authorization: Bearer {access_token} Body: { messaging_product: "whatsapp", to: to_phone, type: "text", text: { body: message } } Returns the wamid (WhatsApp message ID). """ ``` ### 3. Add webhook + send endpoints to `backend/crm/router.py` `GET /api/crm/whatsapp/webhook` — Meta webhook verification. Check `hub.verify_token` == settings.whatsapp_verify_token. Return `hub.challenge` if valid, else 403. **No auth required on this endpoint.** `POST /api/crm/whatsapp/webhook` — Receive inbound message events from Meta. **No auth required on this endpoint.** Parse payload: ``` entry[0].changes[0].value.messages[0] .from → sender phone number (e.g. "306974015758") .id → wamid .type → "text" .text.body → message content .timestamp → unix timestamp ``` For each message: 1. Look up customer by phone number in crm_customers contacts (where type=phone or whatsapp) 2. If found: create crm_comms_log entry (type=whatsapp, direction=inbound, ext_message_id=wamid) 3. If not found: still log it but with customer_id="unknown:{phone}" `POST /api/crm/whatsapp/send` Body: `{ customer_id, to_phone, message }` Requires auth. → calls `send_whatsapp(...)`, creates outbound comms_log entry ## Frontend changes ### Update Comms tab in `CustomerDetail.jsx` - WhatsApp entries: green background, WhatsApp icon - "Send WhatsApp" button → modal with: to_phone (pre-filled from customer's whatsapp/phone contacts), message textarea - On send: POST `/api/crm/whatsapp/send` ### Update `InboxPage.jsx` - WhatsApp entries are already included (from crm_comms_log) - Add type filter option for "WhatsApp" ## Notes - Phone number format: Meta sends numbers without `+` (e.g. "306974015758"). Normalize when matching against customer contacts (strip `+` and spaces). - Webhook payload can contain multiple entries and messages — iterate and handle each - Rate limits: Meta free tier = 1000 conversations/month (a conversation = 24h window with a customer). More than enough. - If whatsapp_phone_number_id is empty, the send endpoint returns 503. The webhook endpoint must always be available (it's a public endpoint). - Media messages (images, docs): in this step, just log "Media message received" as body text. Full media download is a future enhancement.