#62 — WhatsApp Business
PLANIFIÉ
Priorité: 🟠 HAUTE · Type: TYPE B · Conteneur: rgz-api · Code: app/services/whatsapp.pyDépendances: #1 rgz-api
Description
Integration Meta WhatsApp Business API pour notifications revendeurs et abonnés. Support messages template (incidents, SLA credits), webhook pour messages entrants, et auto-répondeur basique (solde, aide, déconnexion). Messages texte uniquement (pas d'images/vidéos pour MVP).
La plateforme WhatsApp offre un meilleur taux de livraison et de lecture que SMS pour les notifications critiques. Les revendeurs peuvent aussi envoyer des messages gratuitement via API une fois authentifiés.
Architecture Interne
Meta WhatsApp Cloud API Integration:
1. SETUP (une fois)
- Business Account ID: créé dans Meta Business Manager
- Phone Number ID: numéro WhatsApp Business (ex: +229-XXXXX)
- Access Token: Bearer token avec scope messages_and_connections_messaging
- Webhook URL: https://api-rgz.duckdns.org/api/v1/whatsapp/webhook
- Webhook Verify Token: secret pour authentifier callbacks
2. FLOW ENVOI MESSAGE
POST https://graph.instagram.com/v18.0/{PHONE_NUMBER_ID}/messages
Header: Authorization: Bearer {ACCESS_TOKEN}
Body: {
messaging_product: "whatsapp",
to: "+229-97979964",
type: "template",
template: {
name: "incident_p0_alert",
language: { code: "fr" },
parameters: {
body: {
parameters: [
{ type: "text", text: "Fiber cut central" },
{ type: "text", text: "45" }
]
}
}
}
}
Response: { messages: [{ id: "wamid.xxx" }] }
3. WEBHOOK CALLBACK (Meta → RGZ)
POST /api/v1/whatsapp/webhook (inbound)
Header: X-Hub-Signature-256: sha256=hmac(payload, WEBHOOK_SECRET)
Body: {
entry: [{
changes: [{
value: {
messages: [{
from: "+229-97979964",
type: "text",
text: { body: "solde" },
timestamp: "1613060669",
id: "wamid.xxx"
}]
}
}]
}]
}
→ Parse message, trigger auto-response
4. AUTO-RÉPONDEUR (simple)
Message reçu: "solde" → Répond: "Votre solde: X.XXX FCFA"
Message reçu: "aide" → Répond: "Menu aide\n1. Solde\n2. Tickets\n3. Support\n4. Forfaits"
Message reçu: "deconnexion" → Appelle CoA logout (#6 RADIUS)
Autres messages: "Merci de contacter support@rgz.bj"Modèles de Données
python
class WhatsAppMessage(BaseModel):
id: UUID
wamid: str # Meta message ID
recipient_phone: str (E164)
message_type: str (template|text)
template_name: str # incident_p0_alert, sla_credit, etc
message_content: str # Final rendered text
status: str (queued|sent|delivered|read|failed)
created_at: datetime
delivered_at: datetime | None
read_at: datetime | None
error_message: str | None
class WhatsAppInboundMessage(BaseModel):
id: UUID
wamid: str
sender_phone: str (E164)
message_type: str (text|image|video|document)
content: str
auto_response_sent: bool
auto_response_text: str | None
routed_to_support: bool
created_at: datetime
# Table DB
whatsapp_messages:
- id UUID PK
- wamid VARCHAR(100) UNIQUE (Meta message ID)
- recipient_phone VARCHAR(20)
- message_type VARCHAR(20) CHECK(template|text)
- template_name VARCHAR(100) NULL
- content TEXT
- status VARCHAR(20) CHECK(queued|sent|delivered|read|failed)
- error_message TEXT NULL
- created_at TIMESTAMP
- delivered_at TIMESTAMP NULL
- read_at TIMESTAMP NULL
whatsapp_inbound_messages:
- id UUID PK
- wamid VARCHAR(100) UNIQUE
- sender_phone VARCHAR(20)
- message_type VARCHAR(20)
- content TEXT
- auto_response_sent BOOLEAN DEFAULT false
- auto_response_text TEXT NULL
- routed_to_support BOOLEAN DEFAULT false
- support_ticket_id UUID FK NULL
- created_at TIMESTAMP
whatsapp_templates:
- id UUID PK
- name VARCHAR(100) UNIQUE
- meta_template_name VARCHAR(100) UNIQUE (registered on Meta)
- description TEXT
- parameters_json TEXT (JSON schema of required parameters)
- is_active BOOLEAN
- created_at TIMESTAMPConfiguration
env
# .env.example
WHATSAPP_ACCESS_TOKEN=EAAxxxxxxxxxxxxxxxx
WHATSAPP_BUSINESS_ACCOUNT_ID=123456789
WHATSAPP_PHONE_NUMBER_ID=1023456789 # Business phone ID on Meta
WHATSAPP_WEBHOOK_SECRET=your_webhook_secret
WHATSAPP_WEBHOOK_VERIFY_TOKEN=your_verify_token
WHATSAPP_API_VERSION=v18.0
WHATSAPP_ENDPOINT=https://graph.instagram.com/v18.0
# Templates registered on Meta (managed separately)
WHATSAPP_TEMPLATE_INCIDENT_P0=incident_p0_alert
WHATSAPP_TEMPLATE_SLA_CREDIT=sla_credit
WHATSAPP_TEMPLATE_INVOICE=billing_invoice
WHATSAPP_AUTO_RESPONDER_ENABLED=true
WHATSAPP_SUPPORT_PHONE=+229-97979964 # Where to forward unknown msgsTemplates WhatsApp (Meta Dashboard)
Les templates doivent être enregistrés dans Meta Business Manager et approuvés :
TEMPLATE: incident_p0_alert
Category: MARKETING (ou TRANSACTIONAL si P0)
Language: French
Content:
"🔴 Alerte RGZ P0: {{1}}
{{2}} sites sans service
Équipe mobilisée. MAJ dans {{3}}min
Ticket: {{4}}"
Parameters:
1. {{1}} = incident title (text)
2. {{2}} = affected site count (text)
3. {{3}} = escalation delay (text)
4. {{4}} = incident number (text)
TEMPLATE: sla_credit
Category: TRANSACTIONAL
Language: French
Content:
"Crédit RGZ appliqué ✓
{{1}} FCFA
Downtime {{2}}min (uptime {{3}}%)
Merci!"
Parameters:
1. {{1}} = amount FCFA
2. {{2}} = downtime minutes
3. {{3}} = uptime percent
TEMPLATE: billing_invoice
Category: TRANSACTIONAL
Language: French
Content:
"Facture {{1}} prête
{{2}} FCFA
Télécharger: {{3}}"
Parameters:
1. {{1}} = month
2. {{2}} = amount FCFA
3. {{3}} = portal linkEndpoints API
| Méthode | Route | Requête | Réponse | Notes |
|---|---|---|---|---|
| POST | /api/v1/whatsapp/send | {phone, template_name, parameters | 201 CREATED | |
| POST | /api/v1/whatsapp/webhook | {entry:[...]} (Meta callback) | 200 OK | |
| GET | /api/v1/whatsapp/webhook | ?hub.mode=subscribe&hub.verify_token=... | 200 OK (Meta verification) | |
| GET | /api/v1/whatsapp/messages | ?phone={number}&limit=50 | {items:[{wamid, content, status, created_at}], total} | 200 OK |
| GET | /api/v1/whatsapp/templates | — | {items:[{name, parameters}], total} | 200 OK |
Commandes Utiles
bash
# Envoyer message template incident P0
curl -X POST http://localhost:8000/api/v1/whatsapp/send \
-H "Content-Type: application/json" \
-d '{
"phone": "+229-97979964",
"template_name": "incident_p0_alert",
"parameters": {
"title": "Fiber cut central",
"affected_sites": "45",
"delay": "5",
"incident_number": "INC-2026-000847"
},
"message_type": "template"
}'
# Envoyer SMS de crédit SLA
curl -X POST http://localhost:8000/api/v1/whatsapp/send \
-d '{
"phone": "+229-97979964",
"template_name": "sla_credit",
"parameters": {
"amount_fcfa": "1000",
"downtime_minutes": "15",
"uptime_percent": "99.7"
},
"message_type": "template"
}'
# Lister templates disponibles
curl http://localhost:8000/api/v1/whatsapp/templates
# Historique messages (NOC)
curl http://localhost:8000/api/v1/whatsapp/messages?phone=+229-97979964 \
-H "Authorization: Bearer NOC_TOKEN" | jq '.items'
# Webhook callback Meta (reçu automatiquement)
# L'endpoint /api/v1/whatsapp/webhook parse inbound messages
# et déclenche auto-répondeur (solde, aide, etc)Auto-Répondeur Logic
python
def auto_responder(message_content: str, sender_phone: str) -> str:
"""
Répondre automatiquement aux messages simples
"""
msg_lower = message_content.strip().lower()
if msg_lower == "solde":
balance = get_subscriber_balance(sender_phone)
return f"Votre solde RGZ: {balance:.0f} FCFA 💰"
elif msg_lower == "aide":
return """Menu RGZ Assistant:
1. *solde* - Vérifier votre solde
2. *tickets* - Afficher mes tickets support
3. *support* - Contacter le support
4. *forfaits* - Voir mes forfaits actifs
Répondez avec le numéro ou le mot-clé.
Support: support@rgz.bj | +229-XXXXX"""
elif msg_lower == "deconnexion":
logout_subscriber(sender_phone)
return "Déconnexion confirmée. À bientôt! 👋"
elif msg_lower in ["tickets", "ticket"]:
tickets = get_support_tickets(sender_phone)
if not tickets:
return "Vous n'avez pas de tickets ouverts."
return f"Vos {len(tickets)} tickets ouverts:\n" + \
"\n".join([f"• {t.number}: {t.title}" for t in tickets[:5]])
elif msg_lower in ["forfaits", "forfait"]:
forfaits = get_active_forfaits(sender_phone)
if not forfaits:
return "Aucun forfait actif. Visitez: https://access-rgz.duckdns.org"
return f"Forfaits actifs ({len(forfaits)}):\n" + \
"\n".join([f"• {f.name}: {f.status}" for f in forfaits])
else:
# Route to support if unknown
return """Message non compris.
Merci de contacter le support: support@rgz.bj
ou appelez: +229-XXXXX"""Intégration Avec Autres Outils
- #58 incident-escalation: Appelle #62 pour alertes P0/P1/P2 (template incident_p0_alert, etc)
- #25 credit-sla-auto: Appelle #62 pour notification crédit (template sla_credit)
- #64 crisis-dispatcher: Appelle #62 pour broadcast crise
- #61 sms-template-engine: Templates parallèles (SMS et WhatsApp même contenu)
Implémentation TODO
- [ ] Meta WhatsApp API client (send message)
- [ ] Webhook handler (inbound message parsing)
- [ ] Auto-répondeur (solde, aide, tickets, forfaits, deconnexion)
- [ ] Status callback handler (delivered, read, failed)
- [ ] Error handling + retry logic
- [ ] Message audit log (whatsapp_messages + whatsapp_inbound_messages)
- [ ] Template registration (meta_template_name validation)
- [ ] Rate limiting (via Redis rgz:rate:whatsapp:{phone})
- [ ] Tests: envoi template → callback delivered → audit trail
- [ ] Dashboard NOC: historique messages par revendeur
- [ ] Support routing (unknown messages → support@rgz.bj)
Dernière mise à jour: 2026-02-21