Skip to main content
Your backend can ask a person to confirm an action — a payment, a device, a data transfer — in a trusted channel, with no sign-in and no browser redirect. This page is the full contract: the configuration that makes it possible, the requests and the answers. A runnable example is samples/dotnet/inproc/confirmation in the repository: one application that is both the Veriqa issuer and the backend that calls it. Two more are built the same way: samples/dotnet/inproc/step-up confirms a destructive action and checks that the account owner confirmed it (expected_identities), and samples/dotnet/inproc/agent-approval makes an AI agent’s tool call wait for a person’s approval.

The flow

  1. Your backend gets a token with the Client Credentials grant.
  2. It creates the transaction: POST /api/transaction/confirmation, naming a declared action type and filling the slots of its message.
  3. It shows the user channel_entry from the answer — the QR image (qr) on a screen, or the link (url) on the same device.
  4. The user opens the channel, reads the wording and confirms or declines.
  5. Your backend polls GET /api/transaction/{id}/result until the outcome is no longer pending.
  6. Optionally, it exchanges a confirmed transaction for the id_token of the person who confirmed.

1. Configure the client

Everything lives in the client entry of the application that calls the API:

Where the action type is declared

The action type is the message kind, and it is declared in the client entry: Veriqa:OpenIddict:Clients:[n]:MessageTemplates:{action type}:Contract and …:Templates. There is no ByType or ByAction group to add for it.
The host section Veriqa:MessageTemplates does not declare an action type. That section is the Core level, the place of the product’s own messages, and an action type visible only there is refused with action_type_unknown: an application must not be able to ask a person to confirm a text the product ships for its own purposes. The host starts without complaint either way — the refusal comes from the API.
A Tenant level is also read, when your host registers a reader for it; none is registered out of the box.

What the user reads when it ends

Two host-level settings word the end of the transaction, and the runnable samples above state both:
DisplayIntent says where the receipt of the outcome appears. The product ships ReplacePrompt — the receipt takes the place of the message that asked the question — and a confirmation is the case for the other value: the question carries the wording of the action, so replacing it leaves the conversation with an outcome and no record of what it answered. NewMessage keeps both. It is an intent rather than a promise: Telegram and MAX honour it, WhatsApp always sends a new message. Where the platform lets the buttons of a delivered message be taken away without rewriting its text, NewMessage takes them away as it delivers the receipt: the question keeps its wording as the record of what the outcome answered, and nothing under a settled transaction invites another press. Where the platform has no such operation, a button may survive — pressing again only earns another receipt, because a terminal transaction cannot be replayed. The receipt kinds are the product’s own messages, so they live in the host section rather than in the client entry — the rule about action types above does not reach them. The product ships no wording under ByType:confirmation, so the broad {kind} declaration is the step that answers and rewording it works as written. {app} is filled by the server with the ClientId, as above; the last step of each ladder uses no slot, because no slot of a receipt is guaranteed — not even one marked Guaranteed.

The contract

Contract:Slots is an array. Each slot: The server fills {app} of a confirmation with the client’s ClientId, not its DisplayName. To put a human-readable name into the wording, use a slot of your own.

The templates

Templates is a ladder, fullest wording first. The shown step is the first one whose slots all have values, so the optional payee drops out cleanly when it is not sent. The last step may use only slots that always have a value — a caller slot with Required: true or a server slot with Guaranteed: true. A ladder whose last step leans on an optional slot is left behind whole, and the transaction has no text to show. Messengers show the plain text; a step may also be a structure { "Plain": "…", "Html": "…" } — see Veriqa:MessageTemplates.

2. Get a token

The answer carries access_token, token_type and expires_in; cache the token until shortly before it expires. A token issued to a user is refused on this API with 403. The endpoint is served over https only — over plain HTTP it answers 400 invalid_request, “This server only accepts HTTPS requests”. Behind a reverse proxy that terminates TLS, the proxy must be trusted and forward X-Forwarded-Proto (OS service, step 7).

3. Create the confirmation

The wording itself is never returned, and your slot values are not logged above Debug.

The answer — 201 Created

The identifiers and the link are synthetic. The answer is sent with Cache-Control: no-store.
The entry is a bearer one. Whoever opens it first becomes the confirming person. Show the QR only to the person you are asking, and put the context they need to recognise the request into the wording.

4. Read the outcome

matched_type is the name of the comparable type that matched expected_identities, or null. Only the client that created the transaction may read it: any other transaction — unknown, removed after CompletedRetentionSeconds, or another client’s — answers the same 404 transaction_not_found.

5. Learn who confirmed (optional)

With AllowConfirmationTokenGrant, exchange a confirmed transaction once for the id_token of the confirming person:
The id_token carries sub, and with the channel scope channel_type and channel_user_id. The rules of the exchange — single redemption, invalid_grant for every kind of “no”, what the application learns — are in Linking from your server.

Errors of the creation request

Every refusal is Problem Details (application/problem+json): the code is in title, and detail names no value you sent. Nothing is created on any refusal.

Next steps

Linking from your server

Learn which messenger account confirmed and store it next to your user.

Veriqa:MessageTemplates

Ladders, editions, levels and axes of messages.