Verification configuration
The Verify → Configuration surface (dashboard Outbound → Verify → Configuration) owns the verification profile — the tenant-owned settings record everyPOST /verify/send can bind by profile_id. One profile carries the templates, code TTL, attempt budget, rate limits, webhook URL, and channel order for one verification product, so a send inherits consistent behaviour without passing options in the body.
The dashboard page is a profile list — create, edit, search, activate/deactivate, and delete profiles. This page is a reference for what each group of settings does and how it interacts with the rest of the platform. For the wizard walkthrough, see Manage verification profiles from the Configuration console; for the fallback-chain deep dive, see Verify profiles: fallback chains and advanced factors.
Tenant-owned controls only. Every control on this page is configured by your organization’s owners, admins, and developers. Template edits can be routed through an org-level two-officer gate — see OTP approvals (we cover it there, not here).
1. What a profile owns
A profile groups six concerns under one id:
Fields left at their defaults (600-second TTL even for direct sends, 3 attempts, the platform hourly cap) apply until you override them on a profile and pass its
profile_id. An inactive profile is blocked for new sends but keeps its history for /check.
2. Template configuration
Templates are a per-channel body override map on the profile. Set a body for the channels you want to brand; leave a channel’s body empty and the server-side default applies. The{{code}} placeholder carries the OTP. A template you save for sms, email, voice, viber, telegram, or silent must contain {{code}} (whitespace inside the braces, e.g. {{ code }}, is fine). The save path rejects a literal body without it — otherwise recipients would receive a message with no verification code in it. These placeholders resolve at send time:
Per-channel shapes:
- SMS / email / voice / Viber / Telegram — free-text body;
{{code}}required when set. Thevoicebody is what the TTS prompt reads aloud. - WhatsApp — holds the name of a Meta-approved authentication template, picked through a dedicated chooser in the dashboard. The OTP lives inside the approved template on Meta’s side, so the free-text
{{code}}rule is exempt. - Flashcall / SNA / TOTP / push / magic_link / backup_code — factor-style channels that deliver no literal code text. Their template fields are accepted for forward-compat (e.g. the notification body around a push prompt) but are exempt from the
{{code}}requirement.
3. Expiry and attempts
Code TTL.expirySeconds is bounded at 60–3600 seconds (1 minute – 1 hour). The default form value is 300s; a direct send with no profile expires at the platform default of 600s. There is no expiry parameter on POST /verify/send — the TTL is profile-only, because the fallback engine and per-channel templates also live on the profile. After the TTL the code is dead: /check returns an expired state and a new send is required.
Retry budget. maxAttempts (1–10, default 3) is the number of /check calls a verification accepts. Each /check consumes one attempt whether the code matches or not. Two accounting rules to design around:
- A
/checkagainst an expired verification still consumes an attempt — the attempt counter is independent of the TTL. - When the attempt budget hits
maxAttemptsthe verification moves to a terminalfailedstate andverification.failedfires (CAS-guarded, exactly once). There is no separate lock-out flag — the terminal state is the lock-out: further/checkcalls on that verification id are rejected, and the user must request a new code.
4. Rate limits
Verify has two distinct rate layers, both tenant-configurable on the profile, both evaluated per recipient before any code is minted:- Per-destination attempt cap —
rateLimitPerHour(1–1000, default 5). A hard ceiling on OTP sends to one recipient per rolling hour; breach returns429 VERIFY_RECIPIENT_RATE_LIMITEDper recipient, not per batch. Raise it above the platform default for high-resend signup flows (e.g. B2C onboarding that re-prompts aggressively). - Account-side velocity — the profile’s fraud caps
fraud.velocityPerHour(1–100) andfraud.velocityPerDay(1–1000) run on top of the recipient cap, counting that tenant’s sends to a recipient across the wider window. Use it to stay under your own budget even when the recipient ceiling was raised.
sms record does not force a fallback hop by itself, the fallback chain only advances on timeout/failure.
Two additional send-path layers worth knowing when you budget: the global resend cooldown (VERIFY_RESEND_COOLDOWN, a 30-second SET NX guard per recipient, run before the hourly counter so a burst does not eat hourly slots) and endpoint-level request ceilings (verify-send 20/min, verify-bulk 2/min, verify-check 60/min per tenant).
5. Webhook events
Save onewebhookUrl per profile (HTTPS, or HTTP on localhost) and every lifecycle event from sends bound by that profile delivers there. A verification lifecycle can emit:
A SIM-swap pre-flight block is the exception: the send returns a synchronous
403 SIM_SWAP_DETECTED and no row is created, so no webhook fires — wire step-up to the response, not to the stream. Payload shape, signatures, and retry semantics live in Webhook events and payloads; sign-verification specifics for Verify webhooks are in Verify webhook signatures.
6. Channel order
channels[] is an ordered chain (1–4 channels): index 0 is the primary send, and the remaining entries are tried in order by the fallback engine when the primary times out or fails. The dashboard wizard renders that chain as a drag-ordered list with explicit move-up/move-down buttons, so the order you save is the order the engine walks.
The typical progression rides hardest-to-carry-channel first and the friendliest fallback last — SMS first, voice next, email last — but the order is yours to set. For how the platform prioritizes that traffic when several profiles run concurrently, see Message priority and traffic lanes. Channel-level timeout and per-channel attempts inside the chain come from fallback_config on the send (or the profile’s saved fallback config): channel_timeout_seconds 10–600 (default 60) and max_attempts_per_channel 1–3 (default 1).
Related
- Verify overview — channel capabilities, send/check details, and profile field-level differences
- Configuration console guide — wizard walkthrough, card grid, and edit flows
- Fallback chains guide — chains, fallback engine, and advanced factors
- Webhook event payloads — envelope shape, signatures, retry semantics
- Message cascade groups · Message suppression model · Message priority and traffic lanes