Skip to main content

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_ADMIN account on the issuer

Tenant lifecycle

A tenant in SaaS mode moves through the following states:

provisioning → active → (suspended) → active
StateDescription
provisioningTenant record created; domain not yet verified; no signing key
activeDomain verified; signing key provisioned; ready to issue credentials
suspendedTemporarily 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 domainVerifiedAt timestamp.


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:

  1. Open the portal in a browser with the mwen.io extension installed.
  2. Click Sign in with mwen.io.
  3. The extension authenticates the user. Because no operator exists for this tenant yet, the user is redirected to /register.
  4. The user completes registration and is assigned the OWNER role on the acme-corp tenant.

The tenant is now fully onboarded.


Step 5 — Issue the first credential

From the admin portal (/acme-corp/admin):

  1. Go to Credentials → New Offer.
  2. Select a schema from the credential schema templates.
  3. Enter the subject's details and click Create Offer.
  4. A QR code is generated. Share it with the credential subject.
  5. 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.