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 channels — sms, 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 channels — totp, 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.- Enroll —
POST /api/v1/verify/factors/totp. The response returns theotpauth://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. - Render the secret — show the OTPauth URI as a QR code (or the base32 secret for manual entry) while the user is present.
- Verify —
POST /api/v1/verify/factors/totp/{id}/verifywith the current 6-digit code from the user’s app. A match approves the factor challenge. - Recovery codes —
POST /api/v1/verify/factors/totp/{id}/recovery-codes/regenerateinvalidates the previous set and re-mints 10 codes. - Revoke —
DELETE /api/v1/verify/factors/totp/{id}removes the factor.
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’sfactor.type = "push").
- Register the device —
POST /api/v1/verify/push/factorswith the device’s public key. - Issue a challenge —
POST /api/v1/verify/push/factors/{factorId}/challengescreates a new challenge for the paired device. - Verify the signed nonce — the device signs the challenge nonce; submit it with
POST /api/v1/verify/push/challenges/{challengeId}/verify. - Revoke the device —
POST /api/v1/verify/push/factors/{factorId}/revokeun-pairs the device.
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.- Start registration —
POST /api/v1/verify/passkey/registration/optionsissues a WebAuthn registration ceremony (challenge, relying-party parameters). - Complete enrollment — run the browser’s
navigator.credentials.create()ceremony, thenPOST /api/v1/verify/passkey/registration/verifywith the attestation response. A verified attestation creates the factor. - Start authentication —
POST /api/v1/verify/passkey/authentication/optionsissues an authentication ceremony for an enrolled passkey. - Verify the assertion —
POST /api/v1/verify/passkey/authentication/verifyvalidates the assertion and bumps the sign counter. - Revoke —
POST /api/v1/verify/passkey/factors/{factorId}/revokeremoves the passkey.
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.- Mint —
POST /api/v1/verify/factors/backup-codesreturns 10 plaintext codes once; the server stores SHA-256 hashes only. - Verify —
POST /api/v1/verify/factors/backup-codes/{id}/verifyconsumes one code. Each code can succeed at most once, and the response returnsremaining_countso your UI can prompt regeneration before the user runs out. - Delete —
DELETE /api/v1/verify/factors/backup-codes/{id}removes the factor (idempotent).
verify:write scope (same posture as TOTP); reads are authentication-gated only.
3.5 Magic link
A magic link replaces the typed code with an email-delivered, single-use HTTPS link (Stytch / WorkOS / Auth0 parity).- Send —
POST /api/v1/verify/sendwithchannel: "magic_link"and an emailto. Orbit emails a signed link; the<Note>in the Verify overview covers the same endpoint lookup pattern the/checkresolver uses. - 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.
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:writescope. 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:writeorverify:readscope, so any authenticated key scoped to the tenant can call them.
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_counton 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
snacheck 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.
See also
- Verify overview — channel catalog, send/check reference, and the full MFA factor endpoint tables.
- Verify profiles: fallback chains and advanced factors — pair factors with OTP profiles and ordered fallback chains.
- Verify integration without our SDK — plain-HTTP send/check with the full error matrix.
- Verify API reference — every endpoint, including profile CRUD.
- Push factor API reference and Passkey API reference — full request/response shapes for those suites.