API REST (Tool API y Server-to-Server)
El SDK de navegador se comunica con la Tool API. Puede llamar a la misma API desde su propio backend: para registrar consentimientos recogidos en su propia interfaz, para emitir tokens de aprobación de un solo uso o para consultar y verificar registros.
URL base: https://system.hashentry.com
| Método | Ruta | Finalidad | Autenticación |
|---|---|---|---|
GET | /tool/v1/config | Configuración del banner, documentos asociados, región detectada | Clave API (lectura) |
GET | /tool/v1/documents/{slug}/active | Versión activa de un documento, incluido el contenido | Clave API (lectura) |
GET | /tool/v1/documents/active | Documentos activos asociados al sitio | Clave API (lectura) |
POST | /tool/v1/consents | Registrar un consentimiento (ruta del SDK de navegador / móvil) | Clave API (escritura) + firma |
POST | /tool/v1/consents/batch | Registrar hasta 100 consentimientos | Clave API (escritura) + firma |
POST | /tool/v1/s2s/consents | Registrar un consentimiento desde su backend | Clave API (escritura) + JWT + firma |
POST | /tool/v1/s2s/consents/batch | Registrar hasta 100 consentimientos desde su backend | Clave API (escritura) + JWT + firma |
POST | /tool/v1/s2s/approval-tokens | Emitir un token de aprobación de documento de un solo uso | Clave API (escritura) + JWT + firma |
GET | /tool/v1/consents/status | Consentimientos actuales de un visitante | Clave API (lectura) |
GET | /tool/v1/consents/verify/{id} | Comprobación de integridad de un registro | Clave API (lectura) |
GET POST | /api/v1/public/consents/verify | Comprobación pública de integridad (sin clave) | ninguna |
Autenticación
Sección titulada «Autenticación»Clave API
Sección titulada «Clave API»Toda solicitud a la Tool API incluye la clave del sitio:
X-API-Key: he_live_…Una clave tiene un ámbito: read permite los endpoints GET; write permite además POST. El sitio debe estar verificado en el panel. Si el sitio tiene una lista de orígenes permitidos, las solicitudes de navegador procedentes de otros orígenes se rechazan; si tiene una lista de direcciones IP permitidas, las solicitudes procedentes de otras direcciones se rechazan.
Firma de la solicitud (todas las solicitudes POST)
Sección titulada «Firma de la solicitud (todas las solicitudes POST)»X-Signature: <HMAC-SHA256 en hexadecimal>X-Timestamp: <hora Unix en segundos>X-Nonce: <valor único por solicitud>body_hash = SHA-256(cuerpo de la solicitud sin procesar) (hexadecimal en minúsculas)canonical = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + body_hashsignature = HMAC-SHA256(clave = clave API, mensaje = canonical) (hexadecimal en minúsculas)METHODva en mayúsculas.PATHes la ruta de la solicitud, incluido/tool/v1, sin la cadena de consulta; p. ej./tool/v1/s2s/consents.- El hash del cuerpo se calcula sobre los bytes exactos que usted envía. Serialice el JSON una sola vez y envíe esa misma cadena.
X-Timestampdebe estar dentro de ±300 segundos respecto a la hora del servidor.X-Noncetiene entre 8 y 128 caracteres deA–Z a–z 0–9 . _ : -(un UUID sirve) y solo puede utilizarse una vez por sitio; una repetición se rechaza conNONCE_REPLAYED.
Ejemplo desarrollado (el vector de prueba compartido por el SDK y la 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 representa un único carácter de salto de línea; el cuerpo está en UTF-8.) Si su implementación produce esta firma para estas entradas, es correcta.
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}`, // solo en las rutas S2S ...sign(apiKey, 'POST', '/tool/v1/s2s/consents', body),};La firma prueba que la solicitud se realizó con la clave API y que no se ha repetido. Dado que el SDK de navegador utiliza la misma clave, que es visible en el código fuente de la página, la firma por sí sola no prueba que una solicitud procediera de su backend: para eso están las rutas S2S.
Rutas de servidor a servidor (/tool/v1/s2s/…)
Sección titulada «Rutas de servidor a servidor (/tool/v1/s2s/…)»Las solicitudes S2S necesitan tres cosas: una clave API de escritura, la firma de la solicitud y un JWT de un usuario con el rol api_user:
- Invite en el panel a un miembro del equipo con el rol
api_user. Este rol solo puede utilizar la API S2S; no puede iniciar sesión en el panel. - Inicie sesión con
POST /api/v1/auth/loginpara obtener un JWT; renuévelo conPOST /api/v1/auth/refresh. - Envíe
Authorization: Bearer <jwt>junto conX-API-Keyy los encabezados de firma. El usuario debe pertenecer al tenant propietario del sitio.
Solo las solicitudes S2S pueden establecer ip_address, user_agent, otp_verified y otp_reference. En la ruta de navegador, la dirección IP y el agente de usuario se toman siempre de la conexión, y los campos OTP se ignoran.
Registro de un consentimiento
Sección titulada «Registro de un consentimiento»POST /tool/v1/consents (o /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" }}| Campo | Reglas |
|---|---|
consent_type | Obligatorio. Hasta 50 caracteres (A–Z a–z 0–9 _ - .). En uso: cookie, document_approval, do_not_sell_opt_out. |
decision | Obligatorio. accepted, rejected, partial o withdrawn. |
document_version_id | UUID de una versión activa de un documento asociado al sitio. Sin él, o con una versión que no está activa, la solicitud se rechaza con 409 CONFIG_OUTDATED. Un consentimiento de cookies hace referencia al documento cookie_policy del sitio. |
language | Si se indica, debe ser el idioma de esa versión. |
categories | Objeto de clave de categoría → booleano. Solo se almacenan las categorías definidas en la configuración del banner. |
idempotency_key | Entre 8 y 64 caracteres (A–Z a–z 0–9 . _ : -). Véase más abajo. |
visitor_id | Su referencia de la persona, hasta 100 caracteres. |
visitor_region, visitor_country | Hasta 10 / 2 caracteres. |
compliance_framework | kvkk, gdpr, uk_gdpr, ccpa, lgpd, popia, pipeda, pipl, pipa o none. |
source | Hasta 20 caracteres, p. ej. client, server. |
metadata, extra_fields | Objetos con un máximo de 30 claves y unos 16 KB cada uno. |
approval_token | Token de un solo uso para un document_approval (véase más abajo). |
| Campos de firma / de documento personalizado | 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; todos de nivel superior. Consulte Aprobación y firma de documentos. |
ip_address, user_agent, otp_verified, otp_reference | Solo S2S. |
Obtenga el document_version_id de GET /tool/v1/config?language=en (data.documents[].version.id) o de GET /tool/v1/documents/{slug}/active?language=en.
Respuesta: 202 Accepted
Sección titulada «Respuesta: 202 Accepted»{ "message": "…", "data": { "consent_token": "ct_…", "proof_hash": "…64 hex characters…", "created_at": "2026-10-10T10:00:00+00:00", "duplicate": false }}202 significa que la solicitud ha superado todas las comprobaciones y que el registro se ha aceptado para su escritura. El token, el hash de prueba y la marca de tiempo son definitivos en ese momento, pero la fila la escribe un proceso en segundo plano, por lo que status y verify pueden no encontrarla durante un breve instante.
idempotency_key
Sección titulada «idempotency_key»Envíe una clave por acción del usuario y reutilícela en cada reintento de esa acción. Una solicitud repetida con la misma clave no crea un segundo registro ni consume cuota; devuelve la respuesta original con "duplicate": true. Las claves se recuerdan durante 24 horas. El SDK de navegador genera un UUID por decisión y lo utiliza para el primer intento, los reintentos del cliente y los reenvíos desde su cola sin conexión.
POST /tool/v1/consents/batch con { "consents": [ { … }, … ] } (de 1 a 100 elementos, con los mismos campos que arriba).
{ "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 } ] }}El estado es 202 cuando el lote se ha procesado; compruebe cada entrada de results. Los errores de validación y un elemento con una versión de documento desconocida o inactiva provocan el rechazo de todo el lote (422 / 409).
Tokens de aprobación de un solo uso
Sección titulada «Tokens de aprobación de un solo uso»POST /tool/v1/s2s/approval-tokens — solo S2S.
{ "document_slug": "service-contract", "subject": "user-4711", "signer_name": "Jane Doe", "expires_in": 600}| Campo | Reglas |
|---|---|
document_slug / document_version_id | Al menos uno. Con un identificador de versión, el token es válido únicamente para esa versión. |
subject | Obligatorio. Su referencia de la persona (máx. 100 caracteres); se almacena como el visitor_id del registro. |
signer_name, signer_email, signer_phone, signer_id_number | Opcionales. Datos de identidad que usted avala. |
otp_verified, otp_reference | Opcionales. Declaran una comprobación OTP que usted ha realizado. |
custom_document_hash | Opcional. SHA-256 (hexadecimal) de un documento generado por su sistema; la aprobación debe incluir entonces exactamente ese contenido. |
expires_in | Segundos, de 30 a 3600. Valor predeterminado: 600. |
Respuesta 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 }}Entregue approval_token a la página y páselo al SDK (approvalToken) o envíelo como approval_token de nivel superior de un consentimiento document_approval. El servidor lo consume una sola vez, toma la identidad del token y registra identity_assurance: tenant_attested junto con el approval_token_id, que no es secreto. Una solicitud rechazada por otro motivo (límite de frecuencia, cuota) no consume el token. HashEntry almacena únicamente un hash del token.
Lectura y verificación
Sección titulada «Lectura y verificación»GET /tool/v1/consents/status?visitor_id=…
Sección titulada «GET /tool/v1/consents/status?visitor_id=…»Devuelve hasta 20 consentimientos no caducados de un visitor_id, del más reciente al más antiguo. Filtros opcionales: consent_type, document_slug, document_version_id.
GET /tool/v1/consents/verify/{id}
Sección titulada «GET /tool/v1/consents/verify/{id}»{id} es el UUID de un consentimiento, un consent_token (ct_…) o un 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" }}Qué significa esta respuesta y qué no:
valid: truesignifica que el registro pertenece a su tenant, que HashEntry ha recalculado el hash de prueba a partir del registro almacenado con la clave secreta de su tenant y coincide, y que, si el registro forma parte de un lote Merkle, su prueba de inclusión es correcta. La existencia de una fila no basta.merkle_validesnullcuando el registro no tiene prueba Merkle.is_tsa_stampedindica que existe un token de sello de tiempo para el registro (directamente o a través de su lote Merkle). No indica que la firma del token se haya verificado en esta llamada.- Se trata de una comprobación realizada por HashEntry sobre los datos de HashEntry. Para verificar sin confiar en la respuesta del servidor, exporte un paquete de evidencias y verifíquelo usted mismo: Verificación de evidencias sin conexión.
- Un identificador desconocido devuelve
404con"valid": falseycode: "CONSENT_NOT_FOUND".
GET|POST /api/v1/public/consents/verify
Sección titulada «GET|POST /api/v1/public/consents/verify»Sin clave API; con límite de frecuencia. Parámetros: token (un token ct_… o el valor de la cookie _he_consent del SDK), proof_hash o id. Devuelve la decisión, las categorías, el dominio del sitio, las marcas de tiempo, el estado del hash y la información del sello de tiempo, y nunca la dirección IP, el agente de usuario ni el identificador del visitante.
- Un registro que existe:
200convalidtal como se describe más arriba. - Un valor de cookie de navegador que no puede asociarse a un registro del servidor:
200con"valid": false,"source": "client_token","server_record_found": falsey los campos decodificados de la cookie. La cookie no está firmada, por lo que nunca se notifica como válida por sí sola. - No se encuentra nada:
404con"valid": false. Sin identificador:422.
Códigos de error
Sección titulada «Códigos de error»Las respuestas de error son JSON e incluyen un código de máquina estable en code (también en error_code y error.code; algunos errores de autenticación solo utilizan error.code).
| HTTP | Código | Significado |
|---|---|---|
| 401 | MISSING_API_KEY, INVALID_API_KEY | Clave API ausente o desconocida. |
| 401 | MISSING_SIGNATURE_HEADERS, TIMESTAMP_EXPIRED, INVALID_SIGNATURE, INVALID_NONCE, NONCE_REPLAYED | Problemas con la firma de la solicitud. |
| 401 | MISSING_BEARER_TOKEN, INVALID_BEARER_TOKEN | S2S: JWT ausente o no válido. |
| 403 | SITE_NOT_VERIFIED | Verifique el sitio en el panel. |
| 403 | ORIGIN_NOT_ALLOWED, IP_NOT_ALLOWED | No figura en la lista de permitidos del sitio. |
| 403 | INSUFFICIENT_ROLE, TENANT_MISMATCH | S2S: el usuario no es un api_user de este tenant. |
| 403 | — | El ámbito de la clave API no permite el método. |
| 404 | DOCUMENT_NOT_FOUND, VERSION_NOT_FOUND, CONSENT_NOT_FOUND | |
| 409 | CONFIG_OUTDATED | document_version_id ausente, no activo, con un idioma incorrecto, o la caché de configuración del servidor está fría. Vuelva a leer /tool/v1/config; reenvíe solo si la versión sigue siendo la activa. |
| 422 | VALIDATION_ERROR | Los errores de campo están en errors. |
| 422 | APPROVAL_TOKEN_REQUIRED, APPROVAL_TOKEN_INVALID, APPROVAL_TOKEN_CONFLICT | Consulte tokens de aprobación de un solo uso. |
| 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 | Límites de la carga útil. |
| 429 | RATE_LIMIT_EXCEEDED | Límite de frecuencia según el plan. Véanse Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining. |
| 429 | CONSENT_LIMIT_EXCEEDED, NO_CONSENT_SUBSCRIPTION | La cuota de consentimientos del plan se ha agotado / no hay ningún plan activo. |
| 429 | TOO_MANY_INVALID_API_KEYS | Demasiadas solicitudes con claves no válidas desde una misma dirección IP. |
| 503 | QUEUE_OVERLOADED, ADMISSION_UNAVAILABLE, API_KEY_LOOKUP_UNAVAILABLE | Temporal. Reintente después de Retry-After con la misma idempotency_key. |
Reintente las respuestas 408, 425, 429 y 5xx con el mismo cuerpo y la misma idempotency_key (y con una marca de tiempo, un nonce y una firma nuevos). No reintente sin cambios otras respuestas 4xx.