Stripe Integration¶
Keygate integrates Stripe end-to-end: a customer pays and a license is created (or renewed) automatically, with three-layer reliability — the webhook, success-page verification, and a periodic sync — so no payment is ever missed.
Setup¶
Always start in Stripe test mode.
- Create Products/Prices in Stripe for your paid plans. Recurring Prices for subscriptions, one-time Prices for perpetual/renewal. Copy each
price_…id. - Set the secret key:
STRIPE_SECRET_KEY=sk_test_…in.env, then recreate the container (docker compose up -d). - Webhook auto-registers. With
STRIPE_SECRET_KEYset,STRIPE_WEBHOOK_SECRETempty, andBASE_URLpublic, Keygate creates the Stripe webhook endpoint at{BASE_URL}/api/v1/webhook/stripeautomatically and stores the signing secret. Confirm in the logs.- Manual alternative: create the endpoint in the Stripe dashboard yourself and set
STRIPE_WEBHOOK_SECRET=whsec_….
- Manual alternative: create the endpoint in the Stripe dashboard yourself and set
- Link plans to prices. In the admin dashboard, set each paid plan's
stripe_price_idto the matching Stripeprice_….
Livemode safety¶
STRIPE_LIVEMODE is auto-derived from the key prefix. Every inbound webhook event whose livemode flag doesn't match the server's configured mode is rejected — so a leaked test secret can't replay forged events into production.
Checkout¶
Each plan has a checkout_id; the hosted checkout URL is:
Point your "Buy" buttons at these. The flow: customer clicks → Keygate creates a Stripe Checkout Session (subscription or one-time, matching the Price type) → customer pays → the webhook fulfills.
Return URLs: success goes to {BASE_URL}/checkout/success, cancel to {BASE_URL}/pricing.
What the webhook does¶
On checkout.session.completed (and related events), Keygate:
- Creates the license on the purchased plan, emails the key to the customer, and records the Stripe customer/subscription ids.
- For subscriptions: advances
valid_untiloninvoice.paid, moves topast_dueoninvoice.payment_failed(starting the dunning ladder), and handles cancellations/refunds/disputes.
All fulfillment is idempotent (keyed on the Stripe session/event id), so retried or duplicated events never double-create.
Testing¶
Use Stripe's test card 4242 4242 4242 4242 (any future expiry/CVC). After paying, confirm the new license appears in the dashboard and the key arrives by email.
Going live¶
Stripe test and live are separate environments:
- Recreate the Products/Prices in live mode → new
price_…ids. - Swap
STRIPE_SECRET_KEYtosk_live_…, recreate the container. - Update each plan's
stripe_price_id(andsupport_renewal_price_id) to the live ids. - Re-test with a real card.
Support-window renewal¶
A dedicated flow lets customers extend their support window without creating a new license:
- Create a one-time Stripe Price for the renewal and set it on the plan's
support_renewal_price_id. - The client calls
POST /license/support/checkoutwith{ "license_key": "KG-…" }and gets back{ "checkout_url": "https://…" }; open it for the customer. - On payment, the webhook (routing on a
purpose=support_renewalmetadata flag) extendssupport_until— anchored atmax(now, current support_until)(early renewers keep their remaining time), by the plan'ssupport_days(default 365). - Fires a
license.support_renewedwebhook and sends a confirmation email. Idempotent on the Stripe session.
If the plan has no support_renewal_price_id, the checkout endpoint returns 503 RENEWAL_NOT_AVAILABLE. Unknown license keys collapse to 404.