WhatsApp Business Cloud API
WhatsApp Business Cloud API
Overview
Official API for businesses to send and receive messages on WhatsApp. Integrated via a thin FastAPI gateway that routes messages to Rivet actors.
Integration Architecture
graph TB
subgraph "WhatsApp Side"
USER[User Phone]
WS[WhatsApp Servers]
end
subgraph "Our Side"
subgraph "FastAPI Gateway"
WH[Webhook Endpoint]
VERIFY[Signature Verification]
ROUTE[Route to Rivet Actor]
end
subgraph "Rivet Actor"
ACTOR[ShoppingAgentActor]
FMT[WhatsApp Formatter]
end
API[WhatsApp Business API]
end
USER -->|sends message| WS
WS -->|webhook POST| WH
WH --> VERIFY
VERIFY --> ROUTE
ROUTE --> ACTOR
ACTOR --> FMT
FMT --> API
API --> WS
WS --> USER
style WH fill:#fff9c4
style API fill:#25d366
style ACTOR fill:#c8e6c9,stroke:#388e3c
The Gateway Is Minimal
from fastapi import FastAPI, Request, BackgroundTasks
app = FastAPI()
@app.post("/webhook")
async def webhook(request: Request, background_tasks: BackgroundTasks):
body = await request.json()
for msg in extract_messages(body):
user_id = msg["sender"]
actor = await rivet.get_actor(ShoppingAgentActor, user_id)
background_tasks.add_task(actor.on_message, msg["text"])
return {"status": "ok"} # Immediate 200 to WhatsApp
The gateway does NOT:
- Process messages
- Manage agent state
- Format responses
- Call LLMs or Shopify
It only:
- Verifies webhook signature
- Extracts user_id + text
- Gets/creates Rivet actor for user
- Sends message to actor
- Returns 200 OK
The actor does everything else, including sending the final response via WhatsApp.
Message Flow
sequenceDiagram
participant U as User
participant W as WhatsApp
participant G as FastAPI
participant A as Actor
participant F as Formatter
U->>W: Sends message
W->>G: Webhook POST
G-->>W: 200 OK (instant)
G->>A: on_message(text)
A->>A: Process + generate response
A->>F: Format for WhatsApp
F->>W: POST /messages (send reply)
W->>U: Deliver message
Key Capabilities
Sending Messages
- Text messages (4096 char limit)
- Media (images, videos, documents)
- Interactive messages (buttons, lists)
- Templates (for notifications)
Receiving Messages
- Webhook-based delivery
- Real-time processing
- Message status updates (delivered, read)
Message Types for Shopping
graph LR
subgraph "Product Results"
LIST[List Message<br/>Multiple products]
PROD[Product Card<br/>Single product]
end
subgraph "Cart/Orders"
BTN[Button Message<br/>Actions]
TEXT[Text Message<br/>Status updates]
end
subgraph "Policies/Help"
TEXT2[Text Message<br/>Formatted help]
end
Setup Requirements
- Facebook Business account — business.facebook.com
- WhatsApp Business account — Business Manager → WhatsApp → Add phone number
- WhatsApp Cloud API access — Apps → Create App → Business → WhatsApp
- Webhook verification — Token + callback URL
- Phone number verification — For production use
Rate Limits
| Limit | Value |
|---|---|
| Messages per second (business) | 200 |
| Messages per second (phone number) | 50 |
| Conversation window | 24 hours |
| Template messages | Require pre-approval |
Session Windows
- 24-hour customer service window
- After 24 hours, need template messages
- Templates require pre-approval
Security
- End-to-end encryption (WhatsApp native)
- Webhook signature verification —
X-Hub-Signature-256header - Access token management
- IP whitelisting (optional)
Credentials Required
Sending replies requires only two credentials, passed to the Messages endpoint as the URL path ID and the Bearer header:
WHATSAPP_PHONE_NUMBER_ID— your business phone number IDWHATSAPP_ACCESS_TOKEN— your system-user access token
Receiving webhooks additionally needs two optional values to authenticate inbound requests. When they are not set, the gateway logs a warning and skips verification (dev mode):
WHATSAPP_APP_SECRET— used to verify theX-Hub-Signature-256headerWHATSAPP_VERIFY_TOKEN— used to answer Meta’s initial GET challenge
Signature Verification
import hmac, hashlib, os
def verify_signature(payload: bytes, signature: str) -> bool:
expected = hmac.new(
os.environ["WHATSAPP_APP_SECRET"].encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
WhatsApp Formatter (Inside Actor)
# extensions/whatsapp_formatter.py
def format_for_whatsapp(response: str) -> dict:
"""Format agent response for WhatsApp Cloud API."""
# WhatsApp message limit
max_length = 4096
if len(response) > max_length:
response = response[:max_length - 50] + "\n\n_(truncated)_"
return {
"messaging_product": "whatsapp",
"to": user_id,
"text": {"body": response}
}
Interactive Messages
List Messages (Product Results)
{
"messaging_product": "whatsapp",
"to": user_id,
"type": "interactive",
"interactive": {
"type": "list",
"header": {"type": "text", "text": "🛍️ Products Found"},
"body": {"text": f"Found {len(products)} products:"},
"footer": {"text": "Tap to view details"},
"action": {
"button": "View Products",
"sections": [{
"title": "Results",
"rows": [
{"id": f"product_{p['id']}", "title": p['name'], "description": f"${p['price']}"}
for p in products
]
}]
}
}
}
Button Messages (Cart Actions)
{
"messaging_product": "whatsapp",
"to": user_id,
"type": "interactive",
"interactive": {
"type": "button",
"body": {"text": "🛒 Your cart total: $120"},
"action": {
"buttons": [
{"type": "reply", "reply": {"id": "checkout", "title": "Checkout"}},
{"type": "reply", "reply": {"id": "add_more", "title": "Add More"}},
{"type": "reply", "reply": {"id": "clear", "title": "Clear Cart"}}
]
}
}
}
Error Handling
flowchart TD
SEND[Send Message] --> CHECK{Success?}
CHECK -->|Yes| LOG[Log success]
CHECK -->|No| TYPE{Error type?}
TYPE -->|Rate limit| WAIT[Wait + retry]
TYPE -->|Auth error| REFRESH[Refresh token]
TYPE -->|Invalid phone| NOTIFY[Notify user]
TYPE -->|Network error| RETRY[Retry with backoff]
WAIT --> SEND
REFRESH --> SEND
RETRY --> SEND
See Also
- Integration Guide — Full wiring
- Rivet Actor Model — Actor implementation
- Architecture — High-level overview