The handshake

Last updated 2 September 2026.

A link is one row: this Trivela account is this user on your service. Creating it is a four-call handshake a player goes through once. Your site and Trivela are different domains, so nothing crosses in a cookie; a single-use ticket crosses instead.

①  you      POST /api/link/start   { subject, returnUrl }    →  { url }
②  player   opens url on Trivela, sees your service's name, presses Confirm
③  Trivela  redirects to returnUrl?ticket=…
④  you      POST /api/link/redeem  { ticket, subject }        →  { trivelaUserId, handle }

The subject

subject is your stable id for the user, and it is how every later call says which user you mean. Choose the id that survives a rename and a change of login provider — a primary key, not a username, not a Discord snowflake. Trivela never parses it, never shows it to another service, and compares it byte for byte; a padded one is rejected rather than trimmed.

① Start

Reserve a request and get the URL to send the player to. It lives ten minutes — how long the person has to decide. returnUrl must be on your registered origin; whatever you put in its query string comes back untouched, so put your CSRF nonce there. subjectLabel is optional display text for the confirm screen — however your service names the account — so the player can see which of your accounts they are linking.

② ③ Confirm and return

On Trivela the player sees your service’s name, the label, and their own handle, and the line: {Service} will be told your Trivela handle. It does not get your email address, your Discord login or access to any other service you have linked.” A player without a handle claims one first. On confirm they are sent to your returnUrl with ticket=… appended — valid for two minutes and one redeem.

④ Redeem

Burn the ticket and receive the identity: trivelaUserId (durable — this is the key you store), handle (may be null, may change), linkedAt. Redeeming the same live pair again is a 200 with alreadyLinked: true, not an error.

Send subject on redeem, and redeem from a route handler that redirects at once. The first stops a ticket started by one person from being redeemed in somebody else’s session on your site — your CSRF nonce cannot catch that, only the subject check can. The second keeps the ticket out of the address bar, history and Referers. A failed redeem cannot be retried; the ticket is burned by the statement that reads it.

The three 409s you can get, and what each means, are on the link/redeem reference page.

After the handshake

Cache the link under your service id. A page of yours must never wait on Trivela; if Trivela is down, a cached link is stale, not broken.

Check it when you show it. Wherever the link is visible — a settings row, a “Connected” badge — call link/status on render. It returns { "linked": false } if the player has since disconnected on their Trivela account page, and that is your cue to clear the cache. A 400 is not a disconnect: show “could not reach Trivela” and keep what you have.

Disconnecting from your side is link/unlink — idempotent, and unlinked: false only means there was nothing to end. Either way the link ends, clear every value you were showing from Trivela; see Staying in sync.