No dashboard screen — passkeys are driven from your own application. A
WebAuthn passkey is registered and used where the end user is present: your app
requests options from these endpoints, runs the browser ceremony
(
navigator.credentials.create() / .get(), or the
@simplewebauthn/browser helper), and posts the
resulting attestation or assertion back to the matching verify endpoint.
The Orbit dashboard covers OTP send/check, profiles, and analytics — it does
not enroll or revoke passkeys on a user’s behalf. Retire a lost or rotated key
from your app with POST /verify/passkey/factors/{factorId}/revoke.Worked ceremony sequence
The five endpoints on this page are one lifecycle. The samples below run them in operator order — registration options → browser ceremony → registration verify → login options + login verify → revoke — with the real wire shapes at each step. Every JSON envelope below is a worked copy of the response, not the autogenerated"data": {} hull.
Hand the returned options object to the browser verbatim — never re-serialize
or mutate fields — and use
@simplewebauthn/browser
to keep the base64url handling honest.
1. Enroll — registration options
POST /api/v1/verify/passkey/registration/options returns the
PublicKeyCredentialCreationOptionsJSON and a challengeId you submit back
with the attestation.
2. Enroll — browser ceremony + attestation verify
In the browser, run the ceremony with the exactoptions payload from step 1,
then POST the serialized credential back within the TTL (default 300s, capped
at 600):
POST success returns 201 (not 200). backupEligible + backupState
echo the authenticator’s BE/BS flags: false on backupEligible means the
key lives on this one device — treat it accordingly.
3. Login — authentication options
POST /api/v1/verify/passkey/authentication/options for the identity returns
PublicKeyCredentialRequestOptionsJSON with that identity’s credentials hinted
in allowCredentials:
4. Login — browser ceremony + assertion verify
cloningSuspected: true. The verify
endpoints are rate-limited at 60 requests/minute per tenant.
5. Retire — revoke
POST /api/v1/verify/passkey/factors/{factorId}/revoke flips the factor to
revoked. Idempotent — re-calling an already-revoked factor returns the same
response:
409 / reason: "FACTOR_REVOKED".
Relying party and origin
WebAuthn binds every credential to a relying-party ID and an HTTPS origin, and the returnedoptions payload carries both. The default RP is
rpId: "verify.devotel.io" with origin https://verify.devotel.io.
- Production apps on their own domain must be issued options whose
rp.idis a registrable suffix of the page origin — browsers reject the ceremony otherwise, no network call ever fires.rpIdmust equal the origin’s host or be a parent domain of it (app.acme.commay useacme.com). - Local development: run on
http://localhost—localhostis exempt from the HTTPS rule and ceremonies work there. - Never mutate the
optionsobject between the API response and the browser call; the attestation/assertion is bound to the exact challenge, RP ID, and origin inside it.
Server-side without an SDK helper
The Node SDK ships no typed passkey helper — walk the same ceremony server-side (test harnesses, CLI, CI smoke) with the genericorbit.request
escape hatch:
Error surface
Failures arrive in the standard error envelope (meta.request_id included).
Plan for these on the ceremony paths:
A
422 on registration verify marks the challenge denied, so a malformed
retry does not silently burn the pending ceremony — re-issue fresh options
instead of looping on the same challengeId.
Every passkey endpoint is authentication- and rate-limit-gated only — no
extra scope — so issue dedicated keys for factor flows and restrict which
callers can reach them. A factor created by one tenant is never visible to
another.