Skip to main content

Second-officer approvals for Verify

Some OTP changes are too sensitive for one operator: the exact message body a subscriber receives, and how aggressively a verification can be re-sent. This guide sets up Orbit Verify’s second-officer approvals (a four-eyes gate) so those changes take effect only when a second supervisor signs off. That holds OTP-burst abuse and copy edits at bay even if a single operator account is compromised. The full field list lives in the OTP approvals reference; this guide walks the setup and day-to-day workflow.

1. What an approval gates

The gate covers exactly two kinds of change:
  • OTP template changes (otp_template) — new submissions and edits to the per-channel message bodies on your verification profiles (the copy a recipient reads in an SMS, WhatsApp, email, or voice OTP).
  • Resend-burst windows (resend_burst) — a burst of POST /verify/resend calls against one verification. Tight resend limits starve a flaky integration; overly wide limits invite OTP-burst abuse, so widening the burst count gets a second set of eyes.
Everything else in Verify — sends, checks, profiles’ non-template settings — flows straight through. With the gate off (the default), nothing queues at all.

2. Roles: requester and approver

Two roles participate — and the approver must never be the requester:
  • Requester — any team member who edits OTP templates or triggers resends (typically a member or developer role). When the gate is on and their change is gated, the request lands in the approvals queue instead of applying.
  • Approver — an owner or admin. They read the queue, judge each item, and approve or reject it. Only owners and admins can see the queue or edit the policy; other roles get a access-restricted notice and the API answers 403.
Self-approval is impossible by design: the API returns 409 FOUR_EYES_VIOLATION when the approver equals the requester, and nothing moves. Decide with a second person, every time.

3. Workflow: request → approve or reject → execute

  1. Requester queues the change. They edit a template on the Verify → Configuration surface, or drive enough resends against one verification to exceed the configured burst threshold. Instead of applying, the change enters the pending queue and the caller receives a gated: true response telling them which kind queued.
  2. Approver opens the queue. In the dashboard they navigate to Outbound → OTP approvals. The queue lists pending items oldest-first, each showing the kind, the requester’s name, and the exact rendered template body (or the burst count and window) they are judging.
  3. Decision. The approver clicks Approve or Reject. Reject may carry an optional reason (up to 1,000 characters) that goes back to the requester.
  4. Execution only on approval. An approved template’s rendered body is applied to its verification profile; an approved resend burst re-enters the normal Verify dispatch path (respecting cooldowns, rate limits, and channel fallback). A rejected item goes nowhere — and the requester can re-submit it, which queues a fresh row.
The whole flow also works over the API: the queue is served from GET /api/v1/verify/approvals/pending, and decisions go through POST /api/v1/verify/approvals/:id/approve or .../reject.

4. Audit trail

Every decision and every policy change lands in the audit log with the acting user, the queue item (or organization, for policy edits), and any rejection reason:
  • verify.approval.approved — a second officer released an item.
  • verify.approval.rejected — with the reason in the detail payload.
  • verify.approval.settings.updated — a policy edit, with the new values.
The requester can never cut their own item through: a self-approve attempt fails before any state moves, and only decisions that actually happened reach the log.

5. Turn the gate on (worked examples)

The policy lives on the Policy card of the OTP approvals page — or over the API at PUT /api/v1/verify/approvals/settings. Owners and admins edit it; any authenticated member can read it (GET /settings) to predict whether their next change will queue.

5.1 Gate OTP template changes

Turn on the master switch. From now on a non-supervisor’s template edit queues for approval:
200
With resend_burst_threshold left at its null default, every resend burst queues — the strictest posture.

5.2 Widen the resend-burst window

To let routine resends through and gate only oversized bursts, set a threshold — for example 25 resends per verification within a 300-second window:
Bursts at or below the threshold pass; anything bigger queues for a second officer. Valid inputs are 1 to 1,000,000 resends per threshold and 1 to 86,400 seconds per window (resend_burst_window_seconds).
Prerequisites and expected result: you need an owner or admin role and a verify:write-capable key. On success the policy applies immediately — changes that match the gate start queueing from the next request onward.

6. Edge cases

  • Time-expired requests. A queued resend burst can outlive its verification: if the verification expires while it waits, the approved replay is declined by the normal resend rules and the requester must start a fresh verification. The queue row stays pending so a supervisor can retry or reject it.
  • Rejection comments. The optional reason is returned to the requester; keep it actionable (“Tighten the wording and re-submit”). A rejection never blocks re-submission — it queues a fresh row.
  • Pending status on the configuration page. When a template edit queues, the requester sees it held as a pending approval instead of applied; the Verify → Configuration surface keeps showing the currently live body until a second officer releases the change. The pending queue itself lives only on Outbound → OTP approvals (owners and admins).
  • Gate flips mid-flight. Turning the policy off stops new queueing; items already pending still need a decision before they execute.

See also