Webhooks
Inbound Stripe events FloPay subscribes to and how they map to outbound Flo webhooks.
Stripe Events
When a client provisions a Stripe gateway, FloPay subscribes to a fixed set of Stripe webhook events at the gateway level and normalizes them into the existing outbound Flo webhook families.
The outbound contract does not change. Stripe events arrive at your client webhook endpoint as the same subscription.*, item.*, and invoice.* events documented in Events. This page is reference material for what happens upstream — your handler code does not need to branch on gateway. The outbound payload shape is identical regardless of which gateway produced the underlying event.
Subscribed Stripe Events
FloPay subscribes to the following 26 Stripe webhook events. These are configured on the Stripe webhook endpoint pointed at FloPay's inbox.
| Stripe event | Purpose |
|---|---|
charge.refunded | A charge was refunded (full or partial). |
customer.created | A Stripe customer was created. |
customer.updated | A Stripe customer's details changed. |
customer.deleted | A Stripe customer was deleted. |
customer.subscription.created | Subscription created in Stripe. |
customer.subscription.updated | Subscription details changed (plan, quantity, cancel-at, pause, reactivation, etc.). |
customer.subscription.deleted | Subscription was deleted / ended in Stripe. |
customer.subscription.paused | Subscription was paused. |
customer.subscription.resumed | Subscription was resumed from a paused state. |
invoice.created | A draft invoice was created. |
invoice.updated | Invoice fields changed (totals, line items, metadata). |
invoice.deleted | A draft invoice was deleted. |
invoice.finalized | Invoice was finalized and is now collectible. |
invoice.sent | Invoice was sent to the customer. |
invoice.upcoming | Stripe rendered an upcoming invoice for an active subscription. |
invoice.will_be_due | Stripe is signalling the invoice is about to be due. |
invoice.payment_action_required | Invoice payment requires SCA / 3DS action. |
invoice.payment_failed | An invoice payment attempt failed. |
invoice.payment_succeeded | An invoice payment attempt succeeded — Flo treats invoice.paid as authoritative. |
invoice.paid | Invoice was paid in full. |
invoice.overdue | Invoice passed its due date and is overdue. |
invoice.voided | Invoice was voided. |
invoice.marked_uncollectible | Invoice was marked uncollectible. |
payment_intent.succeeded | A one-off PaymentIntent succeeded. |
payment_intent.payment_failed | A PaymentIntent attempt failed — Flo records the decline reason locally. |
refund.created | A refund object was created against a charge. |
Signature Verification
FloPay verifies each delivery locally using Stripe's official SDK (Stripe.webhooks.constructEvent) against the HMAC SHA-256 signing secret stored in the gateway record's webhook_signing_secret column. The verifier compares the Stripe-Signature header to the signed payload with Stripe's standard timestamp tolerance — deliveries with a missing signature, malformed header, mismatched HMAC, or stale timestamp are rejected non-retriably.
Inbound → Outbound Mapping
| Stripe event | Outbound Flo event | Notes |
|---|---|---|
charge.refunded | item.refunded | Maps to the refunded charge's line. |
customer.created | (no outbound) | Internal: FloPay associates the Stripe customer id with the Flo user gateway record. |
customer.updated | (no outbound) | Internal: stored on the gateway record. |
customer.deleted | (no outbound) | Internal: gateway-record cleanup. |
customer.subscription.created | subscription.created | |
customer.subscription.updated | subscription.updated, subscription.cancelled, subscription.reactivated, subscription.paused, or subscription.resumed | FloPay inspects cancellation_details, canceled_at, and pause_collection (against previous_attributes) to pick the right transition. Plain field edits fall through to subscription.updated. |
customer.subscription.deleted | subscription.expired | Stripe's "deleted" subscription is normalized to Flo's terminal expired state. |
customer.subscription.paused | subscription.paused | |
customer.subscription.resumed | subscription.resumed | |
invoice.created | invoice.created | |
invoice.updated | invoice.updated | |
invoice.deleted | invoice.deleted | |
invoice.finalized | (no outbound) | Internal state transition; the matching outbound activity rides on invoice.paid / invoice.overdue. |
invoice.sent | (no outbound) | Stripe-driven email notification; not a Flo lifecycle event. |
invoice.upcoming | (no outbound) | Renewal preview only — Flo waits for the real invoice.paid cycle. |
invoice.will_be_due | (no outbound) | Pre-due signal; not surfaced to clients. |
invoice.payment_action_required | (no outbound) | SCA / 3DS prompt is handled on the checkout surface, not via outbound webhook. |
invoice.payment_failed | invoice.payment_failed | Emitted per failed attempt. |
invoice.payment_succeeded | (no outbound) | Suppressed: invoice.paid is the authoritative settlement event. |
invoice.paid | invoice.paid, plus subscription.renewed and/or item.purchased | A single Stripe invoice.paid can fan out: an invoice.paid event always, a subscription.renewed if billing_reason is subscription, subscription_cycle, or subscription_threshold, and an item.purchased for each non-subscription line on the invoice. |
invoice.overdue | invoice.overdue | The invoice is past due but still collectible. |
invoice.voided | (no outbound) | Voids are reflected by the absence of a paid event. |
invoice.marked_uncollectible | (no outbound) | Stripe-side state only. |
payment_intent.succeeded | item.purchased | Suppressed when the PaymentIntent is invoice-backed — invoice.paid carries the line items in that case. Requires a resolvable catalog identity (metadata.productCodes[0] or a matched checkout item); otherwise dropped with a warning. |
payment_intent.payment_failed | (no outbound) | Internal: FloPay records a CheckoutTransactionAttempt row with the decline reason mapped from last_payment_error. |
refund.created | item.refunded | FloPay loads the parent charge (expanding refunds.data) and emits an item-level refund event. |
customer.subscription.updated carries a lot of Stripe's lifecycle traffic. FloPay normalizes on its own state transition — comparing the subscription against previous_attributes — rather than the inbound event name, so consumers see the specific subscription.cancelled / subscription.reactivated / subscription.paused / subscription.resumed event when that is the real change.
Recurring Billing Behaviour
Stripe — not Flo — drives the recurring billing cycle for Stripe subscriptions. Renewals arrive as invoice.paid with billing_reason in subscription, subscription_cycle, or subscription_threshold, and FloPay emits subscription.renewed alongside the invoice.paid event.
Off-session retries (Smart Retries) are Stripe's responsibility. FloPay does not issue additional retries — invoice.payment_failed fires for each failed attempt, and invoice.overdue arrives once Stripe's dunning window has elapsed.
Related
- Events — full outbound event catalog and examples
- Payloads — outbound field-level schema
- Overview — outbound webhook contract
- PayPal via Stripe — Stripe-rendered PayPal fallback path