The issuer-challenge model
Before Orbit trusts certain registrations, it asks the counterparty to prove something only the real counterparty can prove: that a human is at the keyboard, that the party controls the phone number or email address being registered, or that the business is the entity it claims to be. Each such proof request is an issuer challenge. A sender ID does not go live on a form submission alone; a marketplace publisher listing does not appear on a self-declaration alone. The registrar issues a challenge, the counterparty solves it, and only then does the registration bind. This page is the canonical explainer for the model every issuing surface shares. It names the challenge classes, the surfaces that fire them, the state machine each challenge moves through, the payload a solver must return, and what happens when a challenge expires or fails. The per-surface procedures (which documents a DLT submission needs, how the 10DLC wizard walks a brand) live in their own guides; this page tells you how to reason about any challenge any surface issues. If you are looking for the proof a session is live before a destructive operation, that is a different mechanism: see Destructive-operation re-auth challenge. Issuer challenges prove a counterparty at registration time; re-auth challenges prove a person at the keyboard at operation time.The challenge definition
The registrar issues four challenge classes. Each asks for one specific proof and nothing else:
A challenge always names its class, its issuer (the surface that fired
it), and its subject (the sender ID, the WhatsApp Business account, the
publisher submission, the key being created). A solve is only accepted
for the exact challenge it answers; a code delivered for one subject
cannot verify a different one.
When a challenge fires
The following surfaces issue challenges as part of their normal flow. The class column is what the surface typically issues; a surface may stack two classes when its risk warrants it (for example, a sender-ID registration from a fresh account).
The rule across all surfaces is the same: the challenge fires at the
moment state would become trusted, and the trusted state is withheld
until the challenge resolves. Nothing downstream (traffic on the sender
ID, a public listing, an active key) proceeds on an open challenge.
The state enum and transitions
Every challenge moves through one state machine, regardless of class:pending— issued and waiting for a solve. The TTL clock starts here.challenged— a solve payload has been submitted and is being checked (code comparison, captcha verification, or compliance review forkyc-doc-upload).resolved— terminal success. The issuing surface binds the proof to the registration and proceeds.expired— terminal failure. The TTL elapsed before a verified solve; nothing further can be submitted against this challenge.
A resolved challenge is then bound to the request that asked for the
registration: the surface records which challenge id cleared it, and the
registration carries that binding forward. A challenge resolved last
month does not satisfy a registration attempted today.
Payload contract per class
Every challenge, whichever class, carries the same envelope when issued:request_id— copied verbatim from the challenge. The consume step rejects any solve whoserequest_iddoes not match the challenge it targets; this is what stops a code issued for one subject from being replayed against another.challenge_id— theidof the challenge being solved.
The authentication on the solve call is the tenant’s own credential
(session or API key). The challenge payload is not itself a credential;
it is a claim that a proof was produced, checked against the stored
challenge.
Expiry and retry semantics
Each challenge carries a retry budget, reported asattempts_remaining and decremented on every rejected attempt:
Terminal failure modes, in order of how you will meet them:
expired— the TTL elapsed. Mint a fresh challenge; nothing carries over except whatever documents you already uploaded.- Budget exhausted — too many rejected attempts. The challenge closes and the surface imposes a cooldown before a new one can be issued; this is what makes brute-forcing a six-digit code uneconomic.
- Review denied (
kyc-doc-upload) — the reviewer rejected the documents. The response carries the reason; correct the submission and start a new challenge.
Tenant-side handling
If you build on the dashboard, most of this is handled for you: the wizard for each surface renders the right challenge UI (puzzle, code entry, or upload form), shows the remaining time and attempts, and re-issues on expiry. You interact with it as a user, not as an integrator. If you integrate over the API, two patterns work, and the right one depends on the class:- Poll for short classes. After submitting a solve for
captcha,phone-verify, oremail-verify, re-read the registration status; verification completes in seconds, so a short poll interval against the registration endpoint is sufficient. - Subscribe for
kyc-doc-upload. Review takes days, so polling is the wrong shape. The resolution arrives as a webhook event on your tenant’s event stream: achallenge.resolved(or terminalchallenge.expired/ denial) event names the challenge id and the registration it unblocked. Register a webhook endpoint for the surface’s events and drive your state machine off it.
expires_at and attempts_remaining rather than assuming your own
clock or counter agrees with the registrar’s.
Failure-mode troubleshooting
Worked walkthroughs
Dashboard: WhatsApp Business verification (phone-verify)
- In Channels → WhatsApp, start the Business verification for the phone number you are binding.
- The wizard issues a
phone-verifychallenge and sends a one-time code to that number over WhatsApp itself; the page shows the countdown and the attempts remaining. - Enter the code. The wizard submits the solve with the correct
request_idand shows the challenge move toresolved. - The wizard proceeds to bind the Business account. If the code expired, the wizard re-issues the challenge in place; you never need to restart the whole verification.
API: sender-ID registration (captcha + solve)
Register a sender ID from a fresh account; the registration comes back
with an open challenge instead of an approval:
request_id:
resolved, the
registration proceeds to approval, and a re-read of the sender ID shows
it without any open challenges. On rejection, attempts_remaining
decrements and the same sequence runs again with a fresh token; on
expiry, the whole challenge is re-issued.
See also
- Destructive-operation re-auth challenge — the sibling model: proving a live session before irreversible operations, with its own mint-carry-consume flow.
- US 10DLC registration — the carrier registry flow whose verification step issues the challenges above.
- Agent marketplace — where publisher submissions
and their
kyc-doc-uploadchallenges resolve into public listings. - Account security model — the session and 2FA posture that issuer challenges sit beside, not inside.
- Compliance is tenant-owned — which controls in these flows are yours to configure and which the registrar owns.