Stripe & Subscriptions
How Stripe is actually wired in — where the keys live, the webhook endpoint, the pinned API version, and reacting to events.
Connecting Stripe
Stripe is the platform’s payment gateway for subscriptions and card payments, and it is also what drives card readers.
Where to find it
Architect Panel → Configuration:
- Instance Configuration Fields — where the secret key lives, per tenant
- Platform Modules — the commerce module
Architect Panel → Subscriptions:
- Subscription Packages — what customers can buy
- Subscribers — who has bought
- Card Readers — Stripe Terminal devices
The keys live in instance configuration
Specifically the secret key, held per tenant and read at runtime. That is deliberate: it means each tenant can have its own Stripe account, and it means the key is encrypted and restricted rather than sitting in a file.
If it is missing, the webhook endpoint refuses the request and writes an explicit error rather than failing quietly.
The webhook signing secret is separate
And equally required. It comes from the endpoint’s settings in your Stripe dashboard, and without it every webhook is rejected — the platform refuses outright rather than letting each request fail as an opaque error.
Settings that shape subscriptions
- Subscriptions enabled — off by default.
- Maximum subscriptions per product — whether a customer can hold more than one of the same package.
- Link subscriptions to tenants — whether a subscription belongs to a tenant rather than a person, which is what makes per-customer billing work in a multi-tenant installation.
- Discounts enabled — whether coupon codes are offered.
- Single card — whether a customer may store more than one card.
Direct Debit runs alongside
Rather than replacing Stripe. It has its own per-tenant credentials, and switching it on takes two deliberate steps — enabling the payment method and ticking it on a plan — so a half-configured gateway can never be advertised at checkout.
Test with test keys first
The obvious advice that gets skipped under deadline. A subscription created against live keys is a real charge, and unpicking one is more work than testing properly would have been.
Who can read the keys
Anybody who can read instance configuration can read your Stripe secret key, which is control of your payment account. Keep that to a very small group, and remember the audit log for that table holds the values too.
Worked example
An installation holds each tenant’s Stripe secret in instance configuration, restricted to two administrators, with subscriptions linked to tenants so billing follows the customer organisation rather than whoever signed up. Test keys were used until a full sign-up, upgrade and cancellation had been exercised.
Recommendations
- Keys in instance configuration, never in a file.
- Set the signing secret or no webhook will be accepted.
- Exercise sign-up, change and cancel on test keys.
- Restrict who can read the configuration and its audit log.
The Pinned API Version
Stripe versions its API by date, and the platform pins to one rather than following the latest.
Where to find it
Architect Panel → Configuration:
- Site Settings — installation-level settings
- Instance Configuration Fields — per-tenant gateway settings
The pinned version is an application configuration value rather than a screen — it is set alongside the other Stripe settings in the installation’s configuration file.
Why pin at all
Because an unpinned integration changes underneath you. Stripe adds fields, changes defaults and adjusts payloads; pinned, you get the behaviour you tested against, and Stripe maintains that version’s behaviour.
The alternative is an integration that works until the day it does not, for reasons nothing in your own change history explains.
It is an old version, and that is fine
A pinned version being years old is not by itself a problem. Stripe supports old versions for a long time, and the integration was built and tested against that behaviour.
What matters is that it is deliberate, and that somebody knows what it is.
When to move it
- A feature you need exists only in a newer version.
- Stripe has given notice that your version is being retired.
- You are already making substantial changes to the payment integration and can test properly.
Not simply because a newer one exists.
Moving it is a payment change
Which is the highest-consequence category of change you can make. Read Stripe’s changelog across every version between yours and the target, test the full lifecycle against test keys, and have a way back.
The webhook payloads change too
Easy to forget, because webhooks feel separate from API calls. If you have automation reacting to Stripe events and reading fields from the payload, a version change can alter those fields.
Record it
The pinned version, when it was last moved, and what was tested. This is a fact that matters once every few years and is impossible to reconstruct.
Worked example
An installation records its pinned version alongside the date it was set. When Stripe announced a deprecation, the team read the changelog across the gap, moved the pin in a test environment, exercised sign-up, upgrade, cancellation and refund, and checked that the rules reading webhook payloads still found the fields they expected.
Recommendations
- Know your pinned version and when it was set.
- Move it for a reason, not for currency.
- Test the whole lifecycle, not one payment.
- Check automation that reads webhook payloads.
Stripe Webhooks
Webhooks are how Stripe tells the platform what happened — a payment succeeded, a subscription changed, a dispute opened.
Where to find it
Architect Panel → Automation:
- Workflow Builder — rules that fire on a webhook event
Architect Panel → Activity:
- Error Log — rejected or malformed webhooks
Architect Panel → Configuration:
- Instance Configuration Fields — the API key and signing secret
The endpoint
Stripe posts to the platform’s subscription webhook endpoint, with the tenant identified in the request. That tenant determines which Stripe account’s key is used to verify and process the event — which is what allows several tenants to have separate Stripe accounts on one installation.
Every request is verified
Against the signing secret, using Stripe’s own verification. The platform is strict about this on purpose:
- No signing secret configured — the request is refused and an error written, rather than every webhook failing obscurely.
- No signature header — refused.
- Signature does not verify — refused.
This matters because the endpoint is public. Without verification, anybody could post a "payment succeeded" event.
Every event is logged
Each event is recorded with its type, its Stripe event identifier, the object it concerned and its timestamp. Event types are registered automatically the first time they are seen, so the list of types builds itself from real traffic rather than needing to be predicted.
Full payload logging exists but is off by default, because payloads contain customer data.
Reacting to events is automation, not code
The important point. Every verified event fires the platform’s rules — before and after processing — with the event type as the trigger.
So handling payment failed means writing a rule against that event, not editing a handler. Notifying somebody about a dispute, flagging an account when a subscription is cancelled, recording an invoice: all of these are rules.
Which events to react to
Start narrow. Failed payments and cancellations are the two most organisations need. Add others when there is a specific thing you want to happen, rather than building rules for events nobody acts on.
The log is your diagnostic
When something did not happen, the first question is whether the event arrived. The log answers that immediately, and separates "Stripe did not tell us" from "we were told and did nothing".
The gateway log is shared
Direct Debit events are recorded in the same place, distinguished by gateway. So a single view shows everything that arrived from any payment provider.
Worked example
A shop has two rules: a failed payment notifies the account owner and flags the subscriber record, and a cancellation writes a note against the customer. When a customer claimed they had never been told about a failed payment, the event log showed the event arriving and the rule firing that morning.
Recommendations
- Set the signing secret — nothing works without it.
- Handle events with rules, not custom code.
- Start with failures and cancellations.
- Check the event log first when something did not happen.
- Leave verbose payload logging off unless diagnosing.
Failed Payments
Payments fail routinely — expired cards, insufficient funds, fraud blocks. What happens next is something you build, not something that is configured.
Where to find it
Architect Panel → Automation:
- Workflow Builder — the rules that react to a failure
- Journeys — a multi-step recovery sequence
Architect Panel → Subscriptions:
- Subscribers — who is affected
How you find out
Stripe sends a webhook. It is verified, logged, and fires the platform’s rules with the event type as the trigger. Everything after that is yours to define.
There is no built-in dunning schedule waiting to be switched on — which is worth knowing, because assuming one exists means nobody is chasing anything.
Stripe retries on its own
Configured in the Stripe dashboard, not here. Set the retry schedule there and the recovery process here, and be careful not to build a second retry cycle that conflicts with Stripe’s.
A workable process
- First failure — record it against the subscriber, e-mail the customer. Do not restrict anything.
- Continued failure — a second message, more direct, with a way to update the card.
- Final notice — say plainly what will happen and when.
- Restrict — reduce access rather than deleting anything.
- Cancel — and keep the record.
Build it as a journey rather than as separate rules, so the sequence is visible in one place and somebody can see where a customer has reached.
Failures are usually not deliberate
Most are an expired card. The tone of the first message should assume that, because treating an ordinary card expiry as a debt-collection matter loses customers who fully intended to pay.
Restrict, do not delete
A customer who resolves the payment should get their access back intact. Deleting data over a failed payment turns a recoverable situation into a lost customer, and occasionally into a data-protection complaint.
Give somebody the list
Automation handles the routine cases; the ones that need a person need a person. A view of subscribers in failure, checked weekly, catches the customer who has been quietly failing for a month because their e-mail address is also wrong.
Watch the recovery rate
If most failures recover after the first message, your process works. If they do not, the problem is usually that the messages are not reaching people — check what address they go to and whether it bounces.
Worked example
A subscription business runs a five-step journey from first failure to cancellation over three weeks, restricting rather than deleting at day fourteen. A weekly view of accounts in failure is reviewed by one person. Most recover at the first e-mail; the review catches the handful whose contact address is stale.
Recommendations
- Build the process — there is no default one.
- Set retries in Stripe, recovery here, and do not duplicate.
- Assume an expired card in the first message.
- Restrict rather than delete.
- Review the failure list weekly with a person.