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.
| Field | Type | Description |
|---|---|---|
current_step | string | The step the user is currently on, or "complete" |
is_complete | boolean | true when all steps are finished |
steps | array | All onboarding steps in order |
steps[].step | string | Step identifier |
steps[].status | string | One of: pending, current, submitted, completed, skipped |
steps[].gated | boolean | Whether this step is configurable (can be skipped by the platform) |
steps[].meta | object or null | Step-specific configuration metadata (see Step Metadata) |
Step Statuses
| Status | Meaning |
|---|---|
pending | Step has not been reached yet |
current | User is on this step now — action required |
submitted | User submitted this step (transient — auto-advances immediately) |
completed | Step finished successfully |
skipped | Step was skipped by platform configuration |
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
| Step | meta keys | Notes |
|---|---|---|
phone_verification | — | Always null. |
kyc_verification | kyc_mode | Always populated; see below. |
byo_safe | chain_id | Only present in the BYO cohort flow; chain_id populated when the user has a primary_chain_id (see Wallet cohort). |
safe_deploy | chain_id | Only present in the USER_SIGNED_DEPLOY cohort flow; chain_id populated when the user has a primary_chain_id (see Wallet cohort). |
open_banking | — | Always null. |
card_setup | — | Always null. |
feature_selection | — | Always null. |
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.
| Key | Values | Description |
|---|---|---|
kyc_mode | "websdk", "hybrid", "document_only" | Which verification flow to render |
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
Any gated step that is disabled will be automatically skipped during advancement. The state machine jumps to the next enabled step (or tocomplete if all remaining steps are skipped).
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 — for any given user, at most one of them is current; the other is skipped.
AI_AGENT | USER_SIGNED_DEPLOY | byo_safe | safe_deploy | What runs |
|---|---|---|---|---|
| enabled | disabled | skipped | skipped | Sumvin’s signing service deploys a Safe with an agent signer attached. No user action. |
| disabled | enabled | skipped | required | The user signs the Safe-deployment UserOp themselves (see Safe user operations). |
| disabled | disabled | required | skipped | The user submits the address of a Safe they already control on-chain. |
| enabled | enabled | — | — | Rejected at SIS feature-write — SIS-409-008. See Environment features. |
org_id skip both — they’re outside the SIS-managed cohort.
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. A submission for a user already past this step returns 409 with ONB-409-002 (Onboarding Step Already Completed).
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:
UserWallet row immediately in pending so downstream features (like DID minting) can target the predicted address. Bundler inclusion promotes the row to completed via the status route below; revert flips it to failed.
After submission, clients poll the on-chain UserOp status. While a UserOp is in flight, both the 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
If the submitted payload shape does not match the server-derived cohort (typically a stale client cache after an SIS feature flip — e.g. a BYO-shaped{"address": "0x..."} for a user the server resolved as agent_create2), the route returns 409 with ONB-409-003 (Onboarding Cohort Mismatch). Clients should re-fetch GET to learn the new cohort and re-submit.
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.
Behaviour:
- Records a
step_submittedevent, then immediately advances to the next step - If
stepdoes not match the user’s current step, returns409 Conflictwith the user’s current step in the response body so the client can resync without a follow-up read - 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
| Field | Type | Description |
|---|---|---|
step | string | The step this event relates to |
event_type | string | One of: step_entered, step_submitted, step_completed, step_skipped |
from_step | string or null | The previous step at the time of transition |
duration_ms | integer or null | Time spent in the step (only on step_completed events) |
created_at | integer | Event timestamp (epoch milliseconds) |
Step Reference
| Step | Description | Related Endpoints | Auto-advances? |
|---|---|---|---|
phone_verification | Verify phone number via SMS code | Update phone, then verify code — see below | Yes — successful code verification auto-submits this step |
kyc_verification | Complete identity verification (mode determined by meta.kyc_mode) | Start KYC, then poll status — see below | Yes — approved KYC status auto-submits this step |
byo_safe | Submit a Safe wallet address you already control | POST /v0/user/me/onboarding/safe — appears only when the user’s organisation is configured for Bring-Your-Own-Safe | No |
safe_deploy | Sign a deploy UserOp to deploy a new Safe | POST /v0/user/me/onboarding/safe — appears only when the user’s organisation is configured for user-signed Safe deployment | Yes — auto-advances after the bundler confirms the deploy |
open_banking | Connect bank accounts | Explicit submit only | No |
card_setup | Set up payment card | Explicit submit only | No |
feature_selection | Choose platform features | Explicit submit only | No |
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.
| Status | Error code | When it fires |
|---|---|---|
| 401 Unauthorized | USR-401-001 | Missing or invalid authentication token |
| 404 Not Found | USR-404-001 | User account does not exist — call POST /v0/user/ first |
| 409 Conflict | ONB-409-001 | The submitted step does not match the user’s current step |
| 422 Unprocessable Content | (validation) | Request body missing required step field |
GET:
step field):
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.
Reference Tables
Step Identifiers
| Value | Description |
|---|---|
created | Initial state after account creation (not a user-facing step) |
phone_verification | Phone number verification via SMS |
kyc_verification | Identity verification (KYC) |
byo_safe | Bring-Your-Own-Safe submission — appears only when the user’s organisation is configured for that mode |
safe_deploy | User-signed Safe deployment — appears only when the user’s organisation is configured for that mode |
open_banking | Bank account connection |
card_setup | Payment card setup |
feature_selection | Platform feature selection |
complete | Terminal state — onboarding finished |
Event Types
| Value | Description |
|---|---|
step_entered | User arrived at a new step |
step_submitted | User submitted a step (recorded by submit endpoint, not advance) |
step_completed | Step was completed and user advanced past it |
step_skipped | Step was skipped by platform configuration |
Status Values
| Value | Description |
|---|---|
pending | Step not yet reached |
current | User is on this step — action required |
submitted | Step was submitted (transient — advances immediately) |
completed | Step finished |
skipped | Step disabled by platform configuration |