92 lines
3.5 KiB
Python
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)}
|