import json from datetime import datetime, timezone from fastapi import APIRouter, Depends, Header, HTTPException, Request from sqlmodel import Session from ..config import get_settings from ..db import get_session from ..models import InboundEvent from ..services.event_normalizer import normalize_meta_payload from ..services.meta_signature import verify_meta_signature from ..services.openclaw_client import send_event_to_openclaw router = APIRouter(prefix="/meta", tags=["meta-webhooks"]) @router.get("/webhook") async def verify_webhook(request: Request): settings = get_settings() params = dict(request.query_params) if params.get("hub.mode") == "subscribe" and params.get("hub.verify_token") == settings.meta_webhook_verify_token: challenge = params.get("hub.challenge") return int(challenge) if challenge and challenge.isdigit() else challenge raise HTTPException(status_code=403, detail="Webhook verification failed") @router.post("/webhook") async def receive_webhook(request: Request, session: Session = Depends(get_session), x_hub_signature_256: str | None = Header(default=None)): raw = await request.body() verify_meta_signature(raw, x_hub_signature_256) try: payload = json.loads(raw.decode("utf-8")) normalized_events = normalize_meta_payload(payload) except HTTPException: raise except Exception as exc: raise HTTPException(status_code=400, detail=f"Could not normalize payload: {exc}") stored = [] for event in normalized_events: db_event = InboundEvent(**event.model_dump(), raw_payload=payload) session.add(db_event) session.commit() session.refresh(db_event) try: await send_event_to_openclaw(event, db_event.id or 0) db_event.forwarded_at = datetime.now(timezone.utc) except Exception as exc: db_event.forward_error = str(exc)[:1000] session.add(db_event) session.commit() stored.append(db_event.id) return {"status": "ok", "stored_event_ids": stored}