ZevAuth
Sign up

Delegated access to Zev products

Common scenarios

The situations partners hit most often with delegated access, what happens in each, and what to build.

Most questions about delegated access are one of these. Each says what you are trying to do, what happens, and what to build.

You want to test before going live

What happens. Your live app needs ZevID’s approval and a verified domain. Your test environment needs neither, but only your own team can sign in to it or connect it, and test and live never mix.

Build this. Test with your test environment and your team’s own ZevID accounts. Add teammates under Team in the console. Test connections end after a set number of days, shown in the console; reconnect to keep testing. See Test environments.

Your app already uses ZevAuth for sign-in, then asks for ZevCloud access

What happens. The user already has a ZevID account and a ZevID session from signing in to you, so they see the consent screen only, with no second sign-in. High-risk permissions may still ask them to “Confirm it’s you”.

Build this. Tell ZevID who you expect: build the URL on your server with your ZevAuth user_id, or send their ZevID account id as login_hint. Only that account can approve. Check that account_id in the token response is the same user. We check it too, and revoke a connection that came back for anyone else. See The flow.

Your app has its own sign-in, not ZevAuth

What happens. The user signs in to ZevID once, on the consent screen, the way they would for “Connect GitHub”. That proves they own the product account. It does not replace your sign-in.

Build this. Store the connection (grant_id) against your own user, in the session that started the flow. Never match users by email.

The browser is signed in to a different ZevID account than your user

What happens. With a hint (login_hint, or user_id on the server call), ZevID asks the user to switch to the right account. If they cannot or will not, you get error=access_denied with error_reason=account_mismatch.

Build this. Tell the user to continue with the ZevID account they use with you. Never link a connection to a different account.

The user has never used the product

What happens. ZevCloud creates their account the first time they arrive with their ZevID. The consent screen shows ZevCloud’s own “Create a team” button, which brings them straight back to finish connecting.

Build this. Nothing extra. Never create product accounts or teams for users; only the person can, in the product.

None of the user’s teams can be picked

What happens. Teams the user cannot connect, for example where they are only a member, are shown greyed out with the product’s reason, such as “Only owners and admins can connect apps”.

Build this. Explain the requirement in your UI before you send them to connect.

The user allows the connection but leaves a high-risk permission unticked

What happens. The connection works without it.

Build this. Always read the granted scope. Offer the feature again later through authorize. See High-risk permissions.

A high-risk permission ended after its confirmation window

What happens. Tokens stop carrying it; the rest of the connection keeps working. Product calls that need it answer 403 high_risk_confirmation_required. A refresh answers invalid_grant with reason: high_risk_expired only when nothing else usable is left on the connection.

Build this. Send the user through authorize with prompt=reconfirm. Do not ask them to reconnect from scratch.

The user wants to add or remove a team later

What happens. They can remove one from Apps and websites in ZevID. Adding one always starts from your app.

Build this. Offer a Manage teams button that starts authorize with prompt=configure.

You need two Zev products

What happens. Each product is a separate approval, consent and connection.

Build this. Run one authorize per product and store each grant_id separately.

Access stops working

What happens. The user disconnected, removed a team or lost their role; the product or ZevID paused a permission or suspended your app; a test connection ended; or a refresh token was reused. Each arrives as a webhook with a reason, and as an error with the same reason.

Build this. Handle events and errors by reason:

  • reconnect for grant_revoked, refresh_reused and test_grant_expired;
  • reconfirm for high_risk_expired;
  • wait for ZevID on pauses and suspensions (scope_suspended, grant_suspended, app_suspended).

The full list is on Errors.

Two servers refresh the same connection at once

What happens. It looks like theft: every refresh token of that connection is revoked and the user must reconnect.

Build this. Serialise refreshes per connection, for example with a lock on grant_id, and always store the newest refresh token. See Refresh tokens.

Updated at, Wednesday, October 21, 2026