Verify
Orbit Verify is a turnkey OTP (one-time password) verification service. Send verification codes via SMS, WhatsApp, Voice, Email, Viber, Telegram, RCS, or a missed-call Flashcall — Orbit handles code generation, delivery, expiration, brute-force protection, and validation. To understand how a verify works in a real flow, start here; the behavior of endpoints and profiles follows.Formats and recipient requirements
POST /verify/send requires to and accepts channel, code_length, max_attempts, country, profile_id, channels, fallback_config, locale, custom_code, and device_token. Every recipient shape runs through the same resolver the /check endpoint uses.
Recipient shape by channel. The
to resolver rewrites national-format numbers to E.164 when a country hint is present; short codes are rejected up-front (they are sender IDs, not destinations).
Limits and validation windows
These bounds stop misconfiguration at send time and they are the same ones the profile service enforces. The/check endpoint mirrors the same limits on attempts and code length.
Why
expirySeconds is profile-only. The /verify/send body has no expiry field. Direct sends expire after the default 600s. To change the TTL, create a verify profile with expirySeconds and pass its profile_id on the send; the profile owns the TTL because the fallback engine and per-channel templates also live on the profile. When a profile is passed and the profile expires, the request is rejected — not silently hot-swapped. That is also why the profile update path re-validates expirySeconds instead of computing a synthetic default.Rate-limit + fraud surface
Verify runs the full send pipeline in service of the/verify/send and /verify/bulk calls. Every recipient flows through the same per-recipient checks.
- Rate limiting — per-recipient hourly cap (default 5/hour; tuned via a verify profile
rateLimitPerHour, org-levelverify_recipient_hourly_cap, or platform default).429 VERIFY_RECIPIENT_RATE_LIMITEDreceipts are per-recipient, not per-batch. - Velocity limits — the verify profile
fraud.velocityPerHour/fraud.velocityPerDay(values 1–100 and 1–1000) and the composite-risk verifier (lib/verify-fraud-guard.ts) that scores SIM-swap, port-event, and CAMARA possession. - Resend cooldown —
VERIFY_RESEND_COOLDOWNruns as aSET NXguard (30s per recipient) before the hourly INCR, so a resend burst does not consume an hourly slot. - VoIP line-type guard — a tenant opt-in
verify_line_type_policyallowsvoip_sms/voip_voiceto beoff(default),warn, orblock, so VoIP line-types are gated on SMS and voice only by tenant choice. The default is off.
Verify-specific fraud pre-flight
Before rate-limit/increment or billing deduct,/verify/send runs the fraud gate. SIM-swap pre-flight returns a synchronous 403 SIM_SWAP_DETECTED with last_swap_date and block_window_hours in details. No verification row is created and no verification.failed webhook fires — wire step-up (login attempts) to the 403 response, not to the webhook.
The endpoint rate-limits are separate (verify-send = 20/min, verify-bulk = 2/min, verify-check = 60/min, verify-fraud-score = 20/min, verify-fraud-gate = 20/min).
Webhook semantics
Subsequent/check attempts land on verification.checked (any attempt, success or not) so anti-fraud scoring can be driven from the stream. The one-time terminal approvals are the CAS-guarded operations:
verification.approved— a/checkmatched the code; fires exactly once per verification via the compare-and-set (CAS) transition gateverification.failed—max_attemptswere exhausted or the verification moved to a terminal unsuccessful stateverification.sent— the code was handed to the provider successfully
totp.create, backup_code.verify, push.factor.verify, passkey.challenge.verify) — see the API reference for the secure factor-event catalog.
Channel capability at a glance
The first group delivers an OTP over a carrier; the second group are non-delivery / factor channels that prove possession without carrying an OTP.Delivery channels
Factor channels
Sending
channel: "totp", "push", or "backup_code" to POST /verify/send does not
issue an OTP — the send endpoint short-circuits with a typed 422 / 400 to the matching factor endpoint below. Those channels are possession-of-secret factors with their own lifecycle.Send a Verification Code
Response
Fallback chain + fallback config
channels[] index 0 is the primary; the rest are tried in order when the first times out or fails. fallback_config activates the async fallback engine (scheduler-verify-fallback). With no fallback_config, the chain runs synchronously inside the same /verify request.
Bulk send body
POST /verify/bulk fans out up-to-1000 recipients per call. The endpoint rate-limits bulk to 2/min (vs 20/min for /verify/send) because one call can mint up to 1000 OTPs — operators can bump via per-org override.
201 when every recipient sent, 207 (Multi-Status) on partial success, and 400 when every recipient failed for a caller reason, or 503 when every recipient failed because of a genuine upstream delivery outage (retryable, matching verify/send’s per-recipient contract).
Check a Verification Code
Response
/check call consumes one attempt. max_attempts exhausted (default 3) terminates the verification; verification.approved only lands on a successful match.
MFA Factors
Beyond OTP send/check, Verify ships phishing-resistant and possession-of-secret MFA factor surfaces for end-tenant users. These factors 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. The scope requirement depends on the factor type:- TOTP and backup-code write operations (create, verify, delete, and recovery-code
regeneration) require the
verify:writescope. Dashboard (session) callers must also hold an owner, admin, or developer role. - TOTP and backup-code list (read) endpoints, along with every Verify 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 — restrict which keys reach these flows accordingly.
The factor suite is API-only by design — it has no dashboard screen. TOTP, backup codes,
passkeys (WebAuthn / FIDO2), and push are enrolled, verified, and revoked from inside your own
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 them straight from the endpoints below or 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.)
TOTP (authenticator app)
RFC 6238 authenticator-app factors (Google Authenticator, Authy, 1Password, etc.). Creating a factor 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.
Backup codes
Single-use, hashed-at-rest recovery codes for users who have lost their device + SMS access. Creating a factor mints 10 plaintext codes returned once; the server stores SHA-256 hashes only. Each code can succeed at most once (enforced by anUPDATE ... WHERE consumed_at IS NULL
gate), and verify responses return remaining_count so your UI can prompt regeneration.
Verify Push
Phishing-resistant MFA via a device public-key + signed-challenge flow (mirrors Twilio Verifyfactor.type="push"). Register a device public key, issue a challenge, then verify the signed
nonce returned by the device.
Passkeys (WebAuthn / FIDO2)
Phishing-resistant passkey factors. The registration and authentication ceremonies follow the WebAuthn spec; attestation/assertion verification (CBOR / COSE / signature) runs server-side.Magic Link
Email-delivered single-use HTTPS link (Stytch / WorkOS / Auth0 parity) instead of a typed code. Send withchannel: "magic_link" and an email to; Orbit emails a signed link. The user’s
click is consumed by the public, unauthenticated endpoint:
The token is a constant-time HMAC-signed payload; the consume endpoint approves the underlying
verification on success and rejects tampered, expired, or already-used tokens.
Features
- Auto-generated codes — secure random codes (4–8 digits)
- Multi-channel delivery — SMS, WhatsApp, Voice, Email, Viber, Telegram, RCS, Flashcall, plus
silentandsnafor device-fraud-sterile checks - Channel fallback — automatic retry on a different channel if the first times out or fails
- Rate limiting — built-in protection against brute-force attacks
- Expiration — configurable TTL via profiles (default 10 minutes / 600s, max 3600s)
- Attempt limits — configurable max verification attempts per code (1–10, default 3)
- Locale support — localized message templates in 25 languages
- Fraud detection — SIM-swap pre-flight, VoIP-type gating (tenant opt-in), velocity thresholds, composite risk, and offline
fraud-scorescoring
Configuration options (send body)
These are the body parameters accepted byPOST /api/v1/verify/send (sendVerificationSchema).
The profile object mirrors these fields to make them tenant-tunable (codeLength, expirySeconds,
rateLimitPerHour, templates, fallbackConfig).
Code expiry is not a/sendparameter. Direct sends expire after the default 600s (10 minutes). To change the TTL, setexpirySecondson a verify profile and pass itsprofile_id— there is noexpiryfield on the send body.
Webhook Events
A SIM-swap pre-flight block does not emit a webhook. When the pre-flight check rejects a send,/sendreturns a synchronous403 SIM_SWAP_DETECTEDwithlast_swap_dateandblock_window_hoursin the responsedetails. No verification row is created and noverification.failedevent fires, so wire failure/step-up triggers to the403response rather than the webhook.
Next steps
- Verify profiles: fallback chains and advanced factors — configure an ordered SMS → WhatsApp → voice chain, code length/TTL, per-channel templates, and pair the profile with push, TOTP, passkey, or voice-biometrics factors.
- Verify integration without our SDK — the plain-HTTP send/check flow with the full error matrix and webhook walkthrough.
- Troubleshooting: verify OTP — resend cooldowns, recipient caps, VoIP gating, and failover behavior you hit in production.
- Verify API reference — every endpoint, including profile CRUD and the factor suites.
- Verify audit log approvals — OTP changes that require an operator action.