Skip to main content
Ваш бэкенд может попросить человека подтвердить действие — платёж, устройство, передачу данных — в доверенном канале, без входа и без редиректа браузера. На этой странице — весь контракт: конфигурация, которая это разрешает, запросы и ответы. Запускаемый пример — samples/dotnet/inproc/confirmation в репозитории: одно приложение, которое одновременно и издатель Veriqa, и бэкенд, который к нему обращается. Ещё два устроены так же: samples/dotnet/inproc/step-up подтверждает разрушительное действие и проверяет, что подтвердил владелец аккаунта (expected_identities), а samples/dotnet/inproc/agent-approval заставляет вызов инструмента ИИ-агента ждать одобрения человека.

Как это идёт

  1. Бэкенд получает токен грантом Client Credentials.
  2. Создаёт транзакцию: POST /api/transaction/confirmation с объявленным action type и значениями слотов его сообщения.
  3. Показывает пользователю channel_entry из ответа — картинку QR (qr) на экране или ссылку (url) на том же устройстве.
  4. Пользователь открывает канал, читает текстовку и подтверждает или отклоняет.
  5. Бэкенд опрашивает GET /api/transaction/{id}/result, пока исход не перестанет быть pending.
  6. По желанию обменивает подтверждённую транзакцию на id_token того, кто подтвердил.

1. Настройте клиента

Всё живёт в записи клиента — приложения, которое вызывает API:

Где объявляется action type

Action type — это и есть вид сообщения (kind), и объявляется он в записи клиента: Veriqa:OpenIddict:Clients:[n]:MessageTemplates:{action type}:Contract и …:Templates. Групп ByType и ByAction для этого добавлять не нужно.
Секция хоста Veriqa:MessageTemplates action type не объявляет. Эта секция — уровень Core, место собственных сообщений продукта, и action type, видимый только там, получает отказ action_type_unknown: приложение не должно иметь возможность попросить человека подтвердить текст, который продукт поставляет для своих нужд. Хост при этом стартует без жалоб — отказ приходит от API.
Читается и уровень Tenant, если ваш хост зарегистрировал для него читателя; из коробки такого нет.

Что пользователь читает по завершении

Конец транзакции описывают две настройки уровня хоста, и рабочие примеры выше выставляют обе:
DisplayIntent задаёт, где появляется квитанция исхода. Продукт везёт ReplacePrompt — квитанция встаёт на место сообщения с вопросом, — и подтверждение как раз тот случай, когда нужно другое значение: вопрос несёт формулировку действия, и подмена оставляет переписку с исходом, но без записи о том, на что он отвечал. NewMessage сохраняет оба сообщения. Это намерение, а не обещание: Telegram и MAX его исполняют, WhatsApp всегда шлёт новое сообщение. Там, где платформа позволяет убрать кнопки у отправленного сообщения, не переписывая его текст, NewMessage снимает их тем же ходом, которым доставляет квитанцию: вопрос сохраняет формулировку как запись о том, на что отвечал исход, и под завершённой транзакцией не остаётся ничего, что приглашало бы нажать ещё раз. Где такой операции у платформы нет, кнопка может уцелеть — повторное нажатие приносит лишь ещё одну квитанцию, потому что терминальную транзакцию не переиграть. Виды квитанций — собственные сообщения продукта, поэтому живут в секции хоста, а не в записи клиента: правило про типы действий выше на них не распространяется. Под ByType:confirmation продукт не везёт ничего, поэтому отвечает широкое объявление {kind} и переформулировка работает как написана. {app} сервер заполняет ClientId, как и выше; последний шаг каждой лестницы не пользуется слотами, потому что ни один слот квитанции не гарантирован — даже помеченный Guaranteed.

Контракт

Contract:Slots — массив. Каждый слот: {app} подтверждения сервер заполняет ClientId клиента, а не его DisplayName. Чтобы в текстовке было читаемое имя, заведите свой слот.

Шаблоны

Templates — лестница, от самой полной текстовки к минимальной. Показывается первая ступень, у которой заполнены все слоты, поэтому необязательный payee аккуратно выпадает, если его не передали. Последняя ступень может опираться только на слоты, у которых значение есть всегда, — слот вызывающей стороны с Required: true или серверный слот с Guaranteed: true. Лестница, чья последняя ступень опирается на необязательный слот, отбрасывается целиком, и транзакции нечего показать. Мессенджеры показывают простой текст; ступень может быть и структурой { "Plain": "…", "Html": "…" } — см. Veriqa:MessageTemplates.

2. Получите токен

В ответе — access_token, token_type и expires_in; кешируйте токен почти до истечения. Токен, выданный пользователю, этот API отклоняет с 403. Эндпоинт отдаётся только по https: по обычному HTTP он отвечает 400 invalid_request, «This server only accepts HTTPS requests». За reverse-proxy, который терминирует TLS, proxy должен быть доверенным и передавать X-Forwarded-Proto (служба ОС, шаг 7).

3. Создайте подтверждение

Сама текстовка не возвращается, а значения слотов не пишутся в лог выше Debug.

Ответ — 201 Created

Идентификаторы и ссылка синтетические. Ответ отдаётся с Cache-Control: no-store.
Вход — на предъявителя. Кто откроет его первым, тот и станет подтверждающим. Показывайте QR только тому, кого спрашиваете, а контекст, по которому человек узнает запрос, кладите в текстовку.

4. Прочитайте исход

matched_type — имя сравнимого типа, совпавшего с expected_identities, или null. Читать результат может только клиент, создавший транзакцию: любая другая — неизвестная, удалённая после CompletedRetentionSeconds или чужая — отвечает одинаково, 404 transaction_not_found.

5. Узнайте, кто подтвердил (необязательно)

С AllowConfirmationTokenGrant подтверждённую транзакцию можно один раз обменять на id_token подтвердившего:
id_token несёт sub, а со scope channel — ещё channel_type и channel_user_id. Правила обмена — однократное погашение, invalid_grant на любое «нет», что узнаёт приложение — в привязке с вашего сервера.

Ошибки запроса создания

Любой отказ — Problem Details (application/problem+json): код в title, а detail не называет ни одного переданного вами значения. При любом отказе ничего не создаётся.

Дальше

Привязка с вашего сервера

Узнайте, какой аккаунт мессенджера подтвердил, и сохраните его рядом с пользователем.

Veriqa:MessageTemplates

Лестницы, редакции, уровни и оси сообщений.