IBExWalletAPI
Admin

Domains β€” DNS verification & create

Register a tenant domain in IBEx: request a DNS TXT challenge (no auth), publish the record, then call PUT /domains with a user JWT to verify DNS and obtain rpId, apiKey, and primaryRpId.

These endpoints are not under /v1.2 β€” they use the root path /domains/... on the same API host (e.g. https://passkeys-prat1.ibex.fi).

What you are proving

IBEx stores each partner app as a Domain row keyed by rpId (typically your WebAuthn / app hostname, e.g. app.example.com).
Before the row is trusted for production, you must prove control of DNS for that hostname using a one-time TXT token.


Step 1 β€” GET /domains/dns-challenge (public)

Auth: none β€” safe to call from a script or browser without a JWT.

Query

ParameterRequiredDescription
domainYesFQDN to verify (e.g. app.example.com). Lowercased server-side.

Example

curl -sS "https://passkeys-prat1.ibex.fi/domains/dns-challenge?domain=app.example.com"

Response (200) β€” create this DNS TXT record at your DNS provider:

FieldMeaning
recordHostname for the TXT record (e.g. _ibex-verify.app.example.com)
valueFull TXT value to set (starts with ibex-verify= + random token)
typeAlways TXT
ttlChallenge lifetime in seconds (about 1 hour); token is stored in Redis

The server stores the expected token until it expires. If you skip this step, PUT /domains will fail with β€œcall GET /domains/dns-challenge first”.


Step 2 β€” Publish the TXT record

  1. In your DNS zone, add a TXT record:
    • Name / host: as returned in record (often the subdomain _ibex-verify.<your-domain>).
    • Value: exactly the string in value (including the ibex-verify= prefix).
  2. Wait for DNS propagation (minutes to hours depending on TTL and provider).
  3. Optionally verify with dig TXT <record> or an online DNS checker before calling step 3.

Step 3 β€” PUT /domains (JWT)

Auth: Authorization: Bearer <access_token> for a user who should become admin of the new domain (the API connects that user to the Domain after creation).

Headers

HeaderValue
AuthorizationBearer <access_token>
Origin or rpIdMust allow the API to resolve an existing tenant context where applicable (same rules as other authenticated routes).

Body (JSON)

FieldRequiredDescription
domainYesSame FQDN you used in step 1 (after DNS is visible to the API server).
primaryRpIdNoOmit to link the new domain to the default primary rpId configured on the server. Set to null for a standalone domain (no ROR link). Set to another existing standalone domain’s rpId for ROR (related origins).

Example (standalone)

curl -sS -X PUT "https://passkeys-prat1.ibex.fi/domains" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain":"app.example.com","primaryRpId":null}'

What the server does

  1. Loads the challenge for domain from Redis (must still be valid).
  2. Performs a DNS TXT lookup on _ibex-verify.<domain> and checks that one of the TXT strings exactly matches the expected ibex-verify=<token> value.
  3. Creates a new Domain (or upgrades an auto-created unverified domain) with dnsVerified = true, sets primaryRpId from the rules above, and attaches the authenticated user as domain admin.
  4. Returns:
FieldDescription
rpIdTenant identifier (same as domain when created fresh).
apiKeyTenant API key β€” store securely; used as x-api-key on API calls for that rpId.
primaryRpIdLinked primary for ROR, or null if standalone.

After creation

  • Use x-api-key: <apiKey> together with correct Origin / rpId resolution on versioned routes (/v1.2/...).
  • Optional: GET /system/health with x-api-key returns rpId and signerCount when the key is valid β€” handy to confirm the key without hitting a protected route (see internal system docs).

Common errors

SymptomLikely cause
No challenge / bad requestDid not call GET /domains/dns-challenge first, or challenge expired (~1 h).
DNS TXT not foundPropagation delay, wrong host, or record not created at public DNS.
Verification failedTXT value mismatch (extra quotes, spaces, or wrong token).
Domain already existsDomain row already verified β€” use admin flows to change settings instead.
JWT errorsMissing/expired token on PUT /domains only β€” step 1 stays public.

On this page