from fastapi import APIRouter, Depends, HTTPException, Request, Query from sqlalchemy.orm import Session from app.db.base import get_db from app.core.config import settings from app.models.campaign import CampaignRecipient, RecipientStatus import logging router = APIRouter() logger = logging.getLogger(__name__) @router.get("/whatsapp") async def whatsapp_webhook_verify( request: Request, hub_mode: str = Query(None, alias="hub.mode"), hub_challenge: str = Query(None, alias="hub.challenge"), hub_verify_token: str = Query(None, alias="hub.verify_token") ): """ Verify webhook for WhatsApp Cloud API. Meta will send a GET request with these parameters. """ if hub_mode == "subscribe" and hub_verify_token == settings.WHATSAPP_WEBHOOK_VERIFY_TOKEN: logger.info("Webhook verified successfully") return int(hub_challenge) else: raise HTTPException(status_code=403, detail="Verification failed") @router.post("/whatsapp") async def whatsapp_webhook_handler( request: Request, db: Session = Depends(get_db) ): """ Handle webhook updates from WhatsApp Cloud API. Updates message statuses (sent, delivered, read, failed). """ try: body = await request.json() logger.info(f"Webhook received: {body}") # Parse WhatsApp webhook payload # Structure: https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/payload-examples entry = body.get("entry", []) if not entry: return {"status": "no_entry"} for item in entry: changes = item.get("changes", []) for change in changes: value = change.get("value", {}) statuses = value.get("statuses", []) for status_update in statuses: message_id = status_update.get("id") status = status_update.get("status") # sent, delivered, read, failed if not message_id or not status: continue # Find recipient by provider message ID recipient = db.query(CampaignRecipient).filter( CampaignRecipient.provider_message_id == message_id ).first() if not recipient: logger.warning(f"Recipient not found for message_id: {message_id}") continue # Update status if status == "sent": recipient.status = RecipientStatus.SENT elif status == "delivered": recipient.status = RecipientStatus.DELIVERED elif status == "read": recipient.status = RecipientStatus.READ elif status == "failed": recipient.status = RecipientStatus.FAILED errors = status_update.get("errors", []) if errors: recipient.last_error = errors[0].get("message", "Unknown error") logger.info(f"Updated message {message_id} to status {status}") db.commit() return {"status": "ok"} except Exception as e: logger.error(f"Webhook error: {str(e)}") return {"status": "error", "message": str(e)}