Skip to main content

Verify

Orbit Verify is a turnkey OTP (one-time password) verification service. Send verification codes via SMS, WhatsApp, Voice, or Email — Orbit handles code generation, delivery, expiration, and validation so you don’t have to.

Send a Verification Code

Response

Send in Bulk

Need to fan an OTP out to many recipients at once (login surges, password-reset campaigns)? POST /api/v1/verify/bulk accepts an array of up to 1000 recipients that share one channel and optional verify profile. Every recipient runs through the same per-recipient send pipeline as /verify/send, and the call returns one result row per recipient plus batch totals. The HTTP status reflects the batch outcome: 201 when every recipient was sent, 207 (Multi-Status) on partial success, and 400 when every recipient failed. See the Verify API reference → Bulk send for the full request/response shape.

Check a Verification Code

Response

Supported Channels

POST /verify/send accepts the channel values below. The first group delivers an OTP to the recipient; the second group are non-delivery / factor channels that don’t carry a code over a carrier and are handled by dedicated factor endpoints (see MFA Factors below).

Delivery channels

Telegram delivery configuration

The telegram channel delivers codes through Telegram’s Gateway API (sendVerificationMessage) — the verification gateway, which is separate from the Telegram Bot API that powers the Telegram messaging channel. To enable it, set these on the API service: Until DEVOTEL_TELEGRAM_GATEWAY_API_KEY is set, POST /verify/send with channel: "telegram" fails closed with 503 SERVICE_UNAVAILABLE.

Non-delivery / factor channels

Sending channel: "totp", "push", or "backup_code" to POST /verify/send does not issue an OTP — these are possession-of-secret factors with their own lifecycle. The send endpoint short-circuits (422 / 400) and steers you to the matching factor endpoint below.

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:write scope. 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:write or verify:read scope, 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 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.

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 an UPDATE ... 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 Verify factor.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. Email-delivered single-use HTTPS link (Stytch / WorkOS / Auth0 parity) instead of a typed code. Send with channel: "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
  • Channel fallback — automatic retry on a different channel if the first fails
  • Rate limiting — built-in protection against brute-force attacks
  • Expiration — configurable TTL (default 10 minutes / 600s, max 60 minutes / 3600s)
  • Attempt limits — configurable max verification attempts per code (1–10, default 3)
  • Locale support — localized message templates in 25 languages
  • Fraud detection — optional VoIP/high-risk blocking, configurable per tenant (off by default)

Configuration Options

These are the body parameters accepted by POST /api/v1/verify/send (sendVerificationSchema):
Code expiry is not a /send parameter. Direct sends expire after the default 600s (10 minutes). To change the TTL, set expirySeconds on a verify profile and pass its profile_id — there is no expiry field 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, /send returns a synchronous 403 SIM_SWAP_DETECTED with last_swap_date and block_window_hours in the response details. No verification row is created and no verification.failed event fires, so wire failure/step-up triggers to the 403 response rather than the webhook.