REST API (Tool API и Server-to-Server)
Браузерный SDK обращается к Tool API. Вы можете вызывать тот же API со своего бэкенда — чтобы записывать согласия, полученные в вашем собственном интерфейсе, выпускать одноразовые токены одобрения или находить и проверять записи.
Базовый URL: https://system.hashentry.com
| Метод | Путь | Назначение | Аутентификация |
|---|---|---|---|
GET | /tool/v1/config | Конфигурация баннера, прикреплённые документы, определённый регион | API-ключ (read) |
GET | /tool/v1/documents/{slug}/active | Активная версия одного документа, включая содержимое | API-ключ (read) |
GET | /tool/v1/documents/active | Активные документы, прикреплённые к сайту | API-ключ (read) |
POST | /tool/v1/consents | Запись одного согласия (маршрут браузерного / мобильного SDK) | API-ключ (write) + подпись |
POST | /tool/v1/consents/batch | Запись до 100 согласий | API-ключ (write) + подпись |
POST | /tool/v1/s2s/consents | Запись одного согласия с вашего бэкенда | API-ключ (write) + JWT + подпись |
POST | /tool/v1/s2s/consents/batch | Запись до 100 согласий с вашего бэкенда | API-ключ (write) + JWT + подпись |
POST | /tool/v1/s2s/approval-tokens | Выпуск одноразового токена одобрения документа | API-ключ (write) + JWT + подпись |
GET | /tool/v1/consents/status | Текущие согласия одного посетителя | API-ключ (read) |
GET | /tool/v1/consents/verify/{id} | Проверка целостности одной записи | API-ключ (read) |
GET POST | /api/v1/public/consents/verify | Публичная проверка целостности (без ключа) | нет |
Аутентификация
Заголовок раздела «Аутентификация»API-ключ
Заголовок раздела «API-ключ»Каждый запрос к Tool API содержит ключ сайта:
X-API-Key: he_live_…У ключа есть область действия (scope): read разрешает конечные точки GET, write дополнительно разрешает POST. Сайт должен быть подтверждён в панели управления. Если у сайта есть список разрешённых источников (origins), браузерные запросы с других источников отклоняются; если у него есть список разрешённых IP-адресов, запросы с других адресов отклоняются.
Подпись запроса (все запросы POST)
Заголовок раздела «Подпись запроса (все запросы POST)»X-Signature: <hex HMAC-SHA256>X-Timestamp: <Unix-время в секундах>X-Nonce: <уникальное значение для каждого запроса>body_hash = SHA-256(необработанное тело запроса) (hex в нижнем регистре)canonical = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + body_hashsignature = HMAC-SHA256(key = API-ключ, message = canonical) (hex в нижнем регистре)METHODпишется в верхнем регистре.PATH— это путь запроса, включая/tool/v1, без строки запроса, например/tool/v1/s2s/consents.- Хеш тела вычисляется по точным байтам, которые вы отправляете. Сериализуйте JSON один раз и отправляйте именно эту строку.
X-Timestampдолжен отличаться от времени сервера не более чем на ±300 секунд.X-Nonce— это 8–128 символов из набораA–Z a–z 0–9 . _ : -(подойдёт UUID); его можно использовать только один раз для сайта; повтор отклоняется сNONCE_REPLAYED.
Разобранный пример (общий тестовый вектор SDK и API):
API key : he_live_0123456789abcdef0123456789abcdefMethod : POSTPath : /tool/v1/consentsTimestamp : 1760000000Nonce : 3f2b8c1e-5a47-4d0e-9b6a-7c1d2e3f4a5bBody : {"consent_type":"cookie","decision":"accepted","language":"tr","idempotency_key":"9a1c2b3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d","metadata":{"note":"çerez ✓"}}
body_hash : 93316d8d6adb505a19668f4022e4e33b12aed9c0c4bb97d205b5064257090d34canonical : POST\n/tool/v1/consents\n1760000000\n3f2b8c1e-5a47-4d0e-9b6a-7c1d2e3f4a5b\n93316d8d6adb505a19668f4022e4e33b12aed9c0c4bb97d205b5064257090d34signature : e108683927badf35c36d5f467f63a9704c131d0102f14b06af7ed3046d44a8be(\n обозначает один символ перевода строки; тело закодировано в UTF-8.) Если ваша реализация выдаёт эту подпись для этих входных данных, она корректна.
const crypto = require('crypto');
function sign(apiKey, method, path, body) { const timestamp = Math.floor(Date.now() / 1000).toString(); const nonce = crypto.randomUUID(); const bodyHash = crypto.createHash('sha256').update(body, 'utf8').digest('hex'); const canonical = [method.toUpperCase(), path, timestamp, nonce, bodyHash].join('\n'); const signature = crypto.createHmac('sha256', apiKey).update(canonical, 'utf8').digest('hex'); return { 'X-Signature': signature, 'X-Timestamp': timestamp, 'X-Nonce': nonce };}
const body = JSON.stringify({ consent_type: 'document_approval', decision: 'accepted', /* … */ });const headers = { 'Content-Type': 'application/json', 'X-API-Key': apiKey, Authorization: `Bearer ${jwt}`, // только для маршрутов S2S ...sign(apiKey, 'POST', '/tool/v1/s2s/consents', body),};Подпись доказывает, что запрос был сделан с использованием API-ключа и не был воспроизведён повторно. Поскольку браузерный SDK использует тот же ключ, который виден в исходном коде страницы, одна лишь подпись не доказывает, что запрос пришёл с вашего бэкенда, — для этого и предназначены маршруты S2S.
Маршруты server-to-server (/tool/v1/s2s/…)
Заголовок раздела «Маршруты server-to-server (/tool/v1/s2s/…)»Запросам S2S нужны три вещи: API-ключ с правом write, подпись запроса и JWT пользователя с ролью api_user:
- Пригласите в панели управления участника команды с ролью
api_user. Эта роль может использовать только S2S API; войти в панель с ней нельзя. - Выполните вход через
POST /api/v1/auth/login, чтобы получить JWT; обновляйте его черезPOST /api/v1/auth/refresh. - Отправляйте
Authorization: Bearer <jwt>вместе сX-API-Keyи заголовками подписи. Пользователь должен принадлежать тенанту, которому принадлежит сайт.
Только запросы S2S могут задавать ip_address, user_agent, otp_verified и otp_reference. На браузерном маршруте IP-адрес и user agent всегда берутся из соединения, а поля OTP игнорируются.
Запись согласия
Заголовок раздела «Запись согласия»POST /tool/v1/consents (или /tool/v1/s2s/consents)
{ "consent_type": "cookie", "decision": "partial", "document_version_id": "0190a1b2-…", "language": "en", "categories": { "necessary": true, "analytics": true, "marketing": false }, "idempotency_key": "9a1c2b3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d", "visitor_id": "user-4711", "visitor_region": "DE", "compliance_framework": "gdpr", "metadata": { "order_id": "ORD-54321" }}| Поле | Правила |
|---|---|
consent_type | Обязательное. До 50 символов (A–Z a–z 0–9 _ - .). Используются: cookie, document_approval, do_not_sell_opt_out. |
decision | Обязательное. accepted, rejected, partial или withdrawn. |
document_version_id | UUID активной версии документа, прикреплённого к сайту. Без него — или с версией, которая не является активной, — запрос отклоняется с 409 CONFIG_OUTDATED. Согласие на cookie ссылается на документ cookie_policy сайта. |
language | Если указано, должно совпадать с языком этой версии. |
categories | Объект вида «ключ категории → boolean». Сохраняются только категории, определённые в конфигурации баннера. |
idempotency_key | 8–64 символа (A–Z a–z 0–9 . _ : -). См. ниже. |
visitor_id | Ваш идентификатор человека, до 100 символов. |
visitor_region, visitor_country | До 10 / 2 символов. |
compliance_framework | kvkk, gdpr, uk_gdpr, ccpa, lgpd, popia, pipeda, pipl, pipa или none. |
source | До 20 символов, например client, server. |
metadata, extra_fields | Объекты, каждый не более чем с 30 ключами и размером примерно до 16 КБ. |
approval_token | Одноразовый токен для document_approval (см. ниже). |
| Поля подписи / пользовательского документа | signature_image, signature_mime_type, signer_name, signer_email, signer_phone, signer_id_number, declaration_text, drawing_duration_ms, stroke_count, strokes_data, input_type, screen_resolution, custom_document_content, custom_document_mime_type — все на верхнем уровне. См. Одобрение и подписание документов. |
ip_address, user_agent, otp_verified, otp_reference | Только S2S. |
Значение document_version_id можно получить из GET /tool/v1/config?language=en (data.documents[].version.id) или из GET /tool/v1/documents/{slug}/active?language=en.
Ответ: 202 Accepted
Заголовок раздела «Ответ: 202 Accepted»{ "message": "…", "data": { "consent_token": "ct_…", "proof_hash": "…64 hex characters…", "created_at": "2026-10-10T10:00:00+00:00", "duplicate": false }}202 означает, что запрос прошёл все проверки и запись была принята к записи. Токен, хеш доказательства и метка времени на этот момент окончательны, но строку записывает фоновый обработчик, поэтому status и verify могут не находить её в течение короткого времени.
idempotency_key
Заголовок раздела «idempotency_key»Отправляйте один ключ на одно действие пользователя и используйте его повторно при каждой повторной попытке этого действия. Повторный запрос с тем же ключом не создаёт вторую запись и не расходует квоту; он возвращает исходный ответ с "duplicate": true. Ключи запоминаются на 24 часа. Браузерный SDK генерирует один UUID на решение и использует его для первой попытки, клиентских повторных попыток и повторной отправки из своей офлайн-очереди.
Пакетная запись
Заголовок раздела «Пакетная запись»POST /tool/v1/consents/batch с { "consents": [ { … }, … ] } (1–100 элементов, те же поля, что и выше).
{ "data": { "accepted": 2, "rejected": 1, "results": [ { "index": 0, "status": "accepted", "consent_token": "ct_…", "proof_hash": "…", "created_at": "…", "duplicate": false }, { "index": 1, "status": "accepted", "consent_token": "ct_…", "proof_hash": "…", "created_at": "…", "duplicate": true }, { "index": 2, "status": "rejected", "error": "…", "error_code": "CONSENT_LIMIT_EXCEEDED", "code": 429 } ] }}Статус равен 202, когда пакет был обработан; проверяйте каждый элемент results. Ошибки валидации и элемент с неизвестной или неактивной версией документа приводят к отклонению всего пакета (422 / 409).
Одноразовые токены одобрения
Заголовок раздела «Одноразовые токены одобрения»POST /tool/v1/s2s/approval-tokens — только S2S.
{ "document_slug": "service-contract", "subject": "user-4711", "signer_name": "Jane Doe", "expires_in": 600}| Поле | Правила |
|---|---|
document_slug / document_version_id | Хотя бы одно из двух. С идентификатором версии токен действителен только для этой версии. |
subject | Обязательное. Ваш идентификатор человека (не более 100 символов); сохраняется как visitor_id записи. |
signer_name, signer_email, signer_phone, signer_id_number | Необязательные. Сведения о личности, за которые вы ручаетесь. |
otp_verified, otp_reference | Необязательные. Заявляют о проверке OTP, которую вы выполнили. |
custom_document_hash | Необязательное. SHA-256 (hex) документа, сформированного вашей системой; в этом случае одобрение должно содержать именно это содержимое. |
expires_in | Секунды, 30–3600. По умолчанию 600. |
Ответ 201:
{ "data": { "approval_token": "hat_…", "approval_token_id": "atk_…", "expires_in": 600, "expires_at": "2026-10-10T10:10:00+00:00", "document_id": "…", "document_slug": "service-contract", "document_version_id": null }}Передайте approval_token на страницу и отдайте его SDK (approvalToken) или отправьте его как поле верхнего уровня approval_token согласия типа document_approval. Сервер погашает его один раз, берёт сведения о личности из токена и записывает identity_assurance: tenant_attested вместе с несекретным approval_token_id. Запрос, отклонённый по другой причине (ограничение частоты, квота), не расходует токен. HashEntry хранит только хеш токена.
Чтение и проверка
Заголовок раздела «Чтение и проверка»GET /tool/v1/consents/status?visitor_id=…
Заголовок раздела «GET /tool/v1/consents/status?visitor_id=…»Возвращает до 20 неистёкших согласий одного visitor_id, начиная с самых новых. Необязательные фильтры: consent_type, document_slug, document_version_id.
GET /tool/v1/consents/verify/{id}
Заголовок раздела «GET /tool/v1/consents/verify/{id}»{id} — это UUID согласия, consent_token (ct_…) или proof_hash.
{ "valid": true, "data": { "consent_type": "cookie", "decision": "accepted", "proof_hash": "…", "hash_version": 2, "hash_valid": true, "merkle_valid": null, "is_anonymized": false, "is_tsa_stamped": false, "created_at": "2026-10-10T10:00:00+00:00" }}Что означает этот ответ — и чего он не означает:
valid: trueозначает: запись принадлежит вашему тенанту, HashEntry заново вычислил хеш доказательства по сохранённой записи с секретным ключом вашего тенанта, и он совпадает, а также — если запись входит в пакет Merkle — её доказательство включения корректно. Одного существования строки недостаточно.merkle_validравенnull, когда у записи нет доказательства Merkle.is_tsa_stampedсообщает, что для записи существует токен метки времени (непосредственно или через её пакет Merkle). Он не сообщает, что подпись токена была проверена в этом вызове.- Это проверка, выполняемая HashEntry на данных HashEntry. Чтобы выполнить проверку, не доверяя ответу сервера, экспортируйте пакет доказательств и проверьте его самостоятельно: Офлайн-проверка доказательств.
- Неизвестный идентификатор возвращает
404с"valid": falseиcode: "CONSENT_NOT_FOUND".
GET|POST /api/v1/public/consents/verify
Заголовок раздела «GET|POST /api/v1/public/consents/verify»Без API-ключа; с ограничением частоты запросов. Параметры: token (токен ct_… или значение cookie _he_consent из SDK), proof_hash или id. Возвращает решение, категории, домен сайта, метки времени, статус хеша и сведения о метке времени и никогда не возвращает IP-адрес, user agent или идентификатор посетителя.
- Запись, которая существует:
200сvalid, как описано выше. - Значение браузерного cookie, которое не удаётся сопоставить с серверной записью:
200с"valid": false,"source": "client_token","server_record_found": falseи декодированными полями cookie. Cookie не подписан, поэтому сам по себе он никогда не считается действительным. - Ничего не найдено:
404с"valid": false. Нет идентификатора:422.
Коды ошибок
Заголовок раздела «Коды ошибок»Ответы с ошибками имеют формат JSON и содержат стабильный машинный код в code (а также в error_code и error.code; некоторые ошибки аутентификации используют только error.code).
| HTTP | Код | Значение |
|---|---|---|
| 401 | MISSING_API_KEY, INVALID_API_KEY | API-ключ отсутствует или неизвестен. |
| 401 | MISSING_SIGNATURE_HEADERS, TIMESTAMP_EXPIRED, INVALID_SIGNATURE, INVALID_NONCE, NONCE_REPLAYED | Проблемы с подписью запроса. |
| 401 | MISSING_BEARER_TOKEN, INVALID_BEARER_TOKEN | S2S: JWT отсутствует или недействителен. |
| 403 | SITE_NOT_VERIFIED | Подтвердите сайт в панели управления. |
| 403 | ORIGIN_NOT_ALLOWED, IP_NOT_ALLOWED | Нет в списке разрешённых для сайта. |
| 403 | INSUFFICIENT_ROLE, TENANT_MISMATCH | S2S: пользователь не является api_user этого тенанта. |
| 403 | — | Область действия API-ключа не разрешает этот метод. |
| 404 | DOCUMENT_NOT_FOUND, VERSION_NOT_FOUND, CONSENT_NOT_FOUND | |
| 409 | CONFIG_OUTDATED | document_version_id отсутствует, не активен, не соответствует языку, либо кеш конфигурации сервера холодный. Заново прочитайте /tool/v1/config; отправляйте повторно, только если версия по-прежнему активна. |
| 422 | VALIDATION_ERROR | Ошибки полей находятся в errors. |
| 422 | APPROVAL_TOKEN_REQUIRED, APPROVAL_TOKEN_INVALID, APPROVAL_TOKEN_CONFLICT | См. одноразовые токены одобрения. |
| 422 | INVALID_CATEGORIES, METADATA_TOO_MANY_KEYS, METADATA_TOO_LARGE, EXTRA_FIELDS_TOO_MANY_KEYS, EXTRA_FIELD_NAME_TOO_LONG, EXTRA_FIELDS_TOO_LARGE, STROKES_DATA_TOO_LARGE, INVALID_SIGNATURE_IMAGE, SIGNATURE_IMAGE_TOO_LARGE, INVALID_CUSTOM_DOCUMENT, UNSUPPORTED_CUSTOM_DOCUMENT_TYPE, CUSTOM_DOCUMENT_TOO_LARGE | Ограничения на содержимое запроса. |
| 429 | RATE_LIMIT_EXCEEDED | Ограничение частоты запросов по тарифному плану. См. Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining. |
| 429 | CONSENT_LIMIT_EXCEEDED, NO_CONSENT_SUBSCRIPTION | Квота согласий тарифного плана исчерпана / нет активного тарифного плана. |
| 429 | TOO_MANY_INVALID_API_KEYS | Слишком много запросов с недействительными ключами с одного IP-адреса. |
| 503 | QUEUE_OVERLOADED, ADMISSION_UNAVAILABLE, API_KEY_LOOKUP_UNAVAILABLE | Временная ошибка. Повторите попытку по истечении Retry-After с тем же idempotency_key. |
Повторяйте запросы, получившие ответы 408, 425, 429 и 5xx, с тем же телом и тем же idempotency_key (и с новыми меткой времени, nonce и подписью). Не повторяйте без изменений запросы, получившие другие ответы 4xx.