pounce/backend/app/api/webhooks.py
Yves Gugger 5b99145fb2
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
fix: banner position and Sedo affiliate links
2025-12-16 09:02:00 +01:00

103 lines
3.1 KiB
Python

"""
Webhook endpoints for external service integrations.
- Stripe payment webhooks
- Future: Other payment providers, notification services, etc.
"""
import logging
import os
from datetime import datetime
from fastapi import APIRouter, HTTPException, Request, Header, status
from app.database import get_db
from app.services.stripe_service import StripeService
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/stripe/test")
async def test_stripe_webhook():
"""
Test endpoint to verify webhook route is accessible.
Use this to verify the webhook URL is correct.
The actual Stripe webhook should POST to /api/v1/webhooks/stripe
"""
return {
"status": "ok",
"message": "Stripe webhook endpoint is accessible",
"endpoint": "/api/v1/webhooks/stripe",
"method": "POST",
"stripe_configured": StripeService.is_configured(),
"webhook_secret_set": bool(os.getenv("STRIPE_WEBHOOK_SECRET")),
"timestamp": datetime.utcnow().isoformat(),
}
@router.post("/stripe")
async def stripe_webhook(
request: Request,
stripe_signature: str = Header(None, alias="Stripe-Signature"),
):
"""
Handle Stripe webhook events.
This endpoint receives events from Stripe when:
- Payment succeeds or fails
- Subscription is updated or cancelled
- Invoice is created or paid
The webhook must be configured in Stripe Dashboard to point to:
https://pounce.ch/api/v1/webhooks/stripe
Required Header:
- Stripe-Signature: Stripe's webhook signature for verification
"""
logger.info("🔔 Stripe webhook received")
if not stripe_signature:
logger.error("❌ Missing Stripe-Signature header")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Missing Stripe-Signature header",
)
if not StripeService.is_configured():
logger.error("❌ Stripe not configured")
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Stripe not configured",
)
# Get raw body for signature verification
payload = await request.body()
logger.info(f" Payload size: {len(payload)} bytes")
logger.info(f" Signature: {stripe_signature[:50]}...")
try:
async for db in get_db():
result = await StripeService.handle_webhook(
payload=payload,
sig_header=stripe_signature,
db=db,
)
logger.info(f"✅ Webhook processed successfully: {result}")
return result
except ValueError as e:
logger.error(f"❌ Webhook validation error: {e}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e),
)
except Exception as e:
logger.exception(f"❌ Webhook processing error: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Webhook processing failed",
)