Webhooks
RESTHeartPOST /stripe/webhook receives Stripe events and applies them — updating subscription state or order status, recording transactions.
It is the mechanism that makes billing actually work: Checkout only opens a payment page, and nothing in the application changes until Stripe tells the server what happened.
|
Important
|
Without |
The only public endpoint
/stripe/webhook has no authenticated caller: Stripe calls it directly.
The module registers its own ACL allow rule for it — that exact path, POST and OPTIONS only.
Its security is signature verification.
Every request must carry a Stripe-Signature header that verifies against stripeConfig.webhook-secret.
A request without a valid signature is rejected before anything is parsed.
Status codes
| Code | Meaning |
|---|---|
|
Event verified. Handled, ignored, or skipped as stale — all are success. |
|
Signature failed, header missing, or no webhook-secret configured. |
|
Unexpected failure (e.g. MongoDB). Stripe will retry. |
Events handled — Subscriptions
| Event | Effect |
|---|---|
|
Logged only. Stripe follows with |
|
Rebuilds full subscription state. |
|
Plan change, renewal, quantity change, cancellation scheduling. |
|
Resets to |
|
Sends the |
|
Status → |
|
Status → |
|
Invalidates the plans display cache. |
Events handled — Products
| Event | Effect |
|---|---|
|
Order → |
|
Order → |
|
Order → |
|
Order → |
|
Appends refund transaction. Updates |
|
Appends dispute transaction. |
Which events to subscribe to
In Stripe Dashboard → Developers → Webhooks, subscribe to:
# subscriptions
checkout.session.completed
customer.subscription.created
customer.subscription.updated
customer.subscription.deleted
customer.subscription.trial_will_end
invoice.payment_succeeded
invoice.payment_failed
product.updated
price.updated
# products
checkout.session.async_payment_succeeded
checkout.session.async_payment_failed
charge.refunded
charge.dispute.created
checkout.session.completed appears in both lists — it is handled differently per mode.
Idempotency
Subscriptions — every write is conditional on the event timestamp being newer than the last one applied to that entity. Out-of-order delivery and redelivery are both no-ops.
Products — order status transitions are monotonic (the update filter asserts current status). The transactions collection has a unique index on stripe_event_id, so a redelivered event cannot double-record a refund.
Unknown customers
An event for a Stripe Customer that no entity is linked to is logged and ignored. This is normal when a Stripe account is shared with another application.
Local development
Stripe cannot reach localhost. Use the Stripe CLI:
stripe listen --forward-to localhost:8080/stripe/webhook
The CLI prints a signing secret (whsec_…) for that session — use it as STRIPE_WEBHOOK_SECRET.
Trigger events:
stripe trigger customer.subscription.updated
stripe trigger checkout.session.completed
stripe trigger charge.refunded