# WhatsApp Cloud API Debugging - Complete Implementation ## Problem Summary WhatsApp Cloud API returns HTTP 200 with a wamid, but recipients don't receive messages. **Root Cause:** The initial API response (HTTP 200) only means Meta **accepted** the message, NOT that it was delivered. The actual delivery status comes from **webhook callbacks** which were not implemented. --- ## Solution Implemented ### 1. ✅ Message Status Tracking **File:** `models.py` Added `WhatsAppMessage` model to track: - `wamid` - WhatsApp message ID from Meta - `status` - sent, delivered, read, failed - `event_id`, `guest_id` - Context for tracking - `error_code`, `error_title`, `error_message` - Failure details - Timestamps: `sent_at`, `delivered_at`, `read_at`, `failed_at` ### 2. ✅ Webhook Endpoint **File:** `main.py` Added two webhook endpoints: ```python GET /whatsapp/webhook # Webhook verification POST /whatsapp/webhook # Status updates from Meta ``` **What it does:** - Receives delivery status updates from Meta - Updates message status in database - Logs all webhook payloads for debugging - Handles: sent, delivered, read, failed statuses - Captures error details when messages fail ### 3. ✅ Enhanced WhatsApp Service **File:** `whatsapp.py` **Changes:** 1. Added `event_id` and `guest_id` parameters to `send_by_template_key()` 2. Saves messages to database after sending 3. Supports dynamic image headers via `header_handle_key` 4. Enhanced logging with full payload dumps ### 4. ✅ Fixed hina_invitation Template **File:** `whatsapp_templates.py` **Configuration:** ```python { "meta_name": "hina_invitation", "language_code": "he", "header_type": "IMAGE", "header_handle_key": "invitation_image_url", # Dynamic image from params "body_params": ["contact_name"], "button_type": "URL", "button_url": "https://invy.dvirlabs.com/guest/{{1}}", "button_param_key": "event_id", } ``` ### 5. ✅ Comprehensive Test Script **File:** `test_whatsapp_debug.py` **Features:** - Tests phone normalization - Validates template configuration - Sends test message with full logging - Shows webhook setup instructions - Provides clear debugging output ### 6. ✅ Database Migration **File:** `add_whatsapp_messages_table.py` Creates the `whatsapp_messages` table. --- ## Setup Instructions ### Step 1: Run Database Migration ```bash cd backend python add_whatsapp_messages_table.py ``` ### Step 2: Configure Webhook in Meta Business Manager 1. Go to **Meta Business Manager** → **WhatsApp** → **Configuration** → **Webhook** 2. **Callback URL:** ``` https://invy.dvirlabs.com/whatsapp/webhook ``` 3. **Verify Token:** - Set this in `.env` as `WHATSAPP_VERIFY_TOKEN=your_secret_token_here` - Use the same value in Meta's webhook configuration 4. Click **Verify and Save** 5. **Subscribe to Fields:** - ✅ messages ### Step 3: Update Environment Variables Add to `.env`: ```bash # Existing WhatsApp credentials WHATSAPP_ACCESS_TOKEN=your_meta_access_token WHATSAPP_PHONE_NUMBER_ID=your_phone_number_id WHATSAPP_API_VERSION=v20.0 # NEW: Webhook verification token WHATSAPP_VERIFY_TOKEN=your_webhook_secret_token_here ``` ### Step 4: Test the Integration ```bash cd backend python test_whatsapp_debug.py ``` **What to check:** 1. Phone normalization: ✓ All pass 2. Template configuration: ✓ All correct 3. Message send: ✓ Returns wamid 4. Webhook configured: ✓ Verify token set ### Step 5: Send Test Message The script will prompt you to send a test message to: `0504370045` **Expected Result:** ```json { "message_id": "wamid.HBgLOTcyNTA0MzcwMDQ1FQIAERgSQzI4QkE1RkQ5MzFFNTY1RTEwAA==", "status": "sent", "to": "+972504370045", "template": "hina_invitation" } ``` --- ## How to Debug Delivery Issues ### Check 1: Backend Logs Watch for webhook callbacks: ```bash tail -f backend.log | grep "WhatsApp Webhook" ``` **Expected logs:** ``` [WhatsApp Webhook] Status update: wamid=xxx, status=delivered [WhatsApp Webhook] ✓ Updated message xxx to status: delivered ``` ### Check 2: Database Status ```sql SELECT wamid, to_phone, status, template_name, sent_at, delivered_at, failed_at, error_code, error_message FROM whatsapp_messages ORDER BY sent_at DESC LIMIT 10; ``` **Status meanings:** - `sent` - Meta accepted the message - `delivered` - Recipient's phone received it - `read` - Recipient opened the message - `failed` - Delivery failed (check error_code/error_message) ### Check 3: Common Failure Reasons **If status stays "sent" and never reaches "delivered":** 1. **Phone number not on WhatsApp** - Error code: 131026 - Solution: Verify the recipient has WhatsApp 2. **Template parameters mismatch** - Error code: 132000 - Solution: Check template in Meta matches your params 3. **Image URL not accessible** - Error code: 132015 - Solution: Ensure image URL is publicly accessible 4. **Phone number blocked your business** - Error code: 131031 - Solution: Recipient must unblock your business number 5. **Rate limiting** - Error code: 130429 - Solution: Slow down sending rate ### Check 4: Webhook Delivery Test Test webhook is receiving callbacks: ```bash curl -X GET "https://invy.dvirlabs.com/whatsapp/webhook?hub.mode=subscribe&hub.verify_token=YOUR_TOKEN&hub.challenge=test123" ``` Expected: `test123` (echo challenge) --- ## Example: Sending hina_invitation ```python from whatsapp import get_whatsapp_service service = get_whatsapp_service(db) params = { "contact_name": "איילה חורב", "event_id": "f3122a7d-1d7c-4cc1-955d-1c6b7358bd25", "invitation_image_url": "https://api-invy.dvirlabs.com/uploads/1d32b5fbab0f494cae443b4188a83ca3.jpg", } result = await service.send_by_template_key( template_key="hina_invitation", to_phone="0504370045", params=params, event_id="f3122a7d-1d7c-4cc1-955d-1c6b7358bd25", guest_id="guest-uuid-here", ) print(f"Message ID: {result['message_id']}") ``` **Generated payload:** ```json { "messaging_product": "whatsapp", "to": "+972504370045", "type": "template", "template": { "name": "hina_invitation", "language": { "code": "he" }, "components": [ { "type": "header", "parameters": [{ "type": "image", "image": { "link": "https://api-invy.dvirlabs.com/uploads/1d32b5fbab0f494cae443b4188a83ca3.jpg" } }] }, { "type": "body", "parameters": [ { "type": "text", "text": "איילה חורב" } ] }, { "type": "button", "sub_type": "url", "index": "0", "parameters": [ { "type": "text", "text": "f3122a7d-1d7c-4cc1-955d-1c6b7358bd25" } ] } ] } } ``` --- ## Phone Normalization **Israeli numbers:** - `0504370045` → `+972504370045` ✓ - `050-437-0045` → `+972504370045` ✓ - `+972504370045` → `+972504370045` ✓ - `972504370045` → `+972504370045` ✓ --- ## Files Modified 1. ✅ `models.py` - Added WhatsAppMessage model 2. ✅ `main.py` - Added webhook endpoints, updated imports 3. ✅ `whatsapp.py` - Added event/guest tracking, dynamic image support 4. ✅ `whatsapp_templates.py` - Fixed hina_invitation template ## Files Created 1. ✅ `test_whatsapp_debug.py` - Comprehensive debugging script 2. ✅ `add_whatsapp_messages_table.py` - Database migration 3. ✅ `WHATSAPP_DEBUGGING.md` - This documentation --- ## Next Steps 1. **Run migration:** `python add_whatsapp_messages_table.py` 2. **Configure webhook in Meta Business Manager** 3. **Set WHATSAPP_VERIFY_TOKEN in .env** 4. **Run test script:** `python test_whatsapp_debug.py` 5. **Monitor webhook logs for delivery status** 6. **Check database for message status updates** --- ## Critical Understanding **HTTP 200 ≠ Message Delivered** When Meta returns HTTP 200, it means: - ✓ Request was valid - ✓ Message was queued for delivery - ✗ **NOT** that the recipient received it **Actual delivery confirmation comes from webhook callbacks.** Without webhooks, you'll never know if messages were delivered, read, or failed. **This is why webhook implementation is CRITICAL.**