Payment Gateway Webhooks
Ingress points
| URL | Controller | Notes |
|---|---|---|
POST /webhooks/gateways/{code} | GatewayWebhookController | Gateway-agnostic extension point |
POST /stripe/webhook | StripeWebhookController | Extends Cashier; also dispatches Billing Engine |
Flow
Normalized events
GatewayEvent types:
payment_succeeded/payment_failedsubscription_created/subscription_updated/subscription_cancelledunsupported(logged as ignored)
Drivers should populate tenantId when resolvable so BillingEngine stays provider-agnostic. If not, include meta.payment_id (and/or providerSubscriptionId) so the engine can still resolve the workspace for payment_failed without a customer map.
Stripe events handled for billing activation
invoice.payment_succeededinvoice.payment_failedcustomer.subscription.*(cancel → deactivate licensing)
Cashier still handles its own mirror tables on /stripe/webhook.
Creem events handled for billing activation
Signature header: creem-signature (HMAC-SHA256 of raw body).
subscription.paid/checkout.completed→ activate pending modulessubscription.active/subscription.update→ link provider subscription idsubscription.canceled/subscription.expired→ cancel module licensingsubscription.past_due→payment_failedrefund.created→ ignored (unsupported) after signature verification
See Creem gateway for the full event matrix.
Local development
bash
stripe listen --forward-to https://your-app.test/stripe/webhook
# or
stripe listen --forward-to https://your-app.test/webhooks/gateways/stripe
# Creem: forward sandbox webhooks (ngrok / Herd) to:
# https://your-app.test/webhooks/gateways/creemUse the CLI webhook signing secret for local verification (whsec_… for Stripe; Creem dashboard webhook secret for Creem).