Stop rebuilding login and billing for every product you ship.
A secure identity management and payment processing platform for SaaS application providers.
sysidm is the identity provider and revenue backbone your applications plug into. One standards-based login across everything you sell. Subscriptions, seats, trials and dunning enforced in a single place. Payments that settle in your merchant account. Deployed as your instance, on your issuer URL, with your keys.
Deployed for you
An enterprise deployment, not a shared account on someone else’s instance.
- Your issuer URL and your signing keys
- Your payment credentials, per environment
- Your existing users, migrated with their hashes intact
- A written integration contract and an end-to-end test
Every product you ship reinvents the same risky code
Registration, activation links, password reset, sessions, CSRF, seat counting, invitations, trials, plan changes, proration, failed payments, webhook signature verification, receipts. Each one is a security review item, and each one gets written again for the next product — slightly differently, by someone under deadline.
Leaves your codebase
Identity, accounts and money
- Password storage, reset and recovery
- Session issuance, rotation and revocation
- Registration, activation and invitations
- Seats, guests and account membership
- Plans, trials, upgrades, renewals and dunning
- Payment credentials and webhook verification
- Transactional email and its delivery record
Stays yours
Your actual product
- Your domain logic and your data model
- Your application-specific permissions
- Your UI — or ours, carrying your brand
- Your pricing, expressed as configuration
- Your merchant relationship, untouched
- Your roadmap, minus the compliance backlog
- Nothing else to patch at 2am
This is what your customers land on
One portal, one sign-in, every product. Each application is a tile; each tile lists the accounts that person belongs to with its own licence state, and signing in carries them straight into that account rather than dumping them on a home page to find their way.
Your applications
vikram · 4 accounts across 3 products · signed in once
The licence state on each row is not decoration — it is the same entitlement answer your application reads from the API on every request.
Standards, not a SDK
The reason an organisation can commit to this is that there is nothing proprietary to learn and nothing to be locked into. sysidm implements the published protocols and publishes its own configuration so your framework can discover them. Your engineers already know this surface.
What it buys you: any conformant client library in any language works. No vendor-specific auth code in your product, and no bespoke token format for a future team to reverse-engineer.
Grants: authorization_code for people, client_credentials for services,
refresh_token for long-lived sessions. Nothing else, because nothing else is needed.
The part that matters: code_challenge is required, and only S256 is
advertised. A browser SPA or mobile app is a public client — there is no secret in your bundle for
someone to extract.
Asymmetric by design: your application verifies tokens with a public key and never holds a shared secret. A compromised app server cannot mint tokens for another app.
Key rotation without a redeploy: verification keys are published at a documented endpoint, so rotating them is an operational act rather than a coordinated release across every product.
Self-describing: one URL gives your client library the issuer, endpoints, algorithms and supported claims. Leaving later is a configuration change, not a rewrite.
This is the live discovery document of the running instance, trimmed to the fields an integrator reads first. Not a datasheet claim — fetch it yourself.
GET /.well-known/openid-configuration { "issuer": "https://id.eafx.net", "authorization_endpoint": ".../oidc/authorize", "token_endpoint": ".../oidc/token", "jwks_uri": ".../oidc/jwks", "userinfo_endpoint": ".../v1/me", "end_session_endpoint": ".../oidc/session/end", "id_token_signing_alg_values_supported": ["RS256"], "response_types_supported": ["code"], "grant_types_supported": ["authorization_code", "client_credentials", "refresh_token"], "code_challenge_methods_supported": ["S256"] }
Security is the request path, not the roadmap
An identity provider is the highest-value target in your architecture, so it has to be measured by what it does on every single request rather than by a badge in a footer. These are the controls that are actually running.
Bearer artifacts are stored as hashes only
Authorization codes, refresh tokens, IdP sessions, email tokens, invitation tokens and API credentials are kept as SHA-256 digests. A stolen database dump cannot be replayed as access.
Refresh tokens rotate on every use
Each refresh mints a new token and revokes the one that was used, in the same call. Replaying a consumed token fails rather than extending a stolen session.
argon2id for passwords
19 MiB, time cost 2, parallelism 1. Legacy bcrypt and scrypt hashes are verified in place, so a migrated user base keeps its existing passwords and nobody is forced into a reset.
Third-party secrets encrypted at rest
Payment credentials, mail credentials and confidential client secrets are stored AES-256-GCM with an authentication tag. The service refuses to start with a default or empty key instead of running insecurely.
Deactivation is enforced at every mint
Account status is re-checked when a code is issued and again before a code is redeemed, and the stale session is destroyed. Disabling a person takes away their applications, not just their password prompt.
Authorization fails closed
When sysidm is unreachable an application serves its read-only mirror and grants nothing. No role is ever inferred from local state that outlived a revocation.
Cross-site request forgery, every state change
Double-submit validation on each form that changes anything. The session cookie is httpOnly with
SameSite=Lax.
The front door is rate-limited
Login, registration and password recovery each carry their own per-address budget, and registration requires a single-use arithmetic captcha with a five-minute lifetime.
Recovery resists enumeration
The forgot-password form answers identically whether or not the address exists. On the machine API an unknown subject resolves to no roles, not an error — so there is no oracle to probe.
Webhooks verified against raw bytes
Signatures are checked over the exact bytes received, before parsing. Events are idempotent by unique id, and an event is marked handled only once it was handled — so a failure genuinely retries.
A mail API that cannot become a relay
One recipient per call. The address must hold an account, or an invitation from that application, or sit in an explicitly allowlisted domain. Per-application hourly and daily quotas are counted from the delivery log itself, and CR/LF in a subject line is rejected outright.
Escaped by construction
Every server-rendered page escapes & < > " ' through one shared function, and inline
JSON is angle-bracket escaped so a payload cannot close a script context.
Isolation, tenancy and money
Every object in the schema hangs off the same axis: which application, which account, which
person. There is no global pool of users with a visibility filter bolted on top, because a filter is
one forgotten WHERE clause away from a breach.
Roles are deliberately few and deliberately ordered. A request is answered by the highest authority that applies, and the platform’s own administrators are never visible to your applications — there is no flag for an app to accidentally trust.
- The owner counts as a seat. Never an exemption, so the arithmetic always closes.
- Ten read-only guests per licensed seat, for the people who need to look and not edit.
- Pending invitations reserve capacity. Two owners cannot invite past the cap at the same time, because the reservation is made when the invitation is sent.
- Last-owner protection. An owner may only be removed by an owner, only while two remain, and the last owner cannot remove themselves.
- Money is integer cents, timestamps are timezone-aware. No floating-point rounding in a billing path.
Platform administrator
Whoever operates sysidm. Deliberately never surfaced to applications — the machine API strips the flag, so no product can grant itself privileges.
Application administrator
Your product’s own admin, delegated per application. Designate and revoke without a code change and without touching the platform.
Owner · collaborator
Licensed seats in an account. Owners manage members, billing and seats; collaborators work in the account.
Guest
Read-only, consumes no seat. This is how you give an auditor, a client or a contractor a window into one account without hiring them.
Your prices, your merchant account, our lifecycle
sysidm integrates with all industry-standard payment providers. Each application carries its own provider credentials per environment — sandbox and live — its own product and plan catalogue, and its own webhook endpoint. Settlement lands with you. Nothing of yours passes through us.
The catalogue is data, not code
Single or team. Monthly or annual. Per-seat or flat. Each plan carries its own feature and entitlement payloads, so a price change is an edit rather than a release.
Upgrades are prorated and charged now
The target per-cycle total minus the unused fraction of the period already paid, billed as a one-time order, then the recurring subscription is revised to match. The quote and the charge are reconciled against the provider’s actual figures, not our arithmetic.
Downgrades wait for the renewal
Seat reductions and plan changes you are entitled to make yourself, applied at the next renewal. No clawback arguments and no mid-cycle surprise on the invoice.
A failed payment means read-only, immediately
The account and its subscription suspend together and the owners are told. When the renewal succeeds it restores automatically — unless the reserved seats no longer fit the renewed count, in which case the mail says precisely what to fix.
Terminal states are protected
A late, duplicated or replayed event cannot resurrect a cancelled or expired subscription. Idempotency is enforced by unique event id, not by hoping the webhook arrives once.
Reminders that do not nag
Twenty-one, fourteen, seven, three, two and one day before a deadline, idempotent per day. Customers on healthy auto-renewal are spared the “renew soon” mail because the provider already tells them — but everyone gets the expiry notice.
Trials with real rules
Sixty days by default, overridable per application. A trial can switch between single and team for free, and switching does not extend its end date. Administrators can extend a trial; customers cannot extend their own.
Plan prices verified, not assumed
Provisioning reads every plan back from the provider and asserts its price equals the catalogue in sysidm. A drift between the two is a failed deploy, not a customer dispute.
Safe to run more than one instance
The daily settlement and sync job takes a database advisory lock, so scaling the application tier out cannot cause the same renewal to be processed twice.
The API your application actually calls
Everything a product needs to answer “may this person do this, and how many seats do they have” — without the product ever looking at a password.
# who is this person, and what may they do? GET /v1/me id, email, handle, display name GET /v1/roles?app=… is_app_admin + role per account GET /v1/entitlements?app=&tenant= standing, seats, guests, effective role GET /v1/team?app=&tenant= roster + pending invitations + caps GET /v1/invitations?app=… this person’s open invitations # the same answers for someone who is not signed in POST /v1/roles subject in the body, never the URL POST /v1/entitlements # send as your own sender identity, under policy POST /v1/send-mail # what does this key actually permit? GET /v1/credential scopes, label, created, last used
Design decisions worth noticing
- Scopes, not roles, on credentials.
roles:read,entitlements:read,team:read,mail:send. Only the hash of a credential is stored and the plaintext is shown once. - Revocation is immediate because every call checks it. A retired key stops working on the next request, not at session expiry.
- A subject in a URL lands in access logs,
Refererheaders and caches. A subject in a body lands in none. That is why the “ask about an absent person” endpoints are POST. - The two caller kinds are not interchangeable. A user token may only ask about its own
subject. An application credential may ask about anyone — but only inside its own application. A
credential that fails verification is a hard
401, never a fall-through to the other path. - Sending as
app@…is an application capability, so the mail endpoint refuses user tokens outright. - The cache is never the authority. Redis sits in front of entitlements for speed only; every miss, timeout or failure goes to the database.
Built for the security questionnaire
The questions your reviewer will ask, answered the way we would want them answered if we were on the other side of it.
| Question | Answer |
|---|---|
| Where are passwords stored? | In sysidm, hashed with argon2id. Your application stores none, so your application has nothing to breach and no reset flow to get wrong. |
| What happens when someone leaves the company? | One deactivation. It is re-checked at every token mint across every application and the live session is destroyed, so there is no per-product offboarding checklist to forget a line on. |
| Can a stolen database be replayed as access? | No. Codes, refresh tokens, sessions, email tokens, invitations and API credentials are all stored as SHA-256 digests. |
| How is tenant data separated? | Every row is scoped by application and account. A cross-account lookup is refused by the request path rather than filtered after the fact. |
| Blast radius of a leaked application secret? | Your browser and mobile clients have no secret to leak — they are PKCE public clients. Confidential secrets that do exist are AES-256-GCM at rest and scoped to one application. |
| Is there a change history? | Subscription changes are recorded with the actor and the payload, and every outbound message is recorded with its delivery result, so “we never got that email” is answerable. |
| How is it backed up and tested? | Nightly dumps gated by a restore test and mirrored off-box. The encryption key and signing keys are copied alongside in dated pairs, because a dump without its matching keys restores as unreadable ciphertext. |
| How is it verified? | 134 automated tests across pricing, entitlements, seat caps, the OIDC flow, trials, the machine API and the billing UI — plus a written cross-repository integration contract requiring each application to ship an end-to-end test that fails loudly when sysidm is unreachable. |
| What is exposed to the network? | The application port, behind the reverse proxy. The database and cache are never published outside the private network. |
What we are not going to claim
Because the list above is only worth reading if this one is too.
- Sign-in is not yet behind an authenticator app. Recovery is two-factor — the emailed link plus your security questions — but TOTP and WebAuthn are not in this release.
- sysidm is an originating identity provider, not a broker. It does not front SAML or an existing corporate directory today; it issues the identities your products consume.
- Licensing is per account, not per individual user. Seats inside an account, not standalone user licences.
- No tax or VAT engine, and no invoice PDF generation. Payment records and a sequential reference, yes; a jurisdiction-aware invoicing system, no.
One platform, two very different buyers
The same code, the same release, the same standards. What changes is how much you hand over.
Small team, first product
You should not be writing password-reset code
- Point one OIDC client at your issuer and delete your auth module
- Take trials, invitations, seats and dunning for free on day one
- Keep your own merchant account and set your own prices
- No card data in your stack, so no audit surface you did not need
- One login is a feature your customers notice on the first visit
Portfolio, enterprise
One identity across everything you operate
- Central deactivation across every product at once
- Per-product administrators you delegate without a code change
- Per-product catalogues, mail identities and payment credentials
- Standards throughout, so no team is trapped by a dependency
- Your own instance, your issuer URL, your keys, your retention
- A contract and a test suite that survive a change of staff
How a deployment goes
It starts with a conversation rather than a card form, because the honest answer depends on what you are running.
Discovery
We look at your products, your existing user base and your billing rules, and tell you what fits and what does not. Some of it you should keep doing yourself.
Deployment
Your own instance: your issuer URL, your signing keys, your payment credentials in sandbox first, your brand on the forms.
Migration
Your users come across with their password hashes intact — bcrypt and scrypt verify in place, so nobody is emailed a reset link on the day you launch.
First integration
We wire your first application alongside your team and leave a written authority contract plus an end-to-end test that fails loudly if sysidm is unreachable.
Go-live
Live credentials, webhooks registered and read back, plan prices verified against your catalogue rather than assumed. After that, a new product is a registration and a redirect URI.
Questions, answered honestly
Do our applications have to use your login pages?
Can you migrate an existing user table?
Do we keep our own merchant account?
Which payment providers do you support?
What happens if sysidm becomes unavailable?
Is this multi-tenant SaaS that we share with other companies?
Can it sit behind our existing corporate identity provider?
How much of our billing logic has to change?
Who owns the data?
Is there a free trial we can just sign up for?
Put every product you ship on identity you can answer for
Tell us what you run. We will show you the deployment, the migration path for the users you already have, and the integration contract your engineers would actually receive — before anyone commits to anything.
OpenID Connect · OAuth 2.0 + PKCE · RS256 · argon2id · AES-256-GCM · your merchant account