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 in admin panel | 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.
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:
- Your integration exists in the catalog (public or in-house in their organizer's org).
- Your integration has been connected by the organizer of an event the participant has applied to.
- 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 (they've withdrawn). Revento doesn't otherwise gatekeep on 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
For a webview-page use case (your integration embedded inside Revento), the entire warm round-trip happens transparently — the participant sees no Revento UI between their tap and your authenticated content.
Authorization request¶
GET https://auth.revento.example/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.
Consent screen — what the participant sees¶
- Headline: "{your integration name} is requesting access to your data"
- Scope intro: "After signing in, {your integration name} will know:"
- Scope rows: one per scope, with description
- Event context line: "You're using this app in the context of event: {event name}"
- Revocation note: "You can revoke access at any time in Settings → Connected apps."
- Actions: "Sign in" (primary) and "Cancel" (secondary)
(The consent screen renders in the participant's locale; the copy above is the English version, with equivalent Polish translations shown to PL-locale users.)
If the participant clicks "Cancel":
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:
{
"iss": "https://auth.revento.example",
"aud": "your_client_id",
"sub": "usr_def456",
"iat": 1747000000,
"exp": 1747003600,
"email": "kasia@example.com",
"name": "Kasia Nowak",
"event_id": "evt_abc123",
"application_status": "approved"
}
application_status reflects the participant's status at the moment of token issuance for this event. It's a snapshot, not a live signal — for current status, fetch from the API.
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.
Your storage typically looks like:
CREATE TABLE revento_user_sessions (
id SERIAL PRIMARY KEY,
user_id TEXT NOT NULL,
event_id TEXT NOT NULL,
access_token BYTEA NOT NULL,
refresh_token BYTEA NOT NULL,
scopes TEXT[] NOT NULL,
signed_in_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (user_id, event_id)
);
Same user_id, multiple rows. Your app's "this is who's logged in" lookup is by user_id; your "what can this session see" is by (user_id, event_id).
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)GET /api/v1/me/application(scope:event.attendance)
See API → endpoints for full reference.
Revocation¶
User tokens can be invalidated by any of:
| Initiator | What happens |
|---|---|
| Participant revokes via "Settings → Connected apps" | 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_idclaim discriminate. - Caching
application_statusfrom theid_tokenindefinitely. It's a snapshot at issue time. If your UX gates on application state, fetch the live status from/api/v1/me/applicationinstead. - 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_idalone. 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.