sendio/backend/app/api/webhooks.py
2026-01-13 05:17:57 +02:00

92 lines
3.5 KiB
Python

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)}