SaaS Tenant Onboarding
This guide walks through onboarding a new organisation (tenant) in SaaS mode: from creating the tenant record through domain verification, provisioning, and issuing the first credential.
This guide assumes:
- The issuer is running in SaaS mode (
SAAS_MODE=true) - You have a
PLATFORM_ADMINaccount on the issuer
Tenant lifecycle
A tenant in SaaS mode moves through the following states:
provisioning → active → (suspended) → active
| State | Description |
|---|---|
provisioning | Tenant record created; domain not yet verified; no signing key |
active | Domain verified; signing key provisioned; ready to issue credentials |
suspended | Temporarily deactivated; credential endpoints return an error |
Step 1 — Create the tenant
A PLATFORM_ADMIN creates a tenant record via the platform-admin API. Each tenant requires a unique slug that becomes part of their admin portal URL and credential namespace.
curl -X POST https://issuer.yourplatform.com/api/platform-admin/tenants \
-H "Content-Type: application/json" \
-H "Cookie: <platform-admin-session>" \
-d '{
"name": "Acme Corporation",
"slug": "acme-corp",
"issuerDID": "did:web:issuer.acme.com"
}'
Response (201 Created):
{
"id": "a1b2c3d4-...",
"name": "Acme Corporation",
"slug": "acme-corp",
"status": "provisioning",
"issuerDID": "did:web:issuer.acme.com",
"domainVerifiedAt": null,
"createdAt": "2026-03-10T12:00:00Z"
}
Save the id value — you will need it for subsequent API calls.
Step 2 — Verify the domain
Domain verification confirms that the organisation controls the domain their did:web points to. Call the verify-domain endpoint:
curl -X POST https://issuer.yourplatform.com/api/platform-admin/tenants/<tenant-id>/verify-domain \
-H "Cookie: <platform-admin-session>"
Response:
{
"id": "<tenant-id>",
"domainVerifiedAt": "2026-03-10T12:05:00Z",
...
}
In production, implement actual DNS or HTTPS-based domain verification before calling this endpoint. The endpoint itself records the
domainVerifiedAttimestamp.
Step 3 — Provision the tenant
Provisioning derives the tenant's unique signing key from the ISSUER_MASTER_KEY and activates their credential endpoints. Call the provision endpoint:
curl -X POST https://issuer.yourplatform.com/api/platform-admin/tenants/<tenant-id>/provision \
-H "Cookie: <platform-admin-session>"
Response:
{
"id": "<tenant-id>",
"status": "active",
"issuerDID": "did:web:issuer.acme.com",
...
}
The tenant's signing key is derived on-demand from HKDF(masterKey, tenantId). It is never stored in the database.
Step 4 — Register the tenant's first operator
The tenant's admin portal is at https://issuer.yourplatform.com/acme-corp/admin. The first operator registers as follows:
- Open the portal in a browser with the mwen.io extension installed.
- Click Sign in with mwen.io.
- The extension authenticates the user. Because no operator exists for this tenant yet, the user is redirected to
/register. - The user completes registration and is assigned the
OWNERrole on theacme-corptenant.
The tenant is now fully onboarded.
Step 5 — Issue the first credential
From the admin portal (/acme-corp/admin):
- Go to Credentials → New Offer.
- Select a schema from the credential schema templates.
- Enter the subject's details and click Create Offer.
- A QR code is generated. Share it with the credential subject.
- The subject scans the QR code with their mwen.io wallet and accepts the offer.
Suspending and reactivating a tenant
To temporarily deactivate a tenant (e.g. unpaid invoice, policy violation):
curl -X POST https://issuer.yourplatform.com/api/platform-admin/tenants/<tenant-id>/suspend \
-H "Cookie: <platform-admin-session>"
While suspended, all credential issuance endpoints for that tenant return an error. The tenant's previously-issued credentials remain valid in wallets (revocation is a separate action — see Revocation).
To reactivate:
curl -X POST https://issuer.yourplatform.com/api/platform-admin/tenants/<tenant-id>/activate \
-H "Cookie: <platform-admin-session>"
Listing tenants
curl https://issuer.yourplatform.com/api/platform-admin/tenants \
-H "Cookie: <platform-admin-session>"
Returns an array of all tenant records including status, slug, and domainVerifiedAt.
Data isolation
Each tenant's data is isolated by PostgreSQL Row-Level Security. One tenant cannot read or write another tenant's schemas, credential offers, or revocation lists — even if they share the same database instance.
The tenant context is passed via the X-Tenant-ID header on API calls. Page routes (the admin portal) have the tenant context injected by the request middleware based on the URL slug.
Related pages
- Deployment Modes — when to use SaaS mode
- Configuration —
SAAS_MODE,ISSUER_MASTER_KEY - API Key Management — creating API keys for tenant integrations