SaaSFactory · sample report · reviewed 14 Sep 2026

Stripe integration audit — hyperflask/flask-stripe-checkout

This is a real example of what our done-for-you audit delivers. The subject is a small, well-written open-source Flask extension (github.com/hyperflask/flask-stripe-checkout, branch main). The core is solid: webhook signatures are verified with stripe.Webhook.construct_event on the raw body, and no secret keys are committed. The findings below are the kind of edge cases that pass review and bite in production. They are offered constructively to the maintainers and to anyone using the library.

Method: manual read of every Python file under flask_stripe_checkout/ + our 7-rule scanner (webhook signature, raw-JSON parsing, hardcoded keys, idempotency, legacy Charges API, client-controlled amount, event dedup).

High · fulfilment on redirect without payment check

cart_blueprint.py, checkout_success(): on GET /cart/checkout/success?session_id=… the extension retrieves the session and sends the checkout.session.success signal (exposed as webhook.checkout_session_success). It never checks session.payment_status == "paid", and the URL can be reloaded or shared.

Impact: apps that fulfil on this signal can fulfil twice (page reload) or fulfil orders paid with delayed methods (SEPA, ACH, Boleto) that later fail.

Fix: check payment_status before sending the signal, and document that fulfilment belongs in the checkout.session.completed / checkout.session.async_payment_succeeded webhooks.

Medium · webhook secret silently falls back to the Flask secret key

__init__.py, init_app(): webhooks_endpoint_secret = … or app.secret_key. A Stripe signing secret is a whsec_… value; the Flask SECRET_KEY never matches, so a missing config makes every webhook return 400 with no hint why.

Fix: raise a clear error at startup (or log a warning) when STRIPE_WEBHOOKS_ENDPOINT_SECRET is not set and the endpoint is enabled.

Medium · no deduplication of webhook events

webhooks.py, dispatch_webhook_event_as_signal(): every delivery is dispatched; Stripe retries deliveries and can send the same event more than once. Scanner rule eventos_duplicados: FALTA.

Fix: offer an optional hook (e.g. is_duplicate(event_id)) or document that handlers must be idempotent on event["id"].

Medium · upgrade_subscription() crashes for customers with no subscription

subscription.py: subs = stripe.Subscription.list(customer=…) then if not subs:. The returned ListObject is a non-empty dict (it has object, data, has_more…), so it is always truthy; the new-subscription branch never runs and subs["data"][0] raises IndexError.

Fix: if not subs.data:. Also consider filtering by status="active".

Low · missing Stripe-Signature header returns 500

webhooks.py: request.headers["Stripe-Signature"] raises KeyError (HTTP 500) when the header is absent, instead of a 400. Use request.headers.get(...).

Low · no idempotency key on Checkout Session creation

cart.py, create_checkout_session(): scanner rule idempotencia: FALTA. Low risk for Checkout Sessions (a duplicate just creates an unused session), listed for completeness.

Scanner summary

webhook_firma        OK      (construct_event, webhooks.py)
webhook_json_crudo   OK
clave_en_codigo      OK
api_legacy_charges   OK
precio_desde_cliente OK      (amounts set server-side; cart kept in signed session)
idempotencia         FALTA   (cart.py)
eventos_duplicados   FALTA   (webhooks.py)

Want this for your own repo?

Same format, on your code: manual review + scanner, findings with file and function references and concrete fixes. $39, one-time. Public GitHub/GitLab repos only.

See how it works →

Maintainers of flask-stripe-checkout: this review is free and yours to use; if anything here is wrong, it will be corrected.