Проверка подлинности чеков - API
Валидатор встраивается в платёжную систему как микросервис и отвечает вердиктом - настоящий чек, поддельный или нужен повторный снимок. Чек он принимает сам: обычно плательщик прикладывает файл напрямую нам - по ссылке, рамкой на вашем сайте или письмом, - и через вашу систему файл не проходит вовсе. Если чек уже у вас, отдайте его одним запросом. Один REST-интерфейс, вердикт с человекочитаемым пояснением, колбэки с подписью.
https://validator.testoporat.com/sandbox - на ней собирается
интеграция до подключения. Путь внедрения по шагам »
Открыть песочницу »
Чем бой отличается от песочницы »
Как это работает
Путей приёма два, и выбираете их вы. Основной - чек приносит плательщик: он прикладывает файл прямо Валидатору, а вы файла не касаетесь вовсе. Второй - чек отдаёте вы одним запросом, когда файл уже лежит у вас. Дальше пути сходятся в одно: та же проверка, те же вердикты, те же колбэки и та же карточка чека.
Путь 1, основной - чек прикладывает плательщик
Ваш сервер файл не принимает, не хранит и не пересылает: вы просите у нас адрес под заявку и показываете его плательщику. Своя форма загрузки и своё место под чужие чеки не нужны.
- Вы заводите заявку - один запрос
POST /v1/upload-linksс теми же полями заявки, что и у обычной подачи. В ответ201и сразу все адреса:upload_url- страница загрузки для плательщика,embed_url- она же рамкой на вашем сайте,email_address- адрес заявки для письма (если почтовый канал включён), а для вас -status_urlиreceipt_url. Адреса известны с этой секунды и больше не меняются. Ссылки загрузки » - Плательщик прикладывает чек нам - открывает выданную страницу
(
GET /u/{token}) и загружает фото, снимок экрана или PDF; та же страница встаёт рамкой в ваш интерфейс, а вместо загрузки чек можно прислать письмом прямо из банка на адрес заявки. Ключ API плательщику не нужен и не выдаётся: пропуск - токен ссылки с ограниченным сроком жизни и лимитом попыток. Как выглядит экран » - Проверка идёт у нас - тот же конвейер, что у подачи по API. Плательщик видит нейтральный экран «проверяем чек»; подробности проверки уходят только вам, по вашему ключу.
- Судьба заявки - одним адресом:
GET /v1/upload-links/{token}.file_received- плательщик приложил чек (по нему останавливайте свой таймер ожидания, не дожидаясь вердикта),checkingиeta_seconds- проверка идёт,job_idиverdict- готово. Колбэки на этом пути те же самые и приходят на тот же ваш адрес. - Вердикт - те же четыре исхода, что и на втором пути. Если чек не прошёл,
плательщик присылает новый по той же ссылке: сколько попыток истрачено и сколько
осталось времени на новый чек, видно полями
attemptsиresend_left_seconds. - Вы сообщаете итог ордера обратной связью - она нужна для сверки качества и разбора спорных случаев.
Когда выбирают этот путь. Плательщик сидит в мессенджере, в чате поддержки или у оператора, и формы для файла у вас нет; вы не хотите принимать и хранить чужие чеки у себя; интеграцию надо собрать за день - с вашей стороны это один запрос и показ ссылки.
Путь 2 - чек отдаёте вы одним запросом
Файл уже у вас: плательщик приложил его в вашей форме или он лежит в вашем хранилище.
- Плательщик приложил чек к заявке в вашей системе - фото, снимок экрана или PDF.
- Вы отдаёте чек Валидатору одним запросом
POST /v1/receipts:validate: файл плюс ожидаемая сумма и реквизит; вместо файла можно прислать временную ссылку на ваше хранилище. Ответ приходит сразу - задача принята, ссылки на статус и файл уже в нём. Подача чека » - Проверка идёт в фоне - выполняется асинхронно, ваша сделка стоит в ожидании,
клиент ничего не перезагружает. Нужен ответ одним запросом - попросите подождать вердикт
прямо в подаче полем
wait_seconds. - Вердикт приходит колбэком (или вы забираете его запросом статуса
GET /v1/receipts/{job_id}): accepted - признаков подделки не найдено; resend_requested - заключения по этому файлу нет, нужен новый чек; rejected - чек не принят. Редкий четвёртый исход -unverified, разбор ниже. - Вы сообщаете итог ордера обратной связью - она нужна для сверки качества и разбора спорных случаев.
Когда выбирают этот путь. Своя форма загрузки уже есть и менять её не хотят; чек
приходит вам не от человека, а другой системой; нужен синхронный фильтр - вердикт прямо в
ответе подачи по wait_seconds; чек надо проверить задним числом, из архива.
Варианты встраивания
Подключение - лесенка из четырёх уровней: можно остановиться на любом и подняться позже. Лесенка про то, сколько работы вы отдаёте нам, а не про очерёдность: путь плательщика (уровень 2) берут и первым шагом, не проходя нулевой и первый. На каждом уровне наша недоступность не останавливает ваши сделки - это свойство конструкции, а не обещание.
share_url.wait_seconds: вердикт приходит прямо в ответе приёма. PDF решается за доли
секунды - основной поток фильтруется синхронно. Не успели или недоступны - вы пропускаете
файл по своему регламенту, как будто нас нет.Файл чека может попасть к нам пятью путями - все они сходятся в одну и ту же проверку, с теми же вердиктами и колбэками:
/u/… или рамка на вашем сайте - основной путь: файла у вас нет и не нужно.
email_address.file_url - байты не гоняются через ваш бэкенд; домен согласуется заранее.
openapi.yaml понимают Swagger, Postman и генераторы клиентов - готовый код
для вашего языка без ручного переписывания примеров.
Интерфейсы: что видят люди
API - не единственная поверхность продукта. Ниже - четыре готовых экрана, которые достаются вам без разработки. Макеты схематичны, данные выдуманы.
Карточка проверки проверен
share_url без ключа API - кладите ссылку
«посмотреть чек» в карточку операции своей админки. Про ссылку »Страница загрузки для плательщика
Приложите чек об оплате по заявке ORDER-1002
фото, снимок экрана или PDF
embed_url). Про ссылки загрузки »Кабинет партнёра
Резервный блок приёма на вашей странице
заявка, сумма, зона файла - как всегда
Облачное хранилище чеков
Проверенный чек не обязан жить у вас: он остаётся в нашем хранилище - зашифрованным, - а вы храните только ссылку. Своё файлохранилище чеков, резервные копии к нему и права доступа строить не нужно.
Как устроено шифрование хранения
- Каждый файл чека шифруется при записи - алгоритм AES-256-GCM, тот же класс защиты, что у банковских систем. Шифруется файл в архиве, файл в рабочей записи задачи и образцы обучающей базы.
- Ключ шифрования живёт отдельно от данных и в резервные копии не попадает намеренно: архив чеков и обучающая база без ключа не читаются. Диск или копия, оказавшиеся в чужих руках, самих чеков не раскрывают.
- Файлы не покидают контур: распознавание - локальная нейросеть внутри сервиса, во внешние нейросети, облачные распознаватели и чужие API чек не уходит.
- Доступ - только по вашему ключу API или по ссылке: чужая задача для вашего
ключа неотличима от несуществующей, а
share_urlнепрозрачен и подделке не поддаётся. - Честная граница: шифруются файлы. Служебная запись о проверке - вердикт и сверенные значения, без файла - живёт в журнале решений открытой: без неё нельзя разобрать спорный случай.
| Что у вас есть | Как работает |
|---|---|
| Шифрование хранения | включено всегда, у всех партнёров, отключить нельзя - это свойство хранилища, а не настройка. |
| Файл по ключу API | receipt_url - машинный доступ к байтам чека,
в любой момент срока хранения. Подробнее » |
| Карточка без ключа | share_url - человекочитаемая страница
«статус + заключение + файл» для вашей админки; живёт ровно столько, сколько чек лежит
у нас. Ссылка равноценна документу - храните как секрет. |
| Срок хранения - ваша настройка | от «не хранить вовсе» до бессрочно, меняется в кабинете. Отдельная настройка - снимать чек с хранения сразу после того, как вы забрали файл по ключу. |
| Досрочное удаление | DELETE /v1/receipts/{job_id}/file - забрали
файл к себе или удаляете по требованию плательщика. |
| Чистка служебных полей | по настройке исходящий файл отдаётся без метаданных (EXIF, GPS, служебные поля PDF) - данные плательщиков не едут дальше необходимого. |
| Сколько занято | кабинет показывает число чеков на хранении, объём и действующий срок - видно, чем вы у нас пользуетесь. |
Границы честно: хранилище - для чеков, прошедших через проверку, произвольные файлы оно не принимает. Что остаётся после нулевого срока и зачем - в разделе «Безопасность и данные».
Путь внедрения - от ключа до боевого режима
Семь шагов по порядку. У каждого есть проверяемый результат: пока он не получен, следующий шаг делать рано. Ссылка в шаге ведёт в раздел, где то же самое разобрано подробно и показано примером - справочник ниже читается уже по надобности, а не подряд.
- Ключ и адреса
Подключение оформляем мы: выдаём ключ API, боевой адрес и интеграционный пакет (резервный блок для вашей страницы, закрытый справочник кодов). Тем же разговором согласуются четыре вещи, которые потом меняются дольше кода: список ваших исходящих IP, срок хранения чеков, домен вашего хранилища (если чек будете отдавать ссылкой) и секрет подписи колбэков. Сам адрес колбэков вы задаёте себе в кабинете партнёра, без нас. Ключ передаётся заголовком, храните его в секретах.
Готово, когда: ключ лежит в секретах вашего стенда, боевой адрес записан в настройки отдельным значением - не в коде. Авторизация » - Песочница вместо ожидания
Ждать ключ не нужно: интеграция собирается на песочницеhttps://validator.testoporat.com/sandbox- те же адреса и те же имена полей, ключом служит любая строка от 8 символов. Настоящие чеки туда не загружайте, она открыта.
Готово, когда: ваш код ходит в песочницу и получает от неё ответы. Песочница » - Первый чек
Один запросPOST /v1/receipts:validate: файл плюс поля заявки. Ответ приходит сразу -202, задача создана, в телеjob_idи адреса статуса, файла и карточки. Всегда передавайтеrequest_id: он включает защиту от дублей, и повтор запроса по обрыву сети не заводит вторую задачу. Если у вас путь плательщика, первый чек идёт так же, только заявка заводится запросомPOST /v1/upload-links, а файл прикладываете вы сами - открыв выданныйupload_urlвместо плательщика.
Готово, когда: в ответе естьjob_id, а повторная отправка того же запроса возвращает его же с пометкой"repeated": true. Три запроса подряд » · Подача файлом » · Ссылки загрузки » - Вердикт
Проверка идёт в фоне, результат забирается поstatus_url: пока"status":"processing"- ждите, при"status":"done"разбирайте вердикт. Вердиктов четыре, и обработаны должны быть все четыре, а у accepted ещё иstatus_color: жёлтый значит «чек подлинный, но данные разошлись с заявкой», а не «всё сошлось». На пути плательщика тот же вердикт забирается по адресу заявки -GET /v1/upload-links/{token}, отдельного опроса задачи не нужно.
Готово, когда: в вашей системе есть ветка на каждый вердикт и на жёлтый цвет. Получение результата » · Что делать с каждым вердиктом » - Колбэки вместо опроса
Опрос статуса нужен только на время сборки. В бою вердикт приходит сам: адрес регистрируется в кабинете партнёра, секрет подписи выдаётся при подключении. Подпись проверяйте всегда и по сырому телу запроса - непроверенная подпись означает, что вердикт вам может прислать кто угодно.
Готово, когда: событие с чужой подписью ваш приёмник отвергает, повторная доставка того же события ничего не ломает, ответ уходит быстрее секунды. Колбэки » · Порядок обработки » - Обратная связь по итогу ордера
Чем закончилась сделка, знаете только вы. Один запрос после вердикта - и спорный случай потом можно разобрать по фактам, а не по памяти.
Готово, когда: итог ордера уходит нам автоматически, а не руками оператора. Обратная связь » - Переход на боевой контур
Меняются ровно две вещи - адрес и ключ. Но боевой контур строже песочницы: он требует обязательные поля заявки и по-настоящему проверяет подлинность. Сверьтесь с таблицей различий и пройдите чек-лист до выката, а не после.
Готово, когда: адрес и ключ песочницы в боевой конфигурации не встречаются ни разу, чек-лист пройден целиком. Различия песочницы и боя » · Чек-лист внедрения »
Быстрый старт - три запроса в песочницу
Подать чек в песочницу, забрать вердикт, отправить обратную связь. Это второй путь - когда файл уже у вас; первый чек по пути плательщика начинается с создания ссылки загрузки, и песочница умеет его тоже.
# 1. Подать чек (файлом)
curl -s https://validator.testoporat.com/sandbox/v1/receipts:validate \
-H "Authorization: Bearer sandbox-demo-key" \
-F "file=@check.jpg" \
-F "request_id=ORDER-1001" \
-F "expected_amount=1500.00" \
-F "requisite=79001234567" \
-F "initiated_at=2026-08-23T10:15:00"
# Ответ: {"job_id":"...","status":"pending","eta_seconds":3.5,"status_url":"...",...}
# 2. Забрать результат (через пару секунд)
curl -s https://validator.testoporat.com/sandbox/v1/receipts/<job_id> \
-H "Authorization: Bearer sandbox-demo-key"
# Ответ: {"status":"done","verdict":"accepted","status_color":"green",...}
# 3. Сообщить итог ордера
curl -s https://validator.testoporat.com/sandbox/v1/receipts/<job_id>/feedback \
-H "Authorization: Bearer sandbox-demo-key" \
-H "Content-Type: application/json" \
-d '{"order_outcome":"approved","comment":"деньги пришли"}'
Авторизация
Ключ API выдаётся на подключение (у нас это называется «партнёр»). Передавайте его любым
из двух заголовков - подойдёт любой, а если пришли оба, читается X-API-Key:
Authorization: Bearer ВАШ_КЛЮЧ
# или
X-API-Key: ВАШ_КЛЮЧ
- Без ключа или с неверным ключом -
401. - За подключением можно закрепить список разрешённых IP - запрос с чужого адреса
получит
403. - Перевыпуск ключа - по вашему запросу: ключ меняется на нашей стороне, старый перестаёт работать сразу. В кабинете партнёра вы самостоятельно меняете секрет подписи колбэков.
Подача чека
Принимает чек и заявку, отвечает сразу (202): задача создана, проверка пошла в фоне.
Тот же 202 приходит и при отказе приёма - тогда в теле "status": "done",
"verdict": "rejected" и машинный код причины. Решение принимайте по телу ответа,
а не по коду HTTP. Два равноценных способа передать файл.
Способ 1 - файл телом запроса (multipart/form-data)
Основной способ по контракту. Файл кладётся в поле file, остальные поля - строками рядом.
curl -s https://validator.testoporat.com/sandbox/v1/receipts:validate \
-H "Authorization: Bearer sandbox-demo-key" \
-F "file=@check.jpg" \
-F "request_id=ORDER-1001" \
-F "expected_amount=1500.00" \
-F "requisite=79001234567" \
-F "initiated_at=2026-08-23T10:15:00" \
-F "partner_meta={\"order\":1001}"
Способ 2 - временная ссылка (application/json)
Если чек уже лежит в вашем хранилище - передайте временную ссылку, сервис заберёт файл сам.
curl -s https://validator.testoporat.com/sandbox/v1/receipts:validate \
-H "Authorization: Bearer sandbox-demo-key" \
-H "Content-Type: application/json" \
-d '{
"file_url": "https://storage.example.com/tmp/check.jpg?sig=...",
"request_id": "ORDER-1001",
"expected_amount": "1500.00",
"requisite": "79001234567",
"initiated_at": "2026-08-23T10:15:00"
}'
https, домен - из согласованного
списка вашего хранилища, адреса внутренних сетей отклоняются, переходы по редиректам не
выполняются. Пока список доменов не согласован при подключении, приём по ссылке выключен и
отвечает кодом FETCH_DISABLED. Коды отказов - в таблице
кодов приёма. Песочница по ссылке файл не скачивает, а строит ответ по имени файла в ней.
Поля заявки
| Поле | Обязательное | Что это |
|---|---|---|
file / file_url | да | сам чек: PDF, JPEG, PNG, WebP, GIF или HEIC (штатный снимок айфона - переводим в JPEG сами). Список можно сузить под себя в кабинете («Настройки подключения» → «Какие файлы принимаем»): тогда лишний формат отклоняется кодом FORMAT_NOT_ALLOWED на всех путях приёма |
expected_amount | да, в боевом | ожидаемая сумма платежа: десятичная строка, копейки через точку или запятую - "1500.00" и "1500,00" равноценны |
requisite | да, в боевом | реквизит получателя, который выдали плательщику: карта, счёт или телефон. Берётся строкой как есть - маску и пробелы приводить не надо |
initiated_at | да, в боевом | когда заявка создана, ISO 8601 (2026-08-23T10:15:00). Время без смещения считается локальным временем сервиса, со смещением - приводится к нему |
request_id | настоятельно | ID заявки в вашей системе; едет во все ответы и колбэки, включает защиту от дублей |
wait_seconds | нет | подождать вердикт прямо в ответе подачи: от 0 до 25 секунд, большее срезается до 25. Не успели - обычный pending, вердикт забирается по status_url или приходит колбэком |
currency | нет | валюта заявки |
bank | нет | банк, если знаете его заранее - повышает точность |
file_name | нет | имя файла для журналов, до 80 символов; без него подставляется request_id |
partner_meta | нет | ваше сквозное поле до 4096 символов: вернётся как есть в статусе и колбэках - привязывайте наш ответ к своим сущностям без таблицы соответствий. Длиннее 4096 символов обрезается; у себя считайте это значение недоверенным вводом и экранируйте при выводе |
expected_amount, requisite и initiated_at
обязательны все три: без любого из них подача отклоняется на приёме с кодом
CONTEXT_FIELDS_MISSING. Песочница обязательные поля не проверяет, поэтому
интеграция, собранная без них, работает в песочнице и падает в бою.
Ответ подачи
{
"request_id": "ORDER-1001",
"job_id": "3f8a1c2e-...",
"status": "pending",
"comment": "Чек принят, идёт проверка",
"eta_seconds": 3.5,
"status_url": "https://.../v1/receipts/3f8a1c2e-...",
"receipt_url": "https://.../v1/receipts/3f8a1c2e-.../file",
"share_url": "https://.../r/<токен>"
}
eta_seconds- оценка очереди, через сколько ждать вердикт: её можно показать своему оператору вместо «когда-нибудь»;status_url- где забирать вердикт;receipt_url- файл чека (по ключу API): пока идёт проверка отвечает202со статусомprocessing, потом отдаёт файл;share_url- ссылка на карточку проверки без ключа API;partner_metaвозвращается тем же ответом, если вы его прислали.
UNSUPPORTED_FORMAT, FILE_TOO_LARGE, IMAGE_TOO_LARGE и
CONTENT_TYPE_MISMATCH приходят не ответом подачи, а вердиктом задачи - по
status_url или колбэком. В ответе подачи такие отказы видны только у подачи
ссылкой: там файл забирается сразу. Сверка заявленного типа мягкая - расхождение внутри
картинок прощается, CONTENT_TYPE_MISMATCH бывает на границе «картинка - PDF».
Получение результата
Пока проверка идёт - "status":"processing". Когда закончена -
"status":"done" и полный вердикт. Третье состояние - "status":"not_found":
такой задачи нет, она чужая или ей больше суток (результат доступен по status_url
сутки с момента подачи, сам файл чека живёт столько, сколько попросил партнёр). Ответ всегда
200 - разбирайте по полю status:
{
"job_id": "3f8a1c2e-...",
"request_id": "ORDER-1001",
"status": "done",
"verdict": "accepted",
"reason_code": null,
"status_color": "green",
"comment": "Признаков подделки не найдено",
"points": [],
"mismatches": [],
"extracted": {
"amount": "1500.00",
"payment_time": "2026-08-23T10:17:41",
"bank": "...",
"transfer_type": "..."
},
"file_available": true,
"file_cropped": false,
"partner_meta": "{\"order\":1001}"
}
Рядом едут служебные поля (отпечаток файла, длительность проверки и другие) - их состав
может меняться, опирайтесь на перечисленные ниже. Блок extracted приходит только
тогда, когда из чека удалось что-то извлечь.
| Поле | Что это |
|---|---|
verdict | accepted | resend_requested | rejected | unverified - разбор в следующем разделе |
reason_code | машинный код главной причины (для resend_requested, rejected и unverified), список - ниже |
status_color | green | yellow | red | resend | gray - готовый цвет для интерфейса оператора: yellow - данные чека разошлись с заявкой, resend - нужен новый чек, gray - заключения нет |
comment | человекочитаемое пояснение - можно показывать оператору как есть |
mismatches | расхождения с заявкой человеческим языком: непустой список при status_color: yellow - единственное место, где видно, что именно не сошлось |
points | пояснения к решению по пунктам для показа оператору, серьёзное сверху. Состав не фиксирован; у чистого accepted список пустой - это не перечень выполненных проверок |
authenticity | служебная величина; для rejected и resend_requested приходит null. Решение принимается только по verdict и status_color |
file_available | отдадим ли файл чека по receipt_url: при resend_requested - нет |
file_cropped | чек вырезан из кадра - вам уходит очищенный файл |
extracted | что распознали: сумма, время оплаты, банк, вид перевода |
partner_meta | ваше сквозное поле - как прислали, так и вернулось |
status: "done", verdict: "rejected", reason_code,
stage: "intake" и человекочитаемый detail. Полей comment,
status_color и points в этом случае нет.
Вердикты и коды причин
| Вердикт | Что делать вашей системе |
|---|---|
| accepted | признаков подделки не найдено. Дальше смотрите status_color: green - чек сошёлся с заявкой, проводите по вашему регламенту; yellow - данные чека расходятся с заявкой (что именно - в mismatches), решение за вами. При yellow поле reason_code пустое |
| resend_requested | заключения по этому файлу нет - нужен новый чек. Покажите плательщику просьбу прислать чек заново: comment уже сформулирован по-человечески |
| rejected | чек не принят - дальше по вашему регламенту работы с подозрительными операциями. Сюда же попадает отказ приёма файла или заявки, тогда в reason_code код из таблицы приёма. Расхождение с заявкой сюда НЕ попадает - см. accepted с жёлтым цветом |
unverified | проверку не удалось довести до конца по нашей технической причине: чек передан без заключения (status_color: gray), решение принимает ваш регламент |
Что делать с каждым вердиктом
Та же четвёрка, но со стороны вашего процесса: что происходит со сделкой, что видит плательщик и есть ли у вас файл чека. Столбец «что видит плательщик» - про ваш экран; на пути плательщика просьбу прислать чек заново он уже увидел на нашей странице, и ваше дело - дождаться нового файла. Обратная связь по итогу ордера отправляется при любом вердикте - это единственное действие, одинаковое для всех.
| Вердикт | Сделка | Что видит плательщик | Файл чека |
|---|---|---|---|
acceptedstatus_color: green |
идёт дальше по вашему обычному регламенту | ничего особенного: чек принят | есть - по receipt_url и share_url |
acceptedstatus_color: yellow |
решение за вами: подделки нет, но данные чека разошлись с заявкой. Что именно разошлось - в mismatches |
по вашему регламенту: чаще всего это ручная проверка оператором, а не отказ клиенту | есть |
| resend_requested | стоит в ожидании нового файла, отказом не считается | просьбу прислать чек заново. Текст готов - поле comment написано человеческим языком, его можно показывать как есть |
нет: 409 с вердиктом и кодом причины |
| rejected | дальше по вашему регламенту работы с подозрительными операциями | решаете вы. Машинный код причины подделки не расшифровывается открыто, показывать его плательщику незачем | нет: 409. Отказ приёма файла (коды приёма) - тот же вердикт, но причина техническая, и правится она на вашей стороне |
unverifiedstatus_color: gray |
не должна стоять: заключения нет по нашей технической причине, а не по вине чека | ничего: для плательщика это обычная заявка | обычно есть - смотрите file_available |
accepted считают зелёным - и пропускают расхождение с заявкой.
resend_requested показывают плательщику как отказ - и теряют честную сделку.
Ветку unverified не пишут вовсе - и сделка встаёт из-за нашего сбоя, хотя
именно этого конструкция и не допускает.
Коды причин: приём файла
Отказ приёма приходит обычным ответом подачи со status: "done", вердиктом
rejected и машинным кодом.
| reason_code | Что случилось | Что делать |
|---|---|---|
EMPTY_FILE | файл не приложен или пуст | проверьте поле file в запросе |
FILE_TOO_LARGE | файл больше лимита подключения | попросите чек из приложения банка, а не фото экрана в полном разрешении |
IMAGE_TOO_LARGE | изображение больше допустимого размера в пикселях | то же |
UNSUPPORTED_FORMAT | формат файла не поддерживается либо тело запроса не того типа | шлите multipart/form-data или application/json и файл в PDF, JPEG, PNG, WebP, GIF или HEIC |
FORMAT_NOT_ALLOWED | формат сервисом поддерживается, но ваша площадка его не принимает - так выбрано в кабинете («Настройки подключения» → «Какие файлы принимаем») | причина словами приходит в detail - её можно показать плательщику; список форматов меняется в кабинете |
CONTENT_TYPE_MISMATCH | заявленный тип не совпал с содержимым файла | не подставляйте тип вручную - берите его у источника файла |
CONTEXT_FIELDS_MISSING | в заявке нет expected_amount, requisite или initiated_at | в боевом контуре эти поля обязательны |
CONTEXT_FIELDS_INVALID | сумма или дата заявки не разбираются | сумма - десятичная строка, дата - ISO 8601 |
URL_INVALID | в JSON-подаче нет file_url или в нём не разобрать хост | проверьте ссылку |
URL_SCHEME_NOT_ALLOWED | ссылка не по https | временные ссылки принимаются только по https |
DOMAIN_NOT_ALLOWED | домен ссылки не из согласованного списка | согласуйте домен хранилища при подключении |
DNS_RESOLUTION_FAILED | домен ссылки не резолвится | проверьте доступность хранилища снаружи |
URL_RESOLVES_TO_INTERNAL_IP | ссылка ведёт во внутреннюю сеть - загрузка запрещена | давайте ссылку на публичное хранилище |
FETCH_DISABLED | приём по ссылке не включён для вашего подключения | подавайте файл телом запроса или попросите включить |
FETCH_FAILED | ваше хранилище не отдало файл: ошибка, обрыв или таймаут | повторите подачу, когда хранилище ответит |
Коды причин: нужен новый чек (resend_requested)
Файл не удалось прочитать или чек из него не выделить. Поле comment уже
сформулировано человеческим языком - его можно показывать как есть.
| reason_code | Что случилось | Что делать |
|---|---|---|
NOT_A_RECEIPT | файл читается, но это не чек | попросите прислать именно чек об оплате |
LOW_EXTRACTION_CONFIDENCE | чек не читается | попросите файл или снимок экрана из приложения банка |
AMOUNT_NOT_FOUND | сумма платежа в чеке не распозналась | то же |
FOREIGN_BACKGROUND | в кадре снята обстановка вокруг чека, выделить сам чек не удалось | чек с посторонней обстановкой мы не передаём - попросите чек из приложения банка |
FOREIGN_INTERFACE_VISIBLE | в кадре видно постороннее приложение или сервис | нужен чистый чек, без посторонних элементов на снимке |
Коды причин: наши технические обстоятельства
Сюда попадают случаи, когда проверку не удалось довести до конца. Сделка из-за этого стоять
не должна: часть таких чеков доставляется по ссылкам и без заключения - смотрите поле
file_available.
| reason_code | Что случилось | Что делать |
|---|---|---|
QUEUE_FULL | сервис не взял чек в проверку, ответ 503 сразу | повторите подачу позже, заголовок Retry-After подскажет когда |
PIPELINE_ERROR | проверка не завершилась из-за нашего сбоя | подайте чек ещё раз |
CHECK_UNAVAILABLE | заключения нет, но чек передан: вердикт unverified, цвет gray | решайте по своему регламенту, файл доступен по ссылкам |
CHECK_STALLED | проверка затянулась дольше допустимого и была закрыта | то же |
CHECK_INTERRUPTED | перезапуск сервиса оборвал незавершённую проверку | то же |
rejected по результату анализа подлинности машинный код причины
партнёру приходит, но его расшифровка не публикуется: открытый список того, что именно мы
проверяем в чеке, был бы инструкцией для изготовителей подделок. Ориентируйтесь на
verdict и человекочитаемый comment; полный справочник входит в пакет
подключения под соглашением.
Повторные подачи - защита от дублей
Сетевые ретраи безопасны: повтор той же подачи не создаёт новую задачу - вернётся та же
job_id с пометкой "repeated": true и тем же 202. Пока
проверка идёт, ответ повтора приходит со "status":"pending" и без вердикта; когда
готова - со "status":"done", verdict и reason_code, а
полный результат забирается по status_url. Память о поданной заявке держится
сутки, как и сам результат. Без request_id защиты от дублей нет.
# повторная отправка того же запроса по обрыву сети - безопасна
{"request_id":"ORDER-1001","job_id":"3f8a1c2e-...","status":"done",
"repeated":true,"verdict":"accepted",...}
Файл чека и ссылки
Отдаёт файл чека по ключу API. Пока проверка не закончена - 202 и JSON со
статусом processing. Если проверка затягивается, файл может быть отдан раньше
заключения: такой ответ помечен заголовком X-Validator-Verdict: pending, вердикт
придёт отдельно. Когда файла не будет по построению (нужен новый чек, отказ) - 409
с вердиктом и кодом причины; чужая или неизвестная задача - 404.
Срок хранения файла - настройка партнёра: от «не хранить вовсе» до бессрочно. По той же
настройке чек может сниматься с хранения сразу после того, как вы забрали его по ключу API -
тогда повторный запрос того же файла ответит 409 или 404. Открытие
share_url выдачей партнёру не считается и файл не снимает.
Снять чек с хранения самим: вы забрали файл к себе или обязаны удалить его по требованию
плательщика. Ответ - {"job_id": "...", "stored": false, "deleted": true}.
Ссылка без ключа - share_url из ответа подачи (вида
/r/<токен>): в браузере открывается как карточка проверки - статус,
заключение и сам чек, удобно для карточки операции в вашей админке; автоматизации по тому же
адресу приходит файл. Токен непрозрачен и подделке не поддаётся, но он и есть весь пропуск:
ссылка равноценна самому документу - храните её как секрет и не пересылайте плательщику.
Живёт ровно столько, сколько чек лежит у нас на хранении.
Колбэки - вердикт сам приходит к вам
Вместо опроса статуса подключите колбэки: сервис сам пришлёт события на ваш адрес.
Адрес колбэков вы задаёте в кабинете партнёра - принимается только https и только
публичный адрес, адреса внутренних сетей отклоняются. Секрет подписи сервис заводит вместе с
адресом и выдаёт вам при подключении: в кабинете видно лишь его начало и кнопка смены, а новый
секрет вступает в силу сразу - обновите его у себя тем же ходом.
| Событие | Когда приходит |
|---|---|
receipt.accepted | файл принят, проверка пошла - переводите сделку в ожидание |
receipt.verdict | проверка закончена: verdict, status_color, reason_code, comment, points, file_available. Это выжимка - расхождения с заявкой и распознанные значения забираются по status_url |
receipt.delayed | проверка затянулась - сделка не должна стоять из-за нашей очереди. Доступен ли уже сам чек, видно по полю file_available в теле события; вердикт в любом случае придёт отдельным receipt.verdict |
# тело события: имя дублируется полем event и заголовком X-Validator-Event
{
"event": "receipt.verdict",
"job_id": "3f8a1c2e-...",
"request_id": "ORDER-1001",
"status": "done",
"verdict": "accepted",
"status_color": "green",
"reason_code": null,
"comment": "Признаков подделки не найдено",
"points": [],
"file_available": true,
"status_url": "https://.../v1/receipts/3f8a1c2e-...",
"receipt_url": "https://.../v1/receipts/3f8a1c2e-.../file",
"share_url": "https://.../r/<токен>"
}
# у receipt.accepted вместо вердикта - "status": "pending" и comment;
# partner_meta едет в каждом событии, если вы его прислали
Подпись - проверять обязательно
Каждый колбэк подписан: заголовок X-Validator-Signature содержит HMAC-SHA256
от тела запроса, ключ - ваш секрет колбэков. Непроверенная подпись означает, что вердикт вам
может прислать кто угодно.
# Python: проверка подписи
import hmac, hashlib
def подпись_верна(тело: bytes, заголовок: str, секрет: str) -> bool:
ожидаемая = hmac.new(секрет.encode(), тело, hashlib.sha256).hexdigest()
return hmac.compare_digest(ожидаемая, заголовок)
Подписывается сырое тело запроса в том виде, в каком оно пришло: считайте подпись ДО разбора JSON - пересобранное тело даёт другие байты, и подпись не сойдётся. Подпись - шестнадцатеричная строка в нижнем регистре; событие без заголовка подписи считайте чужим.
Доставка
Доставка «как минимум один раз», в два круга. Сразу - три попытки с паузами в секунды: это переживает моргание сети. Если приёмник не ответил, событие ложится в очередь досылки и повторяется с растущими паузами - через полминуты, две минуты, десять, полчаса, два часа, шесть, двенадцать, - пока не кончится окно доставки в сутки. Выкат новой версии вашего приёмника или перезагрузка сервера событие больше не теряют: поднявшись, вы получите всё накопленное. Очередь живёт в базе, поэтому переживает и наш перезапуск.
- повторы приходят с тем же
X-Validator-Event-Id- заголовок не меняется между попытками и досылками. Обрабатывайте события идемпотентно: сверяйте этот идентификатор (или паруjob_id+event) и не откатывайте уже принятый вердикт; - окно кончилось - мы перестаём стучаться и помечаем событие просроченным у себя;
вердикт в любой момент забирается по
status_url, он никуда не девается; - успехом считается только ответ
2xx: отвечайте быстро, тяжёлую обработку уводите в фон; - колбэков не будет вовсе, если подача отклонена на приёме или сервис ответил
503- ответ приходит сразу в самом запросе; повторная подача той же заявки ("repeated": true) событий заново не шлёт; - при подаче с
wait_secondsвердикт возвращается прямо в ответе, но колбэки всё равно придут - будьте готовы получить вердикт дважды.
Приёмник колбэка - порядок действий
Порядок именно такой: каждый следующий шаг опирается на предыдущий.
- Прочитать сырое тело запроса байтами и проверить по нему подпись из
X-Validator-Signature. До разбора JSON: пересобранное тело даёт другие байты, и подпись не сойдётся. Подпись не сошлась или заголовка нет - событие чужое, дальше не идём. - Посмотреть
X-Validator-Event-Id: это событие уже обрабатывали - ответить2xxи выйти. Доставка «как минимум один раз», повторы приходят с тем же идентификатором, и защита от повтора - на вашей стороне. - Разобрать тело:
eventговорит, что случилось,job_idиrequest_id- по какой заявке,partner_metaвернётся тем же, что вы прислали. - Записать событие у себя и сразу ответить
2xx. Тяжёлую работу - в фон: успехом считается только быстрый ответ, медленный приёмник ловит повторные доставки на ровном месте. - Уже в фоне применить вердикт к сделке. Принятый вердикт назад не откатывать: более позднее событие того же типа - это повтор, а не новое решение.
Что делать, если колбэк не пришёл вовсе: вердикт никуда не девается и в любой момент
забирается по status_url. Опрос статуса - штатный запасной путь, а не поломка.
Ссылки загрузки - чек без вашей формы
Если вам неоткуда взять файл (клиент в мессенджере, у оператора нет формы) - создайте
ссылку загрузки: плательщик открывает её и прикладывает чек прямо нам, дальше всё идёт обычным
путём - с вердиктом, статусом и колбэками. Поля заявки те же и так же обязательны;
ttl_minutes задаёт срок жизни ссылки - по умолчанию час, не больше суток.
curl -s https://validator.testoporat.com/sandbox/v1/upload-links \
-H "Authorization: Bearer sandbox-demo-key" \
-H "Content-Type: application/json" \
-d '{
"request_id": "ORDER-1002",
"expected_amount": "900.00",
"requisite": "79007654321",
"initiated_at": "2026-08-23T12:00:00",
"ttl_minutes": 60
}'
# Ответ 201:
{
"token": "kZ2r...", # непрозрачная строка, на формат не опирайтесь
"upload_url": "https://.../u/kZ2r...", # страница загрузки для плательщика
"embed_url": "https://.../u/kZ2r...?embed=1", # та же страница в iframe на вашем сайте
"expires_at": "2026-08-23T13:00:00", # до этого момента ссылка ждёт чек
"max_attempts": 3, # попыток загрузки на одну ссылку
"email_address": "order-1002.a1b2c3@...", # чек можно прислать письмом; null - канал выключен
"status_url": "https://.../v1/upload-links/kZ2r...",
"receipt_url": "https://.../v1/upload-links/kZ2r.../file"
}
Оба партнёрских адреса известны сразу, ещё до того как плательщик приложит чек: интеграция
сводится к одному адресу на заявку. Попытки загрузки ограничены (max_attempts) -
когда они кончились, выпускайте новую ссылку.
Состояние заявки одним запросом. Поле status: pending - ссылка
живёт, done - проверка закончена, expired - срок вышел, а чек так и
не пришёл, not_found - такой ссылки нет. Факт загрузки - отдельным полем
file_received (и uploaded_at): по нему останавливайте свой таймер
ожидания чека, не дожидаясь вердикта. Рядом едут attempts, expires_at,
job_id, verdict, checking, eta_seconds,
resend_deadline и resend_left_seconds (окно на повторный чек), а также
status_url, receipt_url и share_url.
Чек по адресу заявки: 202 со статусом waiting, пока плательщик не
приложил файл, 202 processing, пока идёт проверка, 410,
если срок ссылки вышел, а чек так и не пришёл; дальше - как у обычной ссылки на файл чека.
Обратная связь - итог ордера
Сообщите, чем закончился ордер на вашей стороне: это нужно для сверки качества и разбора спорных случаев. Обратная связь отправляется после вердикта - по задаче, о которой у нас есть записанное решение.
{"order_outcome": "approved", "comment": "деньги поступили"}
# order_outcome: approved | declined; comment - до 1000 символов, лишнее обрезаем
# Ответ: {"job_id": "...", "feedback": "recorded", "order_outcome": "approved"}
# 422 - order_outcome не approved и не declined
# 404 - по этой задаче у нас нет записанного решения
Служебные адреса
| Адрес | Что отвечает |
|---|---|
GET /health | {"status":"ok"} - сервис жив; ключ API не нужен |
GET /readiness | {"status":"ready"} - сервис поднят и принимает запросы; ключ API не нужен. Не готов - 503 и detail с причиной: придержите поток и повторите позже (для проверки живости есть /health) |
Интерактивная схема API - все маршруты с полями, телами и кодами ответов; кнопка «Try it out» выполняет запросы в песочнице, а не в бою. Та же схема файлом: openapi.yaml - её понимают Swagger, Postman, Insomnia и генераторы клиентов, так что готовый код для своего языка получается без переписывания примеров руками. Файл собирается из кода при каждой сборке, поэтому не расходится с тем, что сервис делает на самом деле.
Ошибки и статусы HTTP
| HTTP | Когда |
|---|---|
200 | статус задачи и состояние ссылки загрузки: разбирайте по полю status в теле, в том числе not_found |
201 | ссылка загрузки создана |
202 | чек принят и проверка идёт; тот же код у отказа приёма - смотрите тело |
401 | нет ключа или ключ неверен |
403 | доступ запрещён: IP не в списке разрешённых (проверяется раньше ключа) |
404 | файл чека или решение не найдены, в том числе если задача чужая - чужие задачи выглядят как несуществующие. Опрос статуса задачи отвечает иначе: 200 и {"status":"not_found"} |
409 | файла не будет: нужен новый чек, отказ или чек уже снят с хранения. В теле - verdict и reason_code |
410 | срок ссылки загрузки вышел, а чек так и не пришёл |
413 | запрос заведомо больше лимита; файл, превысивший лимит немного, отклоняется обычным ответом подачи с кодом FILE_TOO_LARGE |
422 | тело запроса не по контракту: неверный order_outcome в обратной связи или неразбираемое тело при создании ссылки загрузки |
429 | слишком много обращений к несуществующим ссылкам с одного адреса |
503 | сервис временно не принимает: в теле "status": "busy" и "reason_code": "QUEUE_FULL", в заголовке Retry-After. Повторите запрос позже, соблюдая паузу |
Форм тела ошибки три, разборщик должен знать все:
{"detail": "..."}- у401и403;{"error": "..."}- у422и404обратной связи;- обычный ответ подачи с полями
verdict,reason_code,stageиdetail- у отказов приёма.
Машинные коды причин собраны в разделе «Вердикты и коды причин»: приём файла, нужен новый чек, наши технические обстоятельства.
Песочница
https://validator.testoporat.com/sandbox - макет API для отладки интеграции: те же
адреса и те же имена полей. Подлинность файлов песочница не проверяет - вердикт выбирается по
имени файла, чтобы каждый сценарий был воспроизводим:
| Имя файла содержит | Вердикт | reason_code |
|---|---|---|
fake | rejected | SANDBOX_MARKED_FAKE |
resend | resend_requested | SANDBOX_UNREADABLE |
| что угодно ещё | accepted | null |
- Ключ - любая строка от 8 символов:
Authorization: Bearer sandbox-demo-key; - вердикт «созревает» через ~4 секунды после подачи: сначала
processing, потомdone; - коды
SANDBOX_*существуют только здесь; - данные держатся в памяти около часа и не сохраняются; не загружайте настоящие чеки - песочница для синтетики;
- ответы помечены заголовком
X-Sandbox: true; - колбэки в песочнице включаются полем
callback_urlпрямо в подаче - это удобство отладки, только здесь. В боевом контуре такого поля нет: адрес колбэков задаётся один раз в кабинете партнёра; - боевой контур строже: он требует обязательные поля заявки и умеет
wait_seconds, а песочница - нет. Адрес песочницы и её ключи в боевой конфигурации быть не должны: она открыта, и её подписи ничего не доказывают.
# сценарий «подделка»: подаём файл, вердикт забираем по status_url через ~4 секунды
curl -s https://validator.testoporat.com/sandbox/v1/receipts:validate \
-H "Authorization: Bearer sandbox-demo-key" \
-F "file=@fake-check.jpg;filename=fake-check.jpg" \
-F "request_id=TEST-FAKE-1"
Переход на боевой контур
В коде меняются две строки - адрес и ключ. Всё остальное различие в том, что боевой контур строже, и собранная в песочнице интеграция может об это споткнуться. Таблица собрана как чек-лист: слева то, что стоит проверить до выката, а не после.
| Что | В песочнице | В боевом контуре |
|---|---|---|
| Адрес | https://validator.testoporat.com/sandbox, открыт всем | выдаётся при подключении вместе с ключом; пути маршрутов те же |
| Ключ | любая строка от 8 символов | выданный вам ключ; за подключением может быть закреплён список разрешённых IP, с чужого адреса - 403 |
| Поля заявки | не проверяются | expected_amount, requisite и initiated_at обязательны все три, иначе CONTEXT_FIELDS_MISSING |
| Вердикт | выбирается по имени файла, подлинность не проверяется | настоящая проверка; вердикт зависит от чека, а не от имени файла |
| Коды причин | SANDBOX_* - существуют только там | коды из таблиц этой страницы и закрытые коды признаков подделки |
wait_seconds | не поддерживается | работает: вердикт можно получить прямо в ответе подачи |
| Подача ссылкой | файл не скачивается, ответ строится по имени в ссылке | файл забирается по-настоящему, домен хранилища должен быть в согласованном списке |
| Адрес колбэков | поле callback_url прямо в подаче - удобство отладки | такого поля нет: адрес задаётся один раз в кабинете партнёра |
| Подпись колбэка | считается общим секретом песочницы и потому ничего не доказывает | ваш секрет подписи, выданный при подключении; проверка обязательна |
| Хранение | в памяти около часа, ничего не сохраняется | файл шифруется, срок хранения - ваша настройка вплоть до «не хранить вовсе» |
| Метка ответа | заголовок X-Sandbox: true в каждом ответе | такого заголовка нет |
sandbox, ни ключей
песочницы. Заголовок X-Sandbox годится и как предохранитель: если он пришёл на
боевом ключе, значит запрос ушёл не туда - такую задачу лучше не проводить, а поднять тревогу.
Что сделать в день перехода: переключить адрес и ключ значениями настроек (без выката кода),
прогнать на боевом контуре один настоящий чек и убедиться, что вердикт дошёл обоими путями -
и по status_url, и колбэком с верной подписью. Дальше - чек-лист
внедрения целиком.
Чек-лист внедрения
- Заявка полная:
expected_amount,requisiteиinitiated_atуходят всегда - без любого из них боевой контур подачу отклоняет. - Транспорт: путь приёма выбран и отработан в песочнице. Подача файлом - обычный
multipart. Подача ссылкой согласована с нами заранее: домен вашего хранилища вносится
в список разрешённых, иначе придёт
FETCH_DISABLEDилиDOMAIN_NOT_ALLOWED. Путь плательщика - решено, где показываетеupload_url(страница, рамка или письмо), а срок жизни ссылки и число попыток выставлены в кабинете под ваши сделки. request_idпередаётся всегда; ретраи по обрыву сети не плодят у вас дублей (проверьте обработку"repeated": true).- Все четыре вердикта обработаны, а у
acceptedразобран иstatus_color: жёлтый - это расхождение с заявкой, а не «всё сошлось». Наresend_requestedплательщик видит просьбу прислать чек заново, а не «отказ». - Колбэки: адрес зарегистрирован в кабинете, секрет подписи получен и хранится в секретах, подпись проверяется по сырому телу, повторная доставка события безопасна, ответ быстрый.
- Список ваших исходящих IP согласован с нами, срок хранения чеков выбран.
- Падение и недоступность Валидатора не останавливают ваши сделки: таймауты и повторы настроены.
- Обратная связь по итогу ордера отправляется после вердикта.
- Ключ хранится в секретах, не в коде; на смену ключа ваша система способна без выката.
Безопасность и данные
- Чеки - персональные данные плательщиков: файлы хранятся зашифрованными, срок хранения
файла - настройка партнёра вплоть до «не хранить вовсе». Даже при нулевом сроке рабочая
запись задачи с файлом живёт около суток, тоже зашифрованной, а служебная запись о
проверке - вердикт и сверенные значения, без файла чека - остаётся в журнале решений:
без неё нельзя разобрать спорный случай. Снять чек с хранения раньше срока можно самим:
DELETE /v1/receipts/{job_id}/file. - Проверенные чеки боевых подключений остаются в закрытом зашифрованном хранилище сервиса - оно нужно для разбора спорных случаев и повышения качества проверки; подключения, помеченные тестовыми, в него не попадают.
- Файлы не передаются третьим лицам: во внешние нейросети, облачные распознаватели и чужие API чек не уходит.
- Загрузка по ссылкам ограждена от SSRF: списки доменов, запрет внутренних адресов.
- Ссылка
share_urlоткрывает карточку проверки без ключа API - обращайтесь с ней как с самим документом. - Ключи партнёров: перевыпуск - по вашему обращению к нам, старый ключ перестаёт работать сразу; в кабинете партнёра меняется секрет подписи колбэков. Списки разрешённых IP закрепляются за подключением. Чужая задача для вашего ключа неотличима от несуществующей.
- Колбэки подписаны HMAC-SHA256; проверка подписи - обязанность принимающей стороны.