Frontend Changes: - Fix runtime error: Remove undefined handleModify reference from ActionQueueCard in DashboardPage - Migrate PurchaseOrderDetailsModal to use correct PurchaseOrderItem type from purchase_orders service - Fix item display: Parse unit_price as string (Decimal) instead of number - Use correct field names: item_notes instead of notes - Remove deprecated PurchaseOrder types from suppliers.ts to prevent type conflicts - Update CreatePurchaseOrderModal to use unified types - Clean up API exports: Remove old PO hooks re-exported from suppliers - Add comprehensive translations for PO modal (en, es, eu) Documentation Reorganization: - Move WhatsApp implementation docs to docs/03-features/notifications/whatsapp/ - Move forecast validation docs to docs/03-features/forecasting/ - Move specification docs to docs/03-features/specifications/ - Move deployment docs (Colima, K8s, VPS sizing) to docs/05-deployment/ - Archive completed implementation summaries to docs/archive/implementation-summaries/ - Delete obsolete FRONTEND_CHANGES_NEEDED.md - Standardize filenames to lowercase with hyphens 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
23 KiB
WhatsApp Shared Account Model - Implementation Guide
Overview
This guide documents the Shared WhatsApp Business Account implementation for the bakery-ia pilot program. This model simplifies WhatsApp setup by using a single master WhatsApp Business Account with phone numbers assigned to each bakery tenant.
Architecture
Shared Account Model
┌─────────────────────────────────────────────┐
│ Master WhatsApp Business Account (WABA) │
│ - Centrally managed by platform admin │
│ - Single set of credentials │
│ - Multiple phone numbers (up to 120) │
└─────────────────────────────────────────────┘
│
┌─────────────┼─────────────┐
│ │ │
Phone #1 Phone #2 Phone #3
Bakery A Bakery B Bakery C
Key Benefits
✅ Zero configuration for bakery users - No Meta navigation required ✅ 5-minute setup - Admin assigns phone number via UI ✅ Lower support burden - Centralized management ✅ Predictable costs - One WABA subscription ✅ Perfect for pilot - Quick deployment for 10 bakeries
User Experience
For Bakery Owners (Non-Technical Users)
Before (Manual Setup):
- Navigate Meta Business Suite ❌
- Create WhatsApp Business Account ❌
- Create message templates ❌
- Get credentials (3 different IDs) ❌
- Copy/paste into settings ❌
- Time: 1-2 hours, high error rate
After (Shared Account):
- Toggle WhatsApp ON ✓
- See assigned phone number ✓
- Time: 30 seconds, zero configuration
For Platform Admin
Admin Workflow:
- Access WhatsApp Admin page (
/app/admin/whatsapp) - View list of tenants
- Select tenant
- Assign phone number from dropdown
- Done!
Technical Implementation
Backend Changes
1. Tenant Settings Model
File: services/tenant/app/models/tenant_settings.py
Changed:
# OLD (Per-Tenant Credentials)
notification_settings = {
"whatsapp_enabled": False,
"whatsapp_phone_number_id": "",
"whatsapp_access_token": "", # REMOVED
"whatsapp_business_account_id": "", # REMOVED
"whatsapp_api_version": "v18.0", # REMOVED
"whatsapp_default_language": "es"
}
# NEW (Shared Account)
notification_settings = {
"whatsapp_enabled": False,
"whatsapp_phone_number_id": "", # Phone # from shared account
"whatsapp_display_phone_number": "", # Display format "+34 612 345 678"
"whatsapp_default_language": "es"
}
2. WhatsApp Business Service
File: services/notification/app/services/whatsapp_business_service.py
Changed _get_whatsapp_credentials() method:
async def _get_whatsapp_credentials(self, tenant_id: str) -> Dict[str, str]:
"""
Uses global master account credentials with tenant-specific phone number
"""
# Always use global master account
access_token = self.global_access_token
business_account_id = self.global_business_account_id
phone_number_id = self.global_phone_number_id # Default
# Fetch tenant's assigned phone number
if self.tenant_client:
notification_settings = await self.tenant_client.get_notification_settings(tenant_id)
if notification_settings and notification_settings.get('whatsapp_enabled'):
tenant_phone_id = notification_settings.get('whatsapp_phone_number_id', '')
if tenant_phone_id:
phone_number_id = tenant_phone_id # Use tenant's phone
return {
'access_token': access_token,
'phone_number_id': phone_number_id,
'business_account_id': business_account_id
}
Key Change: Always uses global credentials, but selects the phone number based on tenant assignment.
3. Phone Number Management API
New File: services/tenant/app/api/whatsapp_admin.py
Endpoints:
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/admin/whatsapp/phone-numbers |
List available phone numbers from master WABA |
| GET | /api/v1/admin/whatsapp/tenants |
List all tenants with WhatsApp status |
| POST | /api/v1/admin/whatsapp/tenants/{id}/assign-phone |
Assign phone to tenant |
| DELETE | /api/v1/admin/whatsapp/tenants/{id}/unassign-phone |
Remove phone assignment |
Example: Assign Phone Number
curl -X POST http://localhost:8001/api/v1/admin/whatsapp/tenants/{tenant_id}/assign-phone \
-H "Content-Type: application/json" \
-d '{
"phone_number_id": "123456789012345",
"display_phone_number": "+34 612 345 678"
}'
Response:
{
"success": true,
"message": "Phone number +34 612 345 678 assigned to tenant 'Panadería San Juan'",
"tenant_id": "uuid-here",
"phone_number_id": "123456789012345",
"display_phone_number": "+34 612 345 678"
}
Frontend Changes
1. Simplified Notification Settings Card
File: frontend/src/pages/app/database/ajustes/cards/NotificationSettingsCard.tsx
Removed:
- Access Token input field
- Business Account ID input field
- Phone Number ID input field
- API Version selector
- Setup wizard instructions
Added:
- Display-only phone number (green badge if configured)
- "Contact support" message if not configured
- Language selector only
UI Before/After:
BEFORE:
┌────────────────────────────────────────┐
│ WhatsApp Business API Configuration │
│ │
│ Phone Number ID: [____________] │
│ Access Token: [____________] │
│ Business Acct: [____________] │
│ API Version: [v18.0 ▼] │
│ Language: [Español ▼] │
│ │
│ ℹ️ Setup Instructions: │
│ 1. Create WhatsApp Business... │
│ 2. Create templates... │
│ 3. Get credentials... │
└────────────────────────────────────────┘
AFTER:
┌────────────────────────────────────────┐
│ WhatsApp Configuration │
│ │
│ ✅ WhatsApp Configured │
│ Phone: +34 612 345 678 │
│ │
│ Language: [Español ▼] │
│ │
│ ℹ️ WhatsApp Notifications Included │
│ WhatsApp messaging is included │
│ in your subscription. │
└────────────────────────────────────────┘
2. Admin Interface
New File: frontend/src/pages/app/admin/WhatsAppAdminPage.tsx
Features:
- Lists all available phone numbers from master WABA
- Shows phone number quality rating (GREEN/YELLOW/RED)
- Lists all tenants with WhatsApp status
- Dropdown to assign phone numbers
- One-click unassign button
- Real-time status updates
Screenshot Mockup:
┌──────────────────────────────────────────────────────────────┐
│ WhatsApp Admin Management │
│ Assign WhatsApp phone numbers to bakery tenants │
├──────────────────────────────────────────────────────────────┤
│ 📞 Available Phone Numbers (3) │
├──────────────────────────────────────────────────────────────┤
│ +34 612 345 678 Bakery Platform [GREEN] │
│ +34 612 345 679 Bakery Support [GREEN] │
│ +34 612 345 680 Bakery Notifications [YELLOW] │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ 👥 Bakery Tenants (10) │
├──────────────────────────────────────────────────────────────┤
│ Panadería San Juan ✅ Active │
│ Phone: +34 612 345 678 [Unassign] │
├──────────────────────────────────────────────────────────────┤
│ Panadería Goiko ⚠️ Not Configured │
│ No phone number assigned [Assign phone number... ▼] │
└──────────────────────────────────────────────────────────────┘
Setup Instructions
Step 1: Create Master WhatsApp Business Account (One-Time)
Prerequisites:
- Meta/Facebook Business account
- Verified business
- Phone number(s) to register
Instructions:
-
Create WhatsApp Business Account
- Go to Meta Business Suite
- Add WhatsApp product
- Complete business verification (1-3 days)
-
Add Phone Numbers
- Add at least 10 phone numbers (one per pilot bakery)
- Verify each phone number
- Note: You can request up to 120 phone numbers per WABA
-
Create Message Templates
- Create
po_notificationtemplate:Category: UTILITY Language: Spanish (es) Message: "Hola {{1}}, has recibido una nueva orden de compra {{2}} por un total de {{3}}." - Submit for approval (15 min - 24 hours)
- Create
-
Get Master Credentials
- Business Account ID: From WhatsApp Manager settings
- Access Token: Create System User or use temporary token
- Phone Number ID: Listed in phone numbers section
Step 2: Configure Environment Variables
File: services/notification/.env
# Master WhatsApp Business Account Credentials
WHATSAPP_BUSINESS_ACCOUNT_ID=987654321098765
WHATSAPP_ACCESS_TOKEN=EAAxxxxxxxxxxxxxxxxxxxxxxxxxx
WHATSAPP_PHONE_NUMBER_ID=123456789012345 # Default/fallback phone
WHATSAPP_API_VERSION=v18.0
ENABLE_WHATSAPP_NOTIFICATIONS=true
WHATSAPP_WEBHOOK_VERIFY_TOKEN=random-secret-token-here
Security Notes:
- Store
WHATSAPP_ACCESS_TOKENsecurely (use secrets manager in production) - Rotate token every 60 days
- Use System User token (not temporary token) for production
Step 3: Assign Phone Numbers to Tenants
Via Admin UI:
- Access admin page:
http://localhost:5173/app/admin/whatsapp - See list of tenants
- For each tenant:
- Select phone number from dropdown
- Click assign
- Verify green checkmark appears
Via API:
# Assign phone to tenant
curl -X POST http://localhost:8001/api/v1/admin/whatsapp/tenants/{tenant_id}/assign-phone \
-H "Content-Type: application/json" \
-d '{
"phone_number_id": "123456789012345",
"display_phone_number": "+34 612 345 678"
}'
Step 4: Test Notifications
Enable WhatsApp for a Tenant:
- Login as bakery owner
- Go to Settings → Notifications
- Toggle WhatsApp ON
- Verify phone number is displayed
- Save settings
Trigger Test Notification:
# Create a purchase order (will trigger WhatsApp notification)
curl -X POST http://localhost:8003/api/v1/orders/purchase-orders \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: {tenant_id}" \
-d '{
"supplier_id": "uuid",
"items": [...]
}'
Verify:
- Check notification service logs:
docker logs -f notification-service - Supplier should receive WhatsApp message from assigned phone number
- Message status tracked in
whatsapp_messagestable
Monitoring & Operations
Check Phone Number Usage
# List all tenants with assigned phone numbers
curl http://localhost:8001/api/v1/admin/whatsapp/tenants | jq
View WhatsApp Message Logs
-- In notification database
SELECT
tenant_id,
recipient_phone,
template_name,
status,
created_at,
error_message
FROM whatsapp_messages
WHERE created_at > NOW() - INTERVAL '24 hours'
ORDER BY created_at DESC;
Monitor Meta Rate Limits
WhatsApp Cloud API has the following limits:
| Metric | Limit |
|---|---|
| Messages per second | 80 |
| Messages per day (verified) | 100,000 |
| Messages per day (unverified) | 1,000 |
| Conversations per 24h | Unlimited (pay per conversation) |
Check Quality Rating:
curl -X GET "https://graph.facebook.com/v18.0/{PHONE_NUMBER_ID}" \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
| jq '.quality_rating'
Quality Ratings:
- GREEN - No issues, full limits
- YELLOW - Warning, limits may be reduced
- RED - Quality issues, severely restricted
Migration from Per-Tenant to Shared Account
If you have existing tenants with their own credentials:
Automatic Migration Script
# services/tenant/scripts/migrate_to_shared_account.py
"""
Migrate existing tenant WhatsApp credentials to shared account model
"""
import asyncio
from sqlalchemy import select
from app.core.database import database_manager
from app.models.tenant_settings import TenantSettings
async def migrate():
async with database_manager.get_session() as session:
# Get all tenant settings
result = await session.execute(select(TenantSettings))
all_settings = result.scalars().all()
for settings in all_settings:
notification_settings = settings.notification_settings
# If tenant has old credentials, preserve phone number ID
if notification_settings.get('whatsapp_access_token'):
phone_id = notification_settings.get('whatsapp_phone_number_id', '')
# Update to new schema
notification_settings['whatsapp_phone_number_id'] = phone_id
notification_settings['whatsapp_display_phone_number'] = '' # Admin will set
# Remove old fields
notification_settings.pop('whatsapp_access_token', None)
notification_settings.pop('whatsapp_business_account_id', None)
notification_settings.pop('whatsapp_api_version', None)
settings.notification_settings = notification_settings
print(f"Migrated tenant: {settings.tenant_id}")
await session.commit()
print("Migration complete!")
if __name__ == "__main__":
asyncio.run(migrate())
Troubleshooting
Issue: Tenant doesn't receive WhatsApp messages
Checklist:
- ✅ WhatsApp enabled in tenant settings?
- ✅ Phone number assigned to tenant?
- ✅ Master credentials configured in environment?
- ✅ Template approved by Meta?
- ✅ Recipient phone number in E.164 format (+34612345678)?
Check Logs:
# Notification service logs
docker logs -f notification-service | grep whatsapp
# Look for:
# - "Using tenant-assigned WhatsApp phone number"
# - "WhatsApp template message sent successfully"
# - Any error messages
Issue: Phone number assignment fails
Error: "Phone number already assigned to another tenant"
Solution:
# Find which tenant has the phone number
curl http://localhost:8001/api/v1/admin/whatsapp/tenants | \
jq '.[] | select(.phone_number_id == "123456789012345")'
# Unassign from old tenant first
curl -X DELETE http://localhost:8001/api/v1/admin/whatsapp/tenants/{old_tenant_id}/unassign-phone
Issue: "WhatsApp master account not configured"
Solution:
Ensure environment variables are set:
# Check if variables exist
docker exec notification-service env | grep WHATSAPP
# Should show:
# WHATSAPP_BUSINESS_ACCOUNT_ID=...
# WHATSAPP_ACCESS_TOKEN=...
# WHATSAPP_PHONE_NUMBER_ID=...
Issue: Template not found
Error: "Template po_notification not found"
Solution:
- Create template in Meta Business Manager
- Wait for approval (check status):
curl -X GET "https://graph.facebook.com/v18.0/{WABA_ID}/message_templates" \ -H "Authorization: Bearer {TOKEN}" \ | jq '.data[] | select(.name == "po_notification")' - Ensure template language matches tenant's
whatsapp_default_language
Cost Analysis
WhatsApp Business API Pricing (as of 2024)
Meta Pricing:
- Business-initiated conversations: €0.0319 - €0.0699 per conversation (Spain)
- User-initiated conversations: Free (24-hour window)
- Conversation window: 24 hours
Monthly Cost Estimate (10 Bakeries):
- Assume 5 PO notifications per bakery per day
- 5 × 10 bakeries × 30 days = 1,500 messages/month
- Cost: 1,500 × €0.05 = €75/month
Shared Account vs. Individual Accounts:
| Model | Setup Time | Monthly Cost | Support Burden |
|---|---|---|---|
| Individual Accounts | 1-2 hrs/bakery | €75 total | High |
| Shared Account | 5 min/bakery | €75 total | Low |
Savings: Time savings = 10 hrs × €50/hr = €500 in setup cost
Future Enhancements
Option 1: Template Management API
Automate template creation for new tenants:
async def create_po_template(waba_id: str, access_token: str):
"""Programmatically create PO notification template"""
url = f"https://graph.facebook.com/v18.0/{waba_id}/message_templates"
payload = {
"name": "po_notification",
"language": "es",
"category": "UTILITY",
"components": [{
"type": "BODY",
"text": "Hola {{1}}, has recibido una nueva orden de compra {{2}} por un total de {{3}}."
}]
}
response = await httpx.post(url, headers={"Authorization": f"Bearer {access_token}"}, json=payload)
return response.json()
Option 2: WhatsApp Embedded Signup
For scaling beyond pilot:
- Apply for Meta Business Solution Provider program
- Implement OAuth-style signup flow
- Users click "Connect WhatsApp" → auto-configured
- Estimated implementation: 2-4 weeks
Option 3: Tiered Pricing
Basic Tier (Free):
- Email notifications only
Standard Tier (€29/month):
- Shared WhatsApp account
- Pre-approved templates
- Up to 500 messages/month
Enterprise Tier (€99/month):
- Own WhatsApp Business Account
- Custom templates
- Unlimited messages
- White-label phone number
Security & Compliance
Data Privacy
GDPR Compliance:
- WhatsApp messages contain supplier contact info (phone numbers)
- Ensure GDPR consent for sending notifications
- Provide opt-out mechanism
- Data retention: Messages stored for 90 days (configurable)
Encryption:
- WhatsApp messages: End-to-end encrypted by Meta
- Access tokens: Stored in environment variables (use secrets manager in production)
- Database: Encrypt
notification_settingsJSON column
Access Control
Admin Access:
- Only platform admins can assign/unassign phone numbers
- Implement role-based access control (RBAC)
- Audit log for phone number assignments
# Example: Add admin check
@router.post("/admin/whatsapp/tenants/{tenant_id}/assign-phone")
async def assign_phone(tenant_id: UUID, current_user = Depends(require_admin_role)):
# Only admins can access
pass
Support & Contacts
Meta Support:
- WhatsApp Business API Support: https://business.whatsapp.com/support
- Developer Docs: https://developers.facebook.com/docs/whatsapp
Platform Admin:
- Email: admin@bakery-platform.com
- Phone number assignment requests
- Template approval assistance
Bakery Owner Help:
- Settings → Notifications → Toggle WhatsApp ON
- If phone number not showing: Contact support
- Language preferences can be changed anytime
Appendix
A. Database Schema Changes
Migration Script:
-- Add new field, remove old fields
-- services/tenant/migrations/versions/00002_shared_whatsapp_account.py
ALTER TABLE tenant_settings
-- The notification_settings JSONB column now has:
-- + whatsapp_display_phone_number (new)
-- - whatsapp_access_token (removed)
-- - whatsapp_business_account_id (removed)
-- - whatsapp_api_version (removed)
;
-- No ALTER TABLE needed (JSONB is schema-less)
-- Migration handled by application code
B. API Reference
Phone Number Info Schema:
interface WhatsAppPhoneNumberInfo {
id: string; // Meta Phone Number ID
display_phone_number: string; // E.164 format: +34612345678
verified_name: string; // Business name verified by Meta
quality_rating: string; // GREEN, YELLOW, RED
}
Tenant WhatsApp Status Schema:
interface TenantWhatsAppStatus {
tenant_id: string;
tenant_name: string;
whatsapp_enabled: boolean;
phone_number_id: string | null;
display_phone_number: string | null;
}
C. Environment Variables Reference
# Notification Service (services/notification/.env)
WHATSAPP_BUSINESS_ACCOUNT_ID= # Meta WABA ID
WHATSAPP_ACCESS_TOKEN= # Meta System User Token
WHATSAPP_PHONE_NUMBER_ID= # Default phone (fallback)
WHATSAPP_API_VERSION=v18.0 # Meta API version
ENABLE_WHATSAPP_NOTIFICATIONS=true
WHATSAPP_WEBHOOK_VERIFY_TOKEN= # Random secret for webhook verification
D. Useful Commands
# View all available phone numbers
curl http://localhost:8001/api/v1/admin/whatsapp/phone-numbers | jq
# View tenant WhatsApp status
curl http://localhost:8001/api/v1/admin/whatsapp/tenants | jq
# Assign phone to tenant
curl -X POST http://localhost:8001/api/v1/admin/whatsapp/tenants/{id}/assign-phone \
-H "Content-Type: application/json" \
-d '{"phone_number_id": "XXX", "display_phone_number": "+34 612 345 678"}'
# Unassign phone from tenant
curl -X DELETE http://localhost:8001/api/v1/admin/whatsapp/tenants/{id}/unassign-phone
# Test WhatsApp connectivity
curl -X GET "https://graph.facebook.com/v18.0/{PHONE_ID}" \
-H "Authorization: Bearer {TOKEN}"
# Check message template status
curl "https://graph.facebook.com/v18.0/{WABA_ID}/message_templates?fields=name,status,language" \
-H "Authorization: Bearer {TOKEN}" | jq
Document Version: 1.0 Last Updated: 2025-01-17 Author: Platform Engineering Team Status: Production Ready for Pilot