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

curl -X POST https://api.orbit.devotel.io/api/v1/verify/send \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+14155552671",
    "channel": "sms",
    "code_length": 6
  }'

Response

{
  "data": {
    "verification_id": "vrf_3f1c0b2a8e4d4f7a9c2b1e6d5a4c3b2a",
    "status": "pending",
    "channel": "sms",
    "expires_at": "2026-03-08T00:10:00Z"
  }
}

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

curl -X POST https://api.orbit.devotel.io/api/v1/verify/check \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "verification_id": "vrf_3f1c0b2a8e4d4f7a9c2b1e6d5a4c3b2a",
    "code": "482901"
  }'

Response

{
  "data": {
    "verification_id": "vrf_3f1c0b2a8e4d4f7a9c2b1e6d5a4c3b2a",
    "status": "approved",
    "channel": "sms"
  }
}

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

ChannelDelivery Method
smsSMS text message with the code
whatsappWhatsApp message with the code
voiceAutomated voice call that reads the code aloud
emailEmail with the code and branded template
viberViber message with the code
telegramTelegram message with the code
flashcallMissed-call OTP — the trailing caller-ID digits are the code

Non-delivery / factor channels

ChannelBehaviour
silentNetwork-based silent verification (no code shown to the user)
snaSilent Network Authentication — possession-proof MNO verification via the CAMARA / GSMA Open Gateway broker. Pass a device-bound device_token; returns status: "verified" on a confirmed match (no OTP). Without a token it fails closed with 503 PROVIDER_NOT_WIRED
magic_linkEmail-delivered single-use HTTPS link instead of a typed code (see Magic Link)
totpAuthenticator-app (RFC 6238) factor — managed via /verify/factors/totp
pushPush-factor MFA — managed via /verify/push
backup_codeSingle-use recovery codes — managed via /verify/factors/backup-codes
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. All factor endpoints are tenant-scoped and require the verify:write scope for create/verify/ delete operations (reads use verify:read).

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.
MethodPathPurpose
GET/verify/factors/totpList TOTP factors
POST/verify/factors/totpCreate a factor (returns otpauth:// URI + secret once)
POST/verify/factors/totp/{id}/verifyValidate a 6-digit code (30s step, ±1 step skew)
DELETE/verify/factors/totp/{id}Delete a factor
POST/verify/factors/totp/{id}/recovery-codes/regenerateInvalidate and re-mint 10 recovery codes

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.
MethodPathPurpose
GET/verify/factors/backup-codesList backup-code factors
POST/verify/factors/backup-codesMint 10 single-use codes (returned once)
POST/verify/factors/backup-codes/{id}/verifyConsume one code (returns remaining_count)
DELETE/verify/factors/backup-codes/{id}Delete a factor (idempotent)

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.
MethodPathPurpose
POST/verify/push/factorsRegister a device public key
POST/verify/push/factors/{factorId}/challengesIssue a new challenge
POST/verify/push/challenges/{challengeId}/verifySubmit a signed nonce
POST/verify/push/factors/{factorId}/revokeRevoke a paired 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.
MethodPathPurpose
POST/verify/passkey/registration/optionsIssue a WebAuthn registration ceremony
POST/verify/passkey/registration/verifyVerify attestation, create the factor
POST/verify/passkey/authentication/optionsIssue a WebAuthn authentication ceremony
POST/verify/passkey/authentication/verifyVerify the assertion, bump the counter
POST/verify/passkey/factors/{factorId}/revokeRevoke a paired passkey
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:
MethodPathPurpose
POST/verify/send (channel: "magic_link")Email a signed single-use link
GET/public/verify/magic-link/consume?token={token}Consume the link and approve the verification
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 30+ languages
  • Fraud detection — blocks known VoIP numbers and high-risk destinations

Configuration Options

These are the body parameters accepted by POST /api/v1/verify/send (sendVerificationSchema):
ParameterTypeDefaultDescription
tostringRequired. Recipient phone number (E.164) or email address.
channelstringsmsDelivery channel: sms, whatsapp, email, voice, viber, telegram, silent.
code_lengthinteger6Number of digits (4–8).
max_attemptsinteger3Maximum verification attempts allowed for this code (1–10).
countrystringISO 3166-1 alpha-2 hint used to parse national-format numbers into E.164 (e.g. TR).
profile_idstringVerify profile to apply (templates, expirySeconds, fallback config, branding).
channelsstring[]Ordered fallback chain (min 1, max 4). Index 0 is the primary channel; the rest are tried in order.
fallback_configobjectAsync fallback engine config — { channel_timeout_seconds: 10–600 (default 60), max_attempts_per_channel: 1–3 (default 1) }. Pairs with channels.
localestring2-letter language code for the localized fallback message/TTS (e.g. en, de, es). Region tags (es-MX) are rejected.
custom_codestringSandbox/test-mode only — fixed OTP digits (must equal code_length). Rejected with live keys.
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

EventDescription
verification.sentThe code was successfully handed to the provider
verification.approvedA /check call matched the code — fires exactly once per verification (CAS-guarded)
verification.failedmax_attempts exhausted with no match, or a SIM-swap pre-flight blocked the send
verification.checkedAny /check attempt landed — successful or not (useful for audit / fraud scoring)
verification.fallback_triggeredThe async fallback engine advanced to the next channel in the chain
verification.fallback_exhaustedEvery channel in the fallback chain was tried without an approved status