82 lines
3.3 KiB
Markdown
82 lines
3.3 KiB
Markdown
# 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.
|