Skip to main content

The Verify factor suite end to end

Verify ships possession-of-secret MFA factors alongside OTP delivery: TOTP (authenticator apps), push (device public key + signed challenge), passkeys (WebAuthn / FIDO2), magic links, and backup codes. This guide walks the enroll → verify → revoke lifecycle for each one, so you can wire factor flows into your own application instead of assembling the endpoint tables yourself. The factor suite is API-only by design — it has no dashboard screen. Factors are enrolled, verified, and revoked from inside your application, where the end user is present to scan a QR code, run a WebAuthn ceremony, or approve a push prompt on their device. Drive the endpoints directly or through the SDK. (The Verify dashboard covers OTP send/check, profiles, and analytics; the 2FA control under Settings → Security protects your Orbit login and is separate from these end-user factors.) Factor endpoints carry no carrier delivery and no wallet deduct — they validate proof-of-possession against a server-side secret or device key pair. Every factor endpoint requires an authenticated request and is isolated to your organization: a factor created by one tenant is never visible to another.

1. Delivery channels vs factor channels

POST /verify/send accepts both a delivery channel and a factor channel value, but they mean different things. The first group delivers an OTP over a carrier; the second group proves possession without carrying an OTP. Delivery channelssms, whatsapp, email, voice, viber, telegram, rcs, flashcall: The send generates a code and hands it to the channel provider. Phone-shaped recipients for phone channels, email-shaped recipients for email. These are also the only channels that make sense in a fallback chain. Factor channelstotp, push, passkey ceremonies, backup_code, plus the device-possession checks sna and silent, and magic_link: There is no OTP in transit and nothing to be delivered along a fallback chain. sna (Silent Network Authentication) and silent are network-level possession lookups rather than enrollable factors; totp, push, and backup_code are enrollable factors with their own lifecycle endpoints, covered below. magic_link replaces the typed code with a single-use email link (see §3.5).

2. Why /verify/send rejects a factor with 422

Sending channel: "totp", "push", or "backup_code" to POST /verify/send does not issue an OTP. The send endpoint short-circuits a factor-channel request with a typed 422 / 400 response that points you at the matching factor endpoint, because those channels are possession-of-secret factors with their own lifecycle — there is no carrier leg to send on, and no code to check with /verify/check afterward. To enroll or exercise a factor, call its dedicated endpoint directly (§3). To use a network-level possession check (sna, silent) or a magic link, those remain valid /verify/send channels — only the enrollable factors are rejected.

3. Enroll, verify, and revoke — per factor

Each subsection is the full lifecycle for one factor. The raw endpoint tables for every factor live in the Verify overview; the steps below put them in operator order.

3.1 TOTP (authenticator app)

RFC 6238 factors compatible with Google Authenticator, Authy, 1Password, and similar apps. Codes are 6 digits on a 30-second step with ±1 step of clock-skew tolerance.
  1. EnrollPOST /api/v1/verify/factors/totp. The response returns the otpauth:// URI for QR rendering plus the base32 secret once — subsequent reads return metadata only. Ten single-use recovery codes are minted on creation and surfaced once; only their SHA-256 hashes are persisted.
  2. Render the secret — show the OTPauth URI as a QR code (or the base32 secret for manual entry) while the user is present.
  3. VerifyPOST /api/v1/verify/factors/totp/{id}/verify with the current 6-digit code from the user’s app. A match approves the factor challenge.
  4. Recovery codesPOST /api/v1/verify/factors/totp/{id}/recovery-codes/regenerate invalidates the previous set and re-mints 10 codes.
  5. RevokeDELETE /api/v1/verify/factors/totp/{id} removes the factor.
TOTP writes (create, verify, delete, recovery-code regeneration) require the verify:write scope; dashboard session callers also need an owner, admin, or developer role. Reads (list) are gated by authentication and per-tenant rate limiting only.

3.2 Push (device public key)

Phishing-resistant push MFA via a device public key and a signed-challenge flow (mirroring Twilio Verify’s factor.type = "push").
  1. Register the devicePOST /api/v1/verify/push/factors with the device’s public key.
  2. Issue a challengePOST /api/v1/verify/push/factors/{factorId}/challenges creates a new challenge for the paired device.
  3. Verify the signed nonce — the device signs the challenge nonce; submit it with POST /api/v1/verify/push/challenges/{challengeId}/verify.
  4. Revoke the devicePOST /api/v1/verify/push/factors/{factorId}/revoke un-pairs the device.
Every push endpoint is gated by authentication and per-tenant rate limiting only — no verify:write scope is required, so restrict which API keys reach these flows accordingly.

3.3 Passkeys (WebAuthn / FIDO2)

Phishing-resistant passkey factors. The registration and authentication ceremonies follow the WebAuthn spec; attestation and assertion verification (CBOR / COSE / signature) runs server-side.
  1. Start registrationPOST /api/v1/verify/passkey/registration/options issues a WebAuthn registration ceremony (challenge, relying-party parameters).
  2. Complete enrollment — run the browser’s navigator.credentials.create() ceremony, then POST /api/v1/verify/passkey/registration/verify with the attestation response. A verified attestation creates the factor.
  3. Start authenticationPOST /api/v1/verify/passkey/authentication/options issues an authentication ceremony for an enrolled passkey.
  4. Verify the assertionPOST /api/v1/verify/passkey/authentication/verify validates the assertion and bumps the sign counter.
  5. RevokePOST /api/v1/verify/passkey/factors/{factorId}/revoke removes the passkey.
Like push, every passkey endpoint is gated by authentication and per-tenant rate limiting only — no extra scope.

3.4 Backup codes

Single-use recovery codes for users who lose their device and SMS access. These are the natural recovery leg for any of the factors above.
  1. MintPOST /api/v1/verify/factors/backup-codes returns 10 plaintext codes once; the server stores SHA-256 hashes only.
  2. VerifyPOST /api/v1/verify/factors/backup-codes/{id}/verify consumes one code. Each code can succeed at most once, and the response returns remaining_count so your UI can prompt regeneration before the user runs out.
  3. DeleteDELETE /api/v1/verify/factors/backup-codes/{id} removes the factor (idempotent).
Backup-code writes require the verify:write scope (same posture as TOTP); reads are authentication-gated only. A magic link replaces the typed code with an email-delivered, single-use HTTPS link (Stytch / WorkOS / Auth0 parity).
  1. SendPOST /api/v1/verify/send with channel: "magic_link" and an email to. Orbit emails a signed link; the <Note> in the Verify overview covers the same endpoint lookup pattern the /check resolver uses.
  2. Consume — the user’s click hits the public, unauthenticated endpoint GET /public/verify/magic-link/consume?token={token}, which approves the underlying verification on success and rejects tampered, expired, or already-used tokens.
The token is a constant-time HMAC-signed payload. There is nothing to enroll or revoke — the link is single-use per verification, so treat it as a send/check variant rather than a persistent factor.

4. Securing factor endpoints

The scope split matters for how you issue API keys:
  • TOTP and backup-code writes (create, verify, delete, recovery-code regeneration) require the verify:write scope. Dashboard session callers must also hold an owner, admin, or developer role.
  • TOTP and backup-code reads, and every push and passkey endpoint, are gated by authentication and per-tenant rate limiting only. They do not require an additional verify:write or verify:read scope, so any authenticated key scoped to the tenant can call them.
Issue dedicated keys for factor flows and restrict them to the callers you actually want enrolling and revoking factors. Tenant isolation applies throughout — a factor created by one tenant is never visible to another.

5. Data model and recovery

  • Secrets surface once. TOTP returns the base32 secret and otpauth:// URI on create; backup codes return the plaintext set on create. Persist them to the enrolling user’s device or your recovery flow at that moment — later reads return metadata only.
  • Hashes at rest. Backup codes (and TOTP recovery codes) are stored as SHA-256 hashes; verify compares hashes, never plaintext.
  • Recovery codes as the fallback leg. Pair any primary factor (TOTP, push, passkey) with a backup-code factor so a user who loses a device can still sign in. Monitor remaining_count on backup-code verify responses and prompt regeneration before exhaustion.
  • Revoke-a-device runbook. To cut off a lost or compromised device: revoke the push factor (/verify/push/factors/{id}/revoke) or passkey (/verify/passkey/factors/{id}/revoke), delete the TOTP factor, and — if the enrollment used backup codes — delete that factor or regenerate the TOTP recovery codes. Revocation endpoints are idempotent-safe to retry where noted (backup-code delete).

6. When to pick a factor over an OTP channel

OTP delivery channels and factors answer different threats:
  • Phishing-resistant possession — passkeys and the network-level sna check are not relayable through a phishing proxy in the way a typed OTP is. Prefer them for account-recovery and step-up flows where an attacker who can relay an OTP would otherwise win.
  • Device-bound approval — push proves a specific paired device answered, not just that someone read a code.
  • Nothing to relay — TOTP and backup codes keep the secret on both ends with no carrier leg, so no delivery provider, no SIM-swap exposure in transit, and no wallet deduct.
  • OTP still fits when the recipient has no enrolled device and you need to reach them over SMS, WhatsApp, email, or voice — the delivery channels and fallback chains cover that reach.
Pick the factor where a device or passkey exists; fall back to OTP delivery where it does not.

See also