Files
bellsystems-cp/.claude/crm-step-11.md

3.3 KiB
Raw Blame History

CRM Step 11 — Integration: WhatsApp Business API

Context

Read .claude/crm-build-plan.md for full context and IMPORTANT NOTES. Steps 0110 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

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

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.