Skip to content

OAuth: Login with Revento (participant-facing)

The flow that issues a user token - credentials your integration uses to know "this specific participant signed in, in the context of this specific event."

Use this when your integration is a participant-facing app: a side-app organizers point participants at for a personalized view, a quiz, a chat room, an interactive map, a feedback form, anything that benefits from "we know who's looking at this and which event they're attending."

If your integration only consumes bulk event data and never has individual participants signing in, you don't need this - organizer-connect is enough.

How this differs from organizer-connect

Organizer connect Login with Revento
Triggered by Organizer for an event Participant on your site clicking "Sign in with Revento"
Token type Installation User
Bound to (event, organization) (user, event, integration)
Acts as "The integration, on behalf of this event" "This specific user, in the context of this event"
Required scopes Event-side: event.read, participants.read, etc. User-side: profile.read, event.attendance
Typical caller Server-to-server cron / handler Participant's browser → your backend
Storage One per event you're connected to One per (user, event) tuple

Both use OAuth 2.0 Authorization Code with PKCE. Both end with a redirect carrying code, exchanged at /oauth/token for tokens. The mechanics are the same; the consent UX, scope catalog, and what the resulting token means are different.

Token lifetimes are shared across both flows. See Refresh tokens → Token lifetimes.

Eligibility - when a participant CAN sign in

A participant can sign into your integration via Login with Revento only if all of the following are true:

  1. Your integration exists in the catalog (public or in-house in their organizer's org).
  2. Your integration has been connected by the organizer of an event the participant has applied to.
  3. The participant has at least one application record for that event - any status counts (submitted, approved, rejected, revision_requested).

Eligibility check #2 is the one developers miss. Your integration sitting in the catalog is not enough - an organizer must have actively connected it to a specific event before any participant of that event can sign in. If no such connection exists, the participant gets an error page rather than a consent screen.

For #3: eligibility is "has any active application record for the event" - submitted, approved, rejected, or revision_requested. A cancelled application makes the participant ineligible. Revento does not otherwise gate access by application state at the auth layer - your integration decides what to expose to the participant per status.

Sequence - first sign-in (cold)

sequenceDiagram
    autonumber
    actor P as Participant
    participant B as Browser
    participant Y as Your backend
    participant R as Revento

    P->>B: Click "Sign in with Revento"<br/>on your site
    B->>Y: GET /signin (your route)
    Y->>Y: Generate PKCE + state
    Y->>B: 302 → Revento /oauth/authorize<br/>(client_id, redirect_uri, scope, state, code_challenge)
    B->>R: GET /oauth/authorize?...
    alt Participant not signed into Revento
        R->>P: Show Revento login
        P->>R: Sign in
    end
    R->>R: Look up which connected events<br/>this participant has applications in
    alt No eligible events
        R->>P: "This app is not connected to<br/>any event you're attending"
    else Multiple eligible events
        R->>P: "Which event do you want to<br/>sign in for?"
        P->>R: Pick event
    else One eligible event
        R-->>R: Use that event automatically
    end
    R->>P: Show consent screen<br/>(scopes, event context, revocation note)
    P->>R: Click "Sign in"
    R->>B: 302 → your redirect_uri<br/>?code=...&state=...
    B->>Y: GET /callback?code=...
    Y->>R: POST /oauth/token (PKCE verifier, etc.)
    R-->>Y: {access_token, refresh_token, id_token, event_id, ...}
    Y->>Y: Store user token by (user_id, event_id)
    Y-->>B: Redirect to participant's content

Sequence - repeat sign-in (warm)

If the participant has previously authorized your integration for the same event, the consent screen is skipped:

sequenceDiagram
    autonumber
    actor P as Participant
    participant B as Browser
    participant Y as Your backend
    participant R as Revento

    P->>B: Click "Sign in with Revento"
    B->>Y: GET /signin
    Y->>B: 302 → Revento /oauth/authorize
    B->>R: GET /oauth/authorize?...
    R-->>R: Existing consent on file<br/>for (user, event, integration)
    R->>B: 302 → your redirect_uri<br/>?code=... (no consent screen)
    B->>Y: GET /callback?code=...
    Y->>R: POST /oauth/token
    R-->>Y: tokens

In embedded or in-app browser flows, the warm round-trip may happen without additional user interaction.

Authorization request

GET https://auth.revento.app/oauth/authorize?
    response_type=code
    &client_id={your client_id}
    &redirect_uri={your registered redirect_uri}
    &scope=profile.read event.attendance
    &state={CSRF token}
    &code_challenge={PKCE challenge}
    &code_challenge_method=S256

Note: no event_id parameter. With Login with Revento, Revento itself decides which event to authenticate the participant in - based on which events have your integration connected and the participant has applications in. If there's exactly one match, it's used automatically. If there are multiple matches, the participant picks. If there are zero matches, the participant sees an error.

The participant authenticates in the context of an event; your integration learns which event from the resulting token's event_id claim. You don't ask the participant which event ahead of time.

The consent screen shows the integration name, requested scopes, the event context, and options to continue or cancel.

If the participant clicks "Cancel":

GET {your redirect_uri}?error=access_denied&state=...

No token is issued, no record is created. The participant can try again later from your site - eligibility is re-evaluated each time.

Token response

{
  "access_token":       "rev_user_x9k2m...",
  "refresh_token":      "rev_refresh_b3a7c...",
  "id_token":           "eyJhbGciOi...",
  "token_type":         "Bearer",
  "expires_in":         3600,
  "refresh_expires_in": 7776000,
  "scope":              "profile.read event.attendance",
  "event_id":           "evt_abc123",
  "user_id":            "usr_def456"
}

Fields specific to user tokens

Field Description
id_token Signed JWT with the participant's identity claims (see below). Does NOT replace access_token for API calls - access_token is still the bearer for /api/v1/me/... endpoints.
event_id The event the participant authenticated in the context of. Different sign-ins from the same participant for different events produce different tokens, each with a different event_id.
user_id Stable across all the participant's tokens. Use this as your foreign key.

id_token claims

Standard OIDC-style claims plus our event context. Claims are scope-gated - only the claims your granted scope set covers are present.

Minimum (always present):

{
  "iss":      "https://auth.revento.app",
  "aud":      "your_client_id",
  "sub":      "usr_def456",
  "iat":      1747000000,
  "exp":      1747003600,
  "event_id": "evt_abc123"
}

With profile.read also granted, email and name are added:

{
  "iss":      "https://auth.revento.app",
  "aud":      "your_client_id",
  "sub":      "usr_def456",
  "iat":      1747000000,
  "exp":      1747003600,
  "event_id": "evt_abc123",
  "email":    "kasia@example.com",
  "name":     "Kasia Nowak"
}

Per event.attendance (the scope that issues this token in the first place): the fact that you hold the token at all is the confirmation the participant attends the event. The scope discloses nothing else - no application status, no roles. If your integration needs email/name, request profile.read in addition; if you need richer data, you're outside the v1 user-token surface.

Multi-event participants

A participant who attends 5 events, all with your integration connected, will have 5 separate user tokens if they sign in 5 times. Each token has a different event_id and is bound to its (user, event, integration) tuple. They cannot be substituted for each other.

If you want a unified view across events for one participant, your integration correlates by user_id on its side. There is no combined token across events; each (user, event) pair is independent.

Store user tokens keyed by both user_id and event_id so separate event sessions remain distinct.

API calls with a user token

User tokens hit only user-scoped endpoints (/api/v1/me/...). Mixing token types is a 403:

GET /api/v1/events/{id}/participants
Authorization: Bearer rev_user_...

403 Forbidden
{
  "error": "installation_token_required",
  "message": "This endpoint requires an installation token, not a user token.",
  "request_id": "..."
}

And vice versa for using an installation token on /me/... - 403 user_token_required.

The user-token endpoints:

  • GET /api/v1/me/profile (scope: profile.read)

See API → endpoints for full reference.

event.attendance by itself doesn't unlock any data-fetching endpoint - the scope's role is to discriminate eligible-events at sign-in time (the participant must have an application on the event for the OAuth flow to issue a token at all). The mere existence of an issued user token is the contract evidence that the participant attends the bound event.

Revocation

User tokens can be invalidated by any of:

Initiator What happens
Participant revokes the integration All access + refresh tokens for (this user, this integration) are invalidated. user.revoked_access webhook fires if subscribed.
Organizer disconnects the integration from the event All user tokens issued under that (event, integration) tuple are invalidated, regardless of which user.
You unpublish your integration Every user token for your integration across every event invalidates.
Revento suspends your integration Same as above.

Any API call after a revocation returns 401 token_revoked. Treat it the same way you treat installation-token revocation - mark the session ended, stop background work for that user, surface to UX as "you've been signed out."

Common pitfalls

  • Treating user tokens as account-level credentials. They're not - they're (user, event, integration)-scoped. A participant who attends two events gets two tokens. Don't try to deduplicate them server-side; let the event_id claim discriminate.
  • Asking for application status from a user token. The event.attendance scope discloses only attendance confirmation (the token's existence). It does NOT include application_status, and there is no user-token endpoint that returns it. If your integration needs to gate UX on application state (approved / pending / etc.), use your installation token's GET /events/{id}/participants (scope: participants.read) and correlate by user_id. That's how participant-facing integrations are expected to combine the two token types.
  • Showing a generic "Sign in with Revento" button when no event is connected. The participant clicks, lands on a Revento error page, and bounces. Better UX: only show the button when you know an event your integration is connected to has applications from this user - which you can check via your installation tokens. If you can't precompute, accept the error path is part of the flow.
  • Persisting tokens by user_id alone. A participant signing in for event A and then for event B issues a new token but doesn't invalidate the first. Your storage should be keyed by (user_id, event_id) to keep both alive.