Skip to content
Skip the search — install the Self skill Let your AI agent integrate Self for you.

Start typing to search the documentation.

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.

BackendOn-chain
Where the proof is verifiedSelf’s serversA contract on Celo, deployed for your flow
What the user needsThe Self appThe Self app and a wallet
What the user receivesNothing on-chainA soulbound token (SBT) in their wallet
Results in webhooks and APIYesYes, plus the transaction hash and contract address
Supported rulesAll rulesAll rules except maximum age, with country-list limits
NetworksCelo Sepolia (test), Celo Mainnet (live)
Gas cost to you or the userNone, 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

  1. Your backend calls sessions.create(...) and sends the user to the verificationUrl.
  2. The user scans the QR code (or taps the deeplink) and approves in the Self app.
  3. Self’s servers verify the proof against the flow’s rules.
  4. The result lands everywhere at once: the verification.completed webhook, the session status, and the activity log.

On-chain

  1. 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).
  2. 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.
  3. The proof is verified by the flow’s contract on Celo, which mints the token. Self submits the transaction and pays the gas.
  4. 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.completed webhook carries verification_mode: 'onchain', plus tx_hash and contract_address when 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 409 with a no_deployment discriminator. 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.

Was this page helpful?