Integration
Integrating Your Own Billing
secureFlows handles sign-in for your app. It does not process payments and does not remember which plan a person purchased — that part needs a small amount of extra setup. The fastest way to get it right is to have an AI coding tool build it for you. Copy the prompt below into whatever AI coding tool you're using (Cursor, Claude Code, GitHub Copilot, Lovable, and similar all work).
Before you start
This only works if your app has, or can have, some kind of backend — even a small one is enough. If your app is a website with no server-side code at all, tell your AI tool that up front; it should explain that this specific feature needs one, rather than trying to fake it. There is no safe way to remember “this person paid” using only code that runs in the visitor's own browser, because anything there can be seen and changed by that visitor.
A future version of secureFlows plans to support this directly for simple applications with no backend at all. That isn’t available yet — do not assume it already works. If it would help your project, let us know via Contact Us and check back later.
Copy this prompt into your AI coding tool
I want to record which subscription plan each signed-in user has purchased, safely — meaning a user must never be able to fake or edit their own plan. My app uses secureFlows for sign-in (https://www.secure-flows.com). secureFlows only handles identity and sessions — it does not process payments and does not store plan or billing data, so this needs to live in my own backend. Please implement the following, asking me anything you need first (which payment provider I use, which database, where backend code should live): 1. Get a stable, per-person id for the signed-in user by calling sf.fetchSessionIdentity(token) (GET /api/v1/sessions/identity with the session token) and use the userId field it returns. Do not use the session token or a session id for this — userId is the one that stays the same across logins. 2. When the user starts checkout with my payment provider, pass that userId through as custom metadata on the checkout (for example Paddle's custom_data, or Stripe's client_reference_id / metadata). 3. Add a backend endpoint that receives my payment provider's webhook directly, verifies its signature exactly as that provider's own documentation describes, and reads the userId back out of the metadata. 4. Store the resulting plan in my own database, keyed by that userId — not anywhere in secureFlows. 5. Use that stored value to decide whether a user can access a paid feature. Never gate a feature on anything stored in secureFlows session data, since that can be read and edited by the signed-in user's own browser.
What this sets up, in plain terms
- A private, unforgeable id for each signed-in person — think of it as an internal reference number nobody can fake.
- A connection between that id and your payment provider, made the moment someone starts checkout.
- A direct confirmation from your payment provider that money actually changed hands — not something the app itself decides.
- A record of that person's plan, kept in your own system, used to unlock (or not unlock) paid features.
If your AI tool (or existing code) suggests simply saving the plan as a piece of secureFlows session data, say no. Session data can be read and changed by the signed-in user themselves — it's fine for preferences, but never safe for anything that unlocks a paid feature.
Not sure this applies to you, or building without a backend at all? Get in touch via Contact Us.
Advanced: how this works, step by step — for developers who want the technical detail or to build it by hand
The model
secureFlows and billing are two separate systems, connected only by one shared, stable identifier. secureFlows proves who someone is and gives your app a private place to store their own data. Your backend verifies payment (directly with your billing provider, using whatever pattern that provider documents) and owns what plan that person is on. Nothing about this requires secureFlows to know your billing provider exists, and nothing about it requires your billing provider to know secureFlows exists — your backend is the only thing that talks to both.
| System | Owns | Never touches |
|---|---|---|
| secureFlows | Identity, sessions, the user’s own private data | Payments, plan status, your billing provider |
| Your backend | Plan status, billing history, feature gating | Passwords, identity providers, session tokens beyond reading them |
1Get a stable id for the user
Call GET /api/v1/sessions/identity
with the session token (or sf.fetchSessionIdentity(token) with
secureflows-js ≥ 0.1.15) and
use the userId it returns as the key that ties a person to their plan in your own database.
const identity = await sf.fetchSessionIdentity(token);
// { userId: 42, email: "[email protected]" }
Use userId, not the session token and not a session id. userId is stable for
this person across logins and devices; session tokens expire and are reissued, and a session id is
scoped to one login, not one person. Correlating billing records against something that rotates means
your plan records stop matching up the moment a user’s session changes — correlate against
the one thing that doesn’t.
2Pass it through checkout
When your app starts a checkout with your billing provider, include userId as custom
metadata on that checkout — most providers have a field for exactly this (for example
Paddle’s custom_data, or Stripe’s client_reference_id /
metadata). That value round-trips back to you in the provider’s webhook payload once
payment completes, which is what lets your backend match a payment event to a specific secureFlows user
in the next step.
3Verify the payment yourself
Your backend receives your billing provider’s webhook directly — secureFlows is not involved in this step at all. Verify it the way your provider documents (checking their signature against a secret only you and they know), the same as you would for any other webhook-based integration. This is standard practice for every payment provider and has nothing secureFlows-specific about it.
4Store and gate on your own data
Once your backend has verified the event, store the plan in your own database keyed by
userId. Feature gating, usage limits, upgrade prompts — all of it runs against your
own data, decided by your own backend. secureFlows never needs to know the result.
The pattern to avoid
Do not record the plan as a secureFlows session value (sf.session.set('plan', 'pro')) and
gate a paid feature on it. Session payload is readable and writable by whoever holds the session token
— that includes the end user’s own browser. Anyone can open devtools and set it to whatever
they want, bypassing payment entirely. Session data is fine for preferences and cosmetic state; it is
not a safe place to record anything a real feature or paywall depends on.
What secureFlows does not do
To be explicit: secureFlows does not verify payments, does not store billing or plan data, does not talk
to your billing provider, and does not need your billing provider’s credentials. It provides
exactly one thing this pattern depends on — a stable, opaque userId for each person.
Everything else described on this page runs entirely in your own backend and your billing provider.