Custom domains
Serve a tenant's sign-in pages and protocol endpoints on a hostname you own — the two DNS records, verification, what a verified domain serves, the passkey sign-in host, and the limits.
Every tenant lives at https://<slug>.sensecrypt.com. A custom domain adds a hostname you own, such as auth.example.com, on which the same tenant serves its end-user and protocol surface. Users see your domain on the sign-in page and in the issuer of the tokens your applications receive. The slug host keeps working alongside it.
What a verified domain serves
Once verified, a custom domain resolves to the tenant and serves:
- OIDC: discovery, JWKS, authorize, PAR, token, UserInfo, introspection, revocation, logout and CIBA.
- SAML metadata and SSO, and SCIM.
- The server-rendered sign-in ceremony pages, the QR universal-link redirector, and the app-link well-known documents.
It does not serve the admin console or the Management API. Those stay on the canonical hosts, and a request for them on a custom domain gets a plain 404 page. One consequence is useful: an M2M token minted at the custom domain's token endpoint still works against the Management API on the canonical host, because the token is mapped back to its tenant by its own issuer.
The issuer mirrors the host, and a relying party pins one host. Discovery on auth.example.com advertises https://auth.example.com as the issuer, and tokens minted there carry it. A sign-in started on one host completes only on that host: redeeming an authorization code, polling a CIBA request or refreshing on a different host of the same tenant is refused with invalid_grant. Configure each application against one issuer and read its endpoints from that host's discovery document.
Add a domain
Open Domains, under Security in the tenant's console. Creating needs the domains:create capability; verifying needs domains:verify, deleting domains:delete, and reading domains:read.
- Enter the hostname. Use a subdomain such as
auth.example.com. A root domain usually cannot take the CNAME the next step needs. A hostname the deployment already serves is refused. - Publish two DNS records. The console shows both, with their exact names and values:
- a TXT record that proves you own the domain, and
- a CNAME record that routes the domain's traffic to SenseCrypt.
- Verify. The console checks both records with a live DNS lookup. The domain becomes verified only when both pass; until then it is pending and serves nothing. If one record is still missing, the result names it. DNS propagation can take a while, so re-check rather than re-create.
- Re-check also probes the live domain end to end once it is verified. That connectivity result is a diagnostic. It never changes the domain's status.
The records are re-checked in the background on a timer. If either record authoritatively disappears, the domain drops back to pending and stops serving until the record is restored. A transient DNS failure does not demote a domain.
Once verified, the console lists the domain's endpoints so you can paste them into a relying party, SAML service provider or SCIM connector that should use your domain:
{
"issuer": "https://auth.example.com",
"oidc_discovery": "https://auth.example.com/.well-known/openid-configuration",
"jwks": "https://auth.example.com/.well-known/jwks.json",
"saml_metadata": "https://auth.example.com/v1/idp/saml/metadata",
"scim_base": "https://auth.example.com/v1/idp/scim/v2"
}There is no edit. To change a domain, delete it and add the new one.
Limits and billing
Three verified custom domains are included per account, counted across all of the account's tenants. Each additional verified domain is billed monthly; a pending domain is not. See Billing. Beyond the included three, counting pending domains too since each is one DNS record from billing, creating another needs a card on file, otherwise the request is refused with 402 and error: "resource_requires_payment". A deployment also caps the number of domains an account may hold; at the cap, creation is refused with 409 and error: "domain_limit".
Only one tenant anywhere can hold a verified claim on a hostname. Creating a pending claim on a hostname another tenant has verified succeeds; the conflict surfaces at verification as domain_taken, so a domain list never reveals what other accounts hold.
The sign-in host for passkeys
Everything above mirrors whichever host a request arrives on. The one thing that cannot is the passkey method. A passkey is bound to exactly one WebAuthn Relying Party ID, so each tenant designates one sign-in host, in the Sign-in host card at the top of the Domains page (signin_host:read and signin_host:update).
| Tenant shape | Sign-in host |
|---|---|
| No custom domain | Leave the default: the slug host. |
| One custom domain | That domain. |
Several subdomains of one registrable domain, such as hr.example.com and finance.example.com | The registrable domain, example.com. One passkey then covers all of them. The console offers this widening explicitly; it is never applied by default, because a wider ID lets the passkey be offered on every origin beneath it. |
| Domains across different registrable domains | Unsupported. One passkey cannot span two registrable domains; the console refuses it with signin_host_spans_registrable_domains and suggests separate tenants. |
Changing the sign-in host is a re-enrolment event. Every passkey in the tenant stops matching, so while live passkeys exist the change requires an explicit confirmation (confirm_reenrolment) and retires all of them in the same step. Nobody is locked out: each user re-enrols with a face check at their next sign-in. For the same reason, deleting the domain that is the sign-in host is refused with 409 and error: "domain_is_signin_host". Change the sign-in host first, then delete the domain.
If the designated domain loses verification, the passkey method fails closed for the tenant until the domain is verified again. The console says so prominently.
Custom domains and your own mobile app
A tenant that runs its own Authenticator app issues sign-in links on an app-link host. By default that is the tenant's slug host. A verified custom domain can be chosen instead, when the app is built and verified for that domain.
Under the hood: the endpoints
| Method and path | Capability | Purpose |
|---|---|---|
GET /v1/admin/domains | domains:read | List the tenant's domains with status, the two records, and the endpoint URLs of verified ones. |
POST /v1/admin/domains | domains:create | Create a pending domain. Body: {"domain": "auth.example.com"}. Returns the TXT and CNAME records to publish. |
GET /v1/admin/domains/{domain_id} | domains:read | Read one domain. |
POST /v1/admin/domains/{domain_id}/verify | domains:verify | Check both DNS records now. Returns the domain with a message naming what is still missing, or null once live. |
POST /v1/admin/domains/{domain_id}/connectivity | domains:verify | Probe the live domain end to end. Returns reachable, the HTTP status seen, and a hint. Never changes status. |
DELETE /v1/admin/domains/{domain_id} | domains:delete | Remove the domain. Refused while it is the sign-in host. |
GET / PUT /v1/admin/signin-host | signin_host:read / signin_host:update | Read the sign-in host in effect, its choices and the number of live passkeys; set it with {"rp_id": "…", "confirm_reenrolment": true}. |
Each domain carries status (pending or verified), txt_verified and cname_verified with their timestamps, the last connectivity result, and urls once verified. See the API reference under Admin domains and Admin sign-in host.
Related
- Multi-tenancy — how a host resolves to a tenant and its issuer.
- Login methods — why passkeys are bound to one host.
- Billing — what domains cost beyond the included three.
- Ship your own Authenticator app — the app-link host.
Managing tenants
Onboard in the admin console and manage tenants — the auto-created default tenant, creating, renaming, and deleting tenants, and getting from signup to your first registered application.
Customize claims and scopes
Control which profile claims SenseCrypt releases into ID tokens, access tokens, UserInfo, and SAML assertions — using typed attributes, scopes, and per-app scope bindings.