User Onboarding
Overview
After account creation, users progress through a configurable series of onboarding steps. Each step represents a task the user must complete (or that can be skipped by platform configuration) before they are fully onboarded. The onboarding system is:- Step-based — a linear sequence of steps, each with a status
- Configurable — steps can be enabled or disabled per-user by the platform
- Idempotent — submitting the same step twice is a no-op, not an error
- Event-sourced — every transition is recorded as an auditable event
Quick Start
1. Create an account
2. Check onboarding state
3. Complete steps
For each step that requires user action, submit it when the user finishes:4. Onboarding complete
When all steps are done,is_complete becomes true and current_step becomes "complete".
Onboarding State
Reading State
Fetch the current onboarding state from the dedicated resource:Response Shape
steps array only contains steps in the user’s cohort flow. A consumer (no org_id) and AGENT_CREATE2 user see five steps. A BYO user sees six (plus byo_safe); a USER_SIGNED_DEPLOY user sees six (plus safe_deploy). Clients should iterate the array rather than indexing into specific positions.
The gated values above are those of an organisation user — the request carries x-juno-orgid, and no environment feature waives phone or KYC. A user without an org_id sees gated: true on all five. See Gating.
Step Statuses
Gating
gated answers whether a skipped status is possible for this user on this step — not whether it applies. A step with gated: false will always require action; a step with gated: true may arrive skipped on a later read if the platform’s configuration changes.
Which configuration decides that depends on the user:
Organisation users always complete phone and KYC verification: no environment feature waives either, so both report
gated: false for them. The wallet steps (byo_safe, safe_deploy) are never waived within a cohort that has them, so they always report gated: false.
Step Metadata
Each step exposes ameta field. When a step has no per-user configuration to communicate, meta is always null — clients can treat meta: null as “no metadata for this step” rather than “metadata pending”.
Per-step meta shape
The
byo_safe and safe_deploy entries only appear in the response for users in their respective cohorts. A consumer or AGENT_CREATE2 user will never see them in the steps array.
KYC Verification Meta
Thekyc_verification step is where a user earns their Sigil (Proof of Personhood) — completing it is what unlocks the identity downstream steps read off.
websdk— use the full embedded verification SDK (default)hybrid— use native API document upload with SDK-based liveness check onlydocument_only— use native API document upload with no liveness check or SDK required
kyc_mode value is selected per integration server-side. Your frontend should read this value and render the appropriate verification flow. See the KYC guide for integration details, including how partner organisations can override the mode per environment.
State Machine
The diagram shows one presentation order shared by every cohort; a cohort’s flow is that order restricted to the steps it contains. Advancement moves to the first step that is required and not yet done, so any gated step that is disabled is passed over and recordedskipped — or the user reaches complete if nothing required remains.
The two phone skipped transitions apply to users without an org_id, whose skips come from per-step platform kill switches. An organisation user’s skips come from their environment features, and none of those waives phone or KYC — so for them both steps are always walked. See Gating.
Wallet cohort: byo_safe vs safe_deploy
Every onboarded user gets a Safe smart wallet, but how the Safe gets there depends on which SIS environment features are enabled for the user’s organisation. The byo_safe and safe_deploy steps are mutually exclusive: a user’s cohort contains at most one of them, and the other does not appear in steps at all. Neither is reported skipped — a step a user’s cohort never had is absent, not waived.
Users without an
org_id have neither step in their flow — they’re outside the SIS-managed cohort, and their Safe is deployed for them.
Submitting the Safe step
The Safe step uses one polymorphic endpoint that branches on the user’s cohort:GET returns the user’s mode (byo, user_signed_deploy, or agent_create2) along with a cohort-specific config block and the persisted wallet once available. config is always present and its shape matches mode; inner fields may be null when no work is currently prepared (e.g. a user_signed_deploy config returns user_operation: null, user_op_hash: null, predicted_safe_address: null until preparation runs).
The POST body is a shape-only payload — the cohort is derived server-side from the user’s SIS environment, so the body does not carry a mode field. Submitting a payload whose shape doesn’t match the server-resolved cohort returns 409 (see Cohort mismatch below).
mode=byo
completed, and auto-advances onboarding. Resubmission with a different address replaces the previously-submitted Safe — the previous row is soft-deleted (deleted_at set, is_primary cleared) so the audit trail is preserved.
Submitting off the step is answered by position, and the two directions are deliberately different statuses:
mode=user_signed_deploy
GET returns the prepared sponsored UserOp envelope when the step is current and no Safe is persisted yet (config.user_operation, config.user_op_hash, config.predicted_safe_address). When the step has not yet been prepared, the same config envelope is returned with those inner fields set to null:
POST:
GET and POST responses surface a _links.user_op_status HAL link pointing to /v0/safe/rpc/{user_op_hash}/status — clients should consume that link rather than constructing the URL manually. See Submitting UserOperations for the full polling protocol — note especially the 503 + Retry-After semantics when finalisation hits a transient downstream error.
mode=agent_create2
The agent signer workflow creates the Safe automatically; the user has nothing to submit. GET returns the predicted Safe address once the agent signer is provisioned. POST is a uniform 204 No Content no-op so clients can use a single “submit” pattern across all cohorts:
GET.
Idempotency
POST accepts an optional Idempotency-Key header (max 255 characters). Retrying the same request with the same key returns the original cached response; reusing a key with a different request body returns 409 with SAF-409-001 (Idempotency Conflict). The cache window is 24 hours.
Cohort mismatch
409 with ONB-409-003 (Onboarding Cohort Mismatch) means the request and the server-derived cohort disagree. It is raised on both methods, for two related reasons:
The second case is a
4xx on a read, which is unusual and deliberate. Cohort membership is written once at signup, while the SIS environment features are read live — so an organisation that enables USER_SIGNED_DEPLOY after a user’s cohort was fixed produces a genuine divergence. Reporting it as 409 is the honest answer; the alternative that was previously returned — 200 with step_status: skipped — told the client the platform had waived a step it had in fact never offered.
In every case, clients should re-fetch GET /v0/user/me/onboarding/steps to learn the user’s actual cohort flow, then act on it. A POST retry with an unchanged payload will never resolve this.
Rate limit
GET /v0/user/me/onboarding/safe is rate-limited per authenticated user — 60 requests per 60 seconds. Polling clients should respect the limit and back off on 429.
Endpoints
Check Onboarding State
Submit Step Completion
200 OK for synchronous transitions, 202 Accepted when the submission enqueues background provisioning work (creation flow, KYC completion). Body shape is identical in both cases:
steps array only contains steps in the user’s cohort flow. A consumer (no org_id) and AGENT_CREATE2 user see five steps. A BYO user sees six (plus byo_safe); a USER_SIGNED_DEPLOY user sees six (plus safe_deploy). Clients should iterate the array rather than indexing into specific positions. As above, the gated values shown are an organisation user’s — see Gating.
Behaviour:
- Records a
step_submittedevent, then immediately advances to the next step - If
stepis still ahead of the user, returns409 Conflictwith the user’s current step in the response body so the client can resync without a follow-up read - Resubmitting a step the user has already finished is an idempotent no-op that returns the current state, so a duplicate submit never needs handling
- Calling submit on an already-completed user is a safe no-op that returns the terminal state
- On a
202 Accepted, the response includes aRetry-Afterheader (seconds) recommending the next poll cadence forGET /v0/user/me/onboarding/steps
Get Event History
Step Reference
Endpoints referenced above:
PUT /v0/user/me/phone— Send a verification code.PUT /v0/user/me/phone/code— Confirm the code.GET /v0/kyc/status— Check KYC result.
Integration Patterns
Building an Onboarding UI
- After account creation, fetch onboarding state.
- Find the step with
"status": "current"and render that step’s UI. - When the user completes the step’s action, submit the step.
- The response contains the updated state — re-render based on the new
currentstep. - Repeat until
is_completeistrue.
GET /v0/user/me/onboarding/steps— Fetch computed onboarding state.POST /v0/user/me/onboarding/steps— Submit the completed step.
Handling Configurable Steps
Steps with"gated": true may have "status": "skipped" if the platform has disabled them. When building a step list UI:
- Filter the
stepsarray to excludeskippedsteps, or show them as completed - Do not hardcode which steps exist — always derive from the
stepsarray - The order in the array is the canonical step order
Implicit Step Advancement
Two steps auto-advance without requiring an explicit submit call: Phone verification: When the user successfully verifies their phone, thephone_verification onboarding step is automatically submitted. After calling the phone verification endpoint, re-fetch onboarding state to see the updated step.
PUT /v0/user/me/phone/code — Confirm the SMS code.
KYC verification: When the KYC status endpoint returns an approved result and the user is currently on the kyc_verification step, it is automatically submitted. After initiating KYC, poll the status endpoint and re-fetch onboarding state once approved.
GET /v0/kyc/status — Poll for the current KYC status.
In both cases, the client should re-fetch state after the triggering action to see the advancement:
Error Handling
All error responses follow RFC 7807 Problem Details format. Theerror_code field is the stable machine-readable identifier; detail is human-readable context for the specific occurrence.
401 Unauthorized:
200 with the current state, so a duplicate or late submit needs no handling. The detail names both the submitted step and the user’s actual current step so the client can resync without a follow-up GET:
step field):
GET /v0/user/me is the deliberate exception: a profile read falls back to the user’s cached onboarding step rather than answering 502, because the cached value can only lag, never point the wrong way.
Polling After 202 Accepted
When the API returns202 Accepted, background provisioning is in flight and the response includes a Retry-After header that is the recommended seconds to wait before the next poll of GET /v0/user/me/onboarding/steps.
Recommended client behaviour:
- Honour
Retry-Afterif present. - If you implement your own backoff, start at 2 seconds and back off exponentially (e.g. 2s → 4s → 8s) up to a 30-second ceiling, until
current_stepadvances oris_completebecomestrue. - Do not poll faster than once per second.
- A client that keeps seeing the same
current_stepafter several polls should surface a “still working” UI rather than retrying indefinitely.