What you send
An ordinary authorize request with thechannel scope:
What you get
After exchanging the code for tokens, two extra claims:
Without the
channel scope there are no typed claims. They reach neither the access_token
nor the id_token, and therefore never appear in the /connect/userinfo response — it mirrors
the access token.
The gate applies in every delivery mode, the embedded one included. Even when Veriqa is
linked as a library into your own process, the application receives the identity through the
normal authorize → code → token exchange rather than as a ready-made principal. There is no
“internal” path that hands out channel claims bypassing the scope.
How to link them
sub is the stable subject identifier; the channel_type + channel_user_id pair is the user’s
address in a specific messaging app. The link lives on your side: Veriqa does not keep your
application’s accounts and does not map them to channels.
The practical consequence: sub is your account key. The off-the-shelf CMSes do the same — Drupal
and TYPO3 store it as the external identity name.
When the account already exists
The scenario doubles as “add a channel to an existing account”: a user who signed in the usual way goes through authorize with thechannel scope, and you store the returned pair next to your
account. There is no separate API for this — it is the same request.
Linking from your server, without a sign-in
Sometimes there is no browser redirect to hang the link on: your server wants a channel for an account on its own — for example, a signed-in user asks your support bot to connect a messaging app. That goes through the server-to-server confirmation entry followed by a token exchange. What the integrator enables for the client:AllowClientCredentials, AllowConfirmationTokenGrant
and openid in AllowedScopes (plus channel if you want the typed claims). The permission is the
integrator’s: no parameter of your request widens what is disclosed, and the claims never go beyond
AllowedScopes.
-
Create a confirmation transaction with
POST /api/transaction/confirmation, withoutexpected_identities— you do not know yet which channel account will confirm; that is exactly what you want to learn. Show the user thechannel_entryfrom the response. -
Wait for the outcome with
GET /api/transaction/{id}/resultuntil it isconfirmed. With no expected identitiesmatched_typeisnull— that is not a refusal, and the token is issued all the same. -
Exchange the transaction for a token:
You get an ordinary token response:
access_token,token_type,expires_inandid_token, with norefresh_token.scopefollows RFC 6749 §5.1: it is returned only when the granted scopes differ from the requested ones. The exchange grants exactly what you requested, so the member is absent — do not require it. -
Store
subfrom theid_tokennext to your account.
scopemust containopenid;channeladdschannel_typeandchannel_user_id; anything else stays withinAllowedScopes.offline_accessis refused withinvalid_scope, as is ascopewithoutopenid. A missingtransaction_idisinvalid_request.- A transaction is redeemed once. A second exchange gets
invalid_grant— the same error with the same description as an unknown identifier, another client’s transaction, a transaction not yet completed or one already removed afterCompletedRetentionSeconds. The responses do not tell these reasons apart on purpose, so exchange right afterconfirmed. Of two simultaneous exchanges, exactly one gets the token. - Once applies to the redemption, not to reading the claims: the
access_tokenof the same response carries the same claims and returns them from/connect/userinfountil it expires. - The link key is
subas a whole. With the default identity resolver it is composed as{channel_type}:{channel_user_id}, but a host with its own identity resolver may compose it differently — do not parse the string; request thechannelscope when you need the pair.
string slots of the action type’s message contract;
each such slot must declare its max_length, and the ceiling is 128 characters. The integrator writes
the wording, and your backend fills the slots (slot_values) at creation with what it sees of the
device — someone who forwards the QR cannot forge those values, because they do not own your
backend. For example, a template:
service, device and location are slots your backend fills. The server fills {app} of a
confirmation itself, with the transaction’s application identifier (client_id) rather than the
client’s DisplayName — so a human-readable
application name reaches the wording only through a slot of your own. The example is synthetic: these slot names and this wording are not in
the shipped configuration. How to declare the contract and the template, and the full request and response, are in
Server-to-server confirmation API.
Two bots: handing the user over to your bot
The sign-in is handled by the bot Veriqa runs, while your own bot runs the business scenarios. After the sign-in the first bot shows a link button into the second, and your bot has to learn who tapped it. The address of that button can carry a link handle — a single-use value your server exchanges for the identity of the user who has just signed in. Two bots is one of several ways to use a bot you already run together with Veriqa — the comparison is in You already have a bot.-
Put
{handle}into the address of the button —RedirectUrlor theActionof a link item of the menu (Menu and Redirect):The example is synthetic:shop_botis not a real bot. The handle is 22 characters[0-9A-Za-z]; the rest of the platform’s budget for the start parameter (64 characters in Telegram) is yours. -
Your bot receives the handle when the user taps the button — in Telegram, as
/start <handle>. -
Your server exchanges it, with the client credentials token of the application the user signed
in to (how to get the token; the client needs
AllowClientCredentials):channel_typeandchannel_user_idare returned only to a client whoseAllowedScopescontainchannel; otherwise the response carriessubalone.subis the one this application receives in its tokens. -
Store the link on your side — “the user of my bot ↔
sub”. Veriqa keeps the handle only until it is exchanged or expires, and keeps no link.
- A handle is exchanged once, and only by the application the sign-in belongs to. Another
application gets
404, and the handle stays valid for its own. - It is short-lived:
Veriqa:TransactionEngine:CompletedRetentionSeconds(600 seconds by default) from the moment the button is sent. The button stays in the chat: tapped later, it brings your bot a handle for which the endpoint returns404— ask the user to sign in again. 404is one response for a missing or emptyhandle, an unknown, expired or already exchanged one, and another application’s — the reasons are not told apart on purpose. A body that is not JSON is400, aContent-Typeother than JSON is415; no token is401; a token issued to a user is403. A500means the identity could not be read — that handle is spent too.- The handle works before the browser finishes the sign-in — and when the browser tab was closed and never does. The identity is confirmed by the user in the channel by then.
- Several replicas need a shared store. Without the Redis channel stores a handle is found only by the replica that issued it.
- A button without a handle is fine where you do not need it — in Telegram your bot sees the same
user identifier the sign-in returned in
channel_user_id, so an account already linked is recognized without an exchange. - The endpoint exists for sign-ins only; a confirmation your server creates is followed by no button.
When the sign-in is pointless without your bot
The sign-in completes when the user confirms it in the channel: the browser receives thecode and
returns to your application. The button into your bot arrives afterwards and does not affect the
sign-in — a user who never taps it is signed in all the same, while your bot does not know them
yet and cannot message them.
If users must not go any further without moving into your bot, make the move mandatory in your
application:
- After the sign-in, check whether a link exists for the
subyou received. It appears when your server has exchanged the handle (step 3 above); where your bot sees the same identifier the sign-in returned, it appears when the bot has received the first message from that user. - No link — show a “one step left” screen with a link into your bot, and do not let the user go any further. The user is already signed in with you, so you can issue the link on that screen yourself: a single-use token of your own in the start parameter, which your bot matches to the session. It does not need the handle from Veriqa — that one exists only in the button in the chat and is short-lived.
- Grant access once the link has appeared — on an event from your bot or by polling from the page.
One sign-in, two channels
A sensitive operation can require confirmation in two different trusted channels.