All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
314 lines
11 KiB
Python
314 lines
11 KiB
Python
"""
|
|
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())
|