""" WhatsApp Cloud API Debug Test Script ===================================== This script tests WhatsApp message sending with full debugging to identify why messages return HTTP 200 but are not received by recipients. Usage: python test_whatsapp_debug.py Target: Phone: 0504370045 → 972504370045 Template: hina_invitation Language: he """ import asyncio import sys import os import json import logging from pathlib import Path # Add backend to path sys.path.insert(0, str(Path(__file__).parent)) # Configure detailed logging logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) # Load environment variables from dotenv import load_dotenv load_dotenv() from whatsapp import WhatsAppService, WhatsAppError from database import SessionLocal async def test_phone_normalization(): """Test that phone number normalization works correctly""" print("\n" + "="*80) print("STEP 1: Phone Number Normalization Test") print("="*80) test_numbers = [ ("0504370045", "+972504370045"), ("050-437-0045", "+972504370045"), ("+972504370045", "+972504370045"), ("972504370045", "+972504370045"), ("0506118707", "+972506118707"), ] for input_phone, expected in test_numbers: normalized = WhatsAppService.normalize_phone_to_e164(input_phone) status = "✓" if normalized == expected else "✗" print(f"{status} {input_phone:20} → {normalized:20} (expected: {expected})") if normalized != expected: print(f" ERROR: Normalization mismatch!") return False print("\n✓ All phone normalizations correct") return True async def test_template_configuration(db): """Test that hina_invitation template is configured correctly""" print("\n" + "="*80) print("STEP 2: Template Configuration Test") print("="*80) try: from whatsapp_templates import get_template template = get_template(db, "hina_invitation") print("\nTemplate Configuration:") print(f" meta_name: {template['meta_name']}") print(f" language_code: {template['language_code']}") print(f" header_type: {template.get('header_type', 'TEXT')}") print(f" header_params: {template.get('header_params', [])}") print(f" header_handle_key:{template.get('header_handle_key', 'N/A')}") print(f" body_params: {template.get('body_params', [])}") print(f" button_type: {template.get('button_type', 'N/A')}") print(f" button_url: {template.get('button_url', 'N/A')}") print(f" button_param_key: {template.get('button_param_key', 'N/A')}") # Validate template structure issues = [] if template.get('meta_name') != 'hina_invitation': issues.append("meta_name must be 'hina_invitation'") if template.get('language_code') != 'he': issues.append("language_code must be 'he'") if template.get('header_type') != 'IMAGE': issues.append("header_type should be 'IMAGE' for this template") if not template.get('header_handle_key'): issues.append("header_handle_key should be set for dynamic image") if template.get('body_params') != ['contact_name']: issues.append("body_params should be ['contact_name']") if template.get('button_param_key') != 'event_id': issues.append("button_param_key should be 'event_id'") if template.get('button_url') != 'https://invy.dvirlabs.com/guest/{{1}}': issues.append("button_url should include dynamic parameter {{1}}") if issues: print("\n✗ Template configuration issues found:") for issue in issues: print(f" - {issue}") return False print("\n✓ Template configuration is correct") return True except Exception as e: print(f"\n✗ Failed to load template: {e}") return False async def test_send_message(db): """Send a test WhatsApp message with full logging""" print("\n" + "="*80) print("STEP 3: Send Test WhatsApp Message") print("="*80) # Test parameters target_phone = "0504370045" template_key = "hina_invitation" params = { "contact_name": "בדיקה", "event_id": "7c170667-9643-48c6-ad95-43cd3a1a36e1", # חינה event with valid image "invitation_image_url": "https://api-invy.dvirlabs.com/uploads/271fe5d87db744ae934337bf91fcd19e.png", } print(f"\nTest Parameters:") print(f" Phone: {target_phone} → {WhatsAppService.normalize_phone_to_e164(target_phone)}") print(f" Template: {template_key}") print(f" Language: he") print(f" Contact Name: {params['contact_name']}") print(f" Event ID: {params['event_id']}") print(f" Image URL: {params['invitation_image_url']}") print(f"\nMeta API Configuration:") print(f" Access Token: {'***' + os.getenv('WHATSAPP_ACCESS_TOKEN', '')[-8:] if os.getenv('WHATSAPP_ACCESS_TOKEN') else 'NOT SET'}") print(f" Phone Number ID: {os.getenv('WHATSAPP_PHONE_NUMBER_ID', 'NOT SET')}") print(f" API Version: {os.getenv('WHATSAPP_API_VERSION', 'v20.0')}") if not os.getenv('WHATSAPP_ACCESS_TOKEN') or not os.getenv('WHATSAPP_PHONE_NUMBER_ID'): print("\n✗ ERROR: WhatsApp credentials not configured in .env file") return False try: service = WhatsAppService(db=db) print("\n" + "-"*80) print("Sending message to Meta WhatsApp Cloud API...") print("-"*80) result = await service.send_by_template_key( template_key=template_key, to_phone=target_phone, params=params, event_id=params['event_id'], guest_id=None, ) print("\n" + "="*80) print("✓ MESSAGE SENT SUCCESSFULLY!") print("="*80) print(f"\nResponse:") print(json.dumps(result, indent=2, ensure_ascii=False)) wamid = result.get('message_id', 'N/A') print(f"\n✓ WhatsApp Message ID (wamid): {wamid}") print(f"✓ Sent to: {result.get('to', 'N/A')}") print(f"✓ Template: {result.get('template', 'N/A')}") print(f"✓ Status: {result.get('status', 'N/A')}") print("\n" + "="*80) print("IMPORTANT: Check WhatsApp Webhook for Delivery Status") print("="*80) print(""" The message was accepted by Meta (HTTP 200), but this does NOT mean it was delivered. To confirm delivery: 1. Configure webhook in Meta Business Manager: URL: https://your-domain.com/whatsapp/webhook Verify Token: (from WHATSAPP_VERIFY_TOKEN in .env) 2. Subscribe to 'messages' webhook events 3. Watch the backend logs for webhook callbacks: - Status: 'sent' = Meta accepted - Status: 'delivered' = Recipient received - Status: 'read' = Recipient opened - Status: 'failed' = Delivery failed (check error details) 4. Query the database for message status: SELECT * FROM whatsapp_messages WHERE wamid = '{wamid}'; If the message stays in 'sent' status and never reaches 'delivered', the problem is with Meta's delivery, not your code. Common reasons for non-delivery: - Phone number not registered with WhatsApp - Phone number blocked your business account - Template not approved or parameters mismatch - Image URL not accessible to Meta servers - Rate limiting or business account issues """) return True except WhatsAppError as e: print(f"\n✗ WhatsApp API Error: {e}") return False except Exception as e: print(f"\n✗ Unexpected error: {e}") logger.exception("Full traceback:") return False async def check_webhook_configuration(): """Check if webhook is configured""" print("\n" + "="*80) print("STEP 4: Webhook Configuration Check") print("="*80) verify_token = os.getenv('WHATSAPP_VERIFY_TOKEN', '') print(f"\nWebhook Settings:") print(f" Verify Token: {'***' + verify_token[-8:] if verify_token else 'NOT SET'}") print(f" Webhook URL: https://your-domain.com/whatsapp/webhook") if not verify_token: print("\n⚠ Warning: WHATSAPP_VERIFY_TOKEN not set in .env") print(" You need this to verify the webhook with Meta") else: print("\n✓ Verify token is configured") print("\nWebhook Endpoints Available:") print(" GET /whatsapp/webhook - Webhook verification") print(" POST /whatsapp/webhook - Status updates from Meta") print("\nTo configure webhook in Meta Business Manager:") print(" 1. Go to WhatsApp > Configuration > Webhook") print(" 2. Set Callback URL: https://your-domain.com/whatsapp/webhook") print(f" 3. Set Verify Token: {verify_token if verify_token else '(SET THIS IN .env)'}") print(" 4. Click 'Verify and Save'") print(" 5. Subscribe to 'messages' webhook field") async def main(): """Run all debug tests""" print("\n" + "="*80) print("WhatsApp Cloud API Debugging Tool") print("="*80) print(""" This script will: 1. Test phone number normalization 2. Validate template configuration 3. Send a test message with full logging 4. Show webhook configuration status """) db = SessionLocal() try: # Run tests test1 = await test_phone_normalization() test2 = await test_template_configuration(db) if not (test1 and test2): print("\n✗ Pre-flight checks failed. Fix errors above before sending.") return # Ask user to confirm send print("\n" + "="*80) response = input("Ready to send test message? (yes/no): ").strip().lower() if response not in ['yes', 'y']: print("Test cancelled.") return test3 = await test_send_message(db) await check_webhook_configuration() print("\n" + "="*80) print("Debug Session Complete") print("="*80) if test3: print("\n✓ Message sent successfully") print("\nNext steps:") print("1. Check if recipient received the message on WhatsApp") print("2. Watch backend logs for webhook status updates") print("3. Query database: SELECT * FROM whatsapp_messages;") else: print("\n✗ Message send failed - see errors above") finally: db.close() if __name__ == "__main__": asyncio.run(main())