Verification modes
Every flow has a verification mode, chosen on the Configure tab when you build it:
- Backend (the default): proofs are verified by Self’s servers. Results arrive through webhooks and the API.
- On-chain: proofs are verified by a smart contract on Celo. Each verified user receives a soulbound token (SBT) in their wallet, a non-transferable, on-chain record that they passed your flow. You still get the same webhooks and API results.
The mode is part of the flow’s configuration, so like every other setting it is immutable once deployed. To switch modes, archive the flow and create a new one.
Which mode should I use?
Pick Backend unless you specifically need an on-chain artifact. It supports every rule, has no wallet requirement for your users, and is the mode the rest of these docs assume.
Pick On-chain when the verification result must be readable by other smart contracts or wallets, for example token-gated apps, airdrops with sybil resistance, or membership that other on-chain protocols can check without calling your backend.
| Backend | On-chain | |
|---|---|---|
| Where the proof is verified | Self’s servers | A contract on Celo, deployed for your flow |
| What the user needs | The Self app | The Self app and a wallet |
| What the user receives | Nothing on-chain | A soulbound token (SBT) in their wallet |
| Results in webhooks and API | Yes | Yes, plus the transaction hash and contract address |
| Supported rules | All rules | All rules except maximum age, with country-list limits |
| Networks | — | Celo Sepolia (test), Celo Mainnet (live) |
| Gas cost to you or the user | — | None, Self submits the transactions |
What happens during a verification
The lifecycle is the same shape in both modes: create a session, the user verifies, you get a result. Here’s where they differ, step by step:
Backend
- Your backend calls
sessions.create(...)and sends the user to theverificationUrl. - The user scans the QR code (or taps the deeplink) and approves in the Self app.
- Self’s servers verify the proof against the flow’s rules.
- The result lands everywhere at once: the
verification.completedwebhook, the session status, and the activity log.
On-chain
- Same
sessions.create(...)call. The only precondition is that the flow’s contract is deployed for that environment (a one-time step that happens when you deploy the flow). - Before the QR code, the hosted page asks the user to connect a wallet and sign a gas-free message. That wallet receives the soulbound token.
- The proof is verified by the flow’s contract on Celo, which mints the token. Self submits the transaction and pays the gas.
- Once the transaction confirms, the same webhook, session status, and activity log update, now carrying the transaction hash and contract address.
What to expect: on-chain adds the wallet step for the user and waits for the transaction to confirm, so results take a little longer to become terminal. Either way, treat the webhook as the authoritative result, exactly as in backend mode.
What changes in your integration
Very little. Sessions are created the same way, the hosted page handles the wallet steps, and the terminal statuses are the same (valid, invalid, error, expired).
Three additions:
- The
verification.completedwebhook carriesverification_mode: 'onchain', plustx_hashandcontract_addresswhen a token was minted. - The Activity log shows the mint transaction for each verification, linked to the block explorer.
- Session creation requires the flow’s contract to be deployed for that environment; otherwise the API returns
409with ano_deploymentdiscriminator. Deploys finish in about a minute, so in practice you only see this immediately after creating the flow.
And one removal: on-chain verifications keep no off-chain proof record, the chain is the record. The session’s storage.state reports 'skipped' for them.
Billing
On-chain verifications follow the same billing rules as off-chain: a confirmed mint (valid) and a contract rejection (invalid) are both charged; a verification that fails for infrastructure reasons is never charged. Test-environment verifications are never charged in either mode. See Credits and usage.
Related
- On-chain verification and SBTs: contracts, deploys, the wallet experience, and limits.
- Anatomy of a flow: what a flow holds and why it’s immutable.
- Event catalog: the webhook payload, including the on-chain fields.
Thanks for your feedback!