Webhooks
Unchurn receives inbound Stripe webhooks to reconcile subscription events with session outcomes. This page documents those inbound webhooks.
Scope
app/app/api/webhooks/stripe/ handles inbound Stripe webhooks — events Stripe sends to the Unchurn backend (not merchant-outbound webhooks delivered to the merchant’s server).
The webhook handler is internal to the Unchurn platform. Merchants do not configure this endpoint; it is wired during Stripe Connect onboarding.
Merchant-outbound webhooks (outcomes delivered to the merchant’s server) are referenced in the architecture overview but are not yet exposed as a configurable merchant surface. They will be documented here when the outbound webhook dispatcher is released.
Inbound Stripe webhook endpoints
| Path | Mode | Description |
|---|---|---|
/api/webhooks/stripe/live | live | Receives live-mode Stripe Connect events |
/api/webhooks/stripe/test | test | Receives test-mode Stripe Connect events |
/api/webhooks/stripe/platform | platform | Receives platform-level Stripe events |
Signature verification
All inbound Stripe webhooks are verified using stripe.webhooks.constructEvent:
// app/app/api/webhooks/stripe/_shared/handler.ts
const SIGNATURE_HEADER = 'stripe-signature';
event = verifier.webhooks.constructEvent(rawBody, signature, webhookSecretForMode(mode));| Header | Value |
|---|---|
stripe-signature | Stripe-generated HMAC signature (also accepted as Stripe-Signature) |
Secrets are environment-specific:
STRIPE_CONNECT_WEBHOOK_SECRET_LIVE— live modeSTRIPE_CONNECT_WEBHOOK_SECRET_TEST— test mode
Requests missing the stripe-signature header are rejected with HTTP 400. Invalid signatures are rejected with HTTP 400 { "error": "invalid_signature" }.
Mode enforcement
Every Stripe event carries a livemode boolean. The handler cross-checks this against the expected mode for the endpoint:
- A live-mode event received at
/api/webhooks/stripe/testis rejected with HTTP 400{ "error": "mode_mismatch" } - A test-mode event received at
/api/webhooks/stripe/liveis rejected with HTTP 400{ "error": "mode_mismatch" }
Response codes
| Status | Condition |
|---|---|
200 { "ok": true } | Event handled successfully |
400 { "error": "invalid_signature" } | Stripe signature verification failed |
400 { "error": "mode_mismatch" } | Event livemode does not match the endpoint’s expected mode |
400 | Missing stripe-signature header |
Stripe interprets any non-2xx response as a delivery failure and retries.
Cross-links
- Architecture — how Stripe Connect and webhooks fit into the four-component picture
- Environment variables — webhook secret env var names