Aller au contenu

API REST (Tool API et Server-to-Server)

Le SDK navigateur communique avec la Tool API. Vous pouvez appeler la même API depuis votre propre backend — pour enregistrer des consentements recueillis dans votre propre interface, pour émettre des jetons d’approbation à usage unique, ou pour rechercher et vérifier des enregistrements.

URL de base : https://system.hashentry.com

MéthodeCheminObjetAuthentification
GET/tool/v1/configConfiguration du bandeau, documents rattachés, région détectéeClé API (read)
GET/tool/v1/documents/{slug}/activeVersion active d’un document, contenu comprisClé API (read)
GET/tool/v1/documents/activeDocuments actifs rattachés au siteClé API (read)
POST/tool/v1/consentsEnregistrer un consentement (route du SDK navigateur / mobile)Clé API (write) + signature
POST/tool/v1/consents/batchEnregistrer jusqu’à 100 consentementsClé API (write) + signature
POST/tool/v1/s2s/consentsEnregistrer un consentement depuis votre backendClé API (write) + JWT + signature
POST/tool/v1/s2s/consents/batchEnregistrer jusqu’à 100 consentements depuis votre backendClé API (write) + JWT + signature
POST/tool/v1/s2s/approval-tokensÉmettre un jeton d’approbation de document à usage uniqueClé API (write) + JWT + signature
GET/tool/v1/consents/statusConsentements actuels d’un visiteurClé API (read)
GET/tool/v1/consents/verify/{id}Contrôle d’intégrité d’un enregistrementClé API (read)
GET POST/api/v1/public/consents/verifyContrôle d’intégrité public (sans clé)aucune

Chaque requête à la Tool API porte la clé du site :

X-API-Key: he_live_…

Une clé possède une portée : read autorise les points de terminaison GET, write autorise en plus POST. Le site doit être vérifié dans le tableau de bord. Si le site possède une liste d’origines autorisées, les requêtes de navigateur provenant d’autres origines sont rejetées ; s’il possède une liste d’adresses IP autorisées, les requêtes provenant d’autres adresses sont rejetées.

Signature des requêtes (toutes les requêtes POST)

Section intitulée « Signature des requêtes (toutes les requêtes POST) »
X-Signature: <HMAC-SHA256 en hexadécimal>
X-Timestamp: <temps Unix en secondes>
X-Nonce: <valeur unique par requête>
body_hash = SHA-256(corps brut de la requête) (hex en minuscules)
canonical = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + body_hash
signature = HMAC-SHA256(key = clé API, message = canonical) (hex en minuscules)
  • METHOD est en majuscules. PATH est le chemin de la requête, /tool/v1 compris, sans la chaîne de requête, par exemple /tool/v1/s2s/consents.
  • Le hash du corps est calculé sur les octets exacts que vous envoyez. Sérialisez le JSON une seule fois et envoyez cette même chaîne.
  • X-Timestamp doit se situer à ±300 secondes de l’heure du serveur.
  • X-Nonce comporte 8 à 128 caractères parmi A–Z a–z 0–9 . _ : - (un UUID convient) et ne peut être utilisé qu’une seule fois par site ; une répétition est rejetée avec NONCE_REPLAYED.

Exemple détaillé (le vecteur de test commun au SDK et à l’API) :

API key : he_live_0123456789abcdef0123456789abcdef
Method : POST
Path : /tool/v1/consents
Timestamp : 1760000000
Nonce : 3f2b8c1e-5a47-4d0e-9b6a-7c1d2e3f4a5b
Body : {"consent_type":"cookie","decision":"accepted","language":"tr","idempotency_key":"9a1c2b3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d","metadata":{"note":"çerez ✓"}}
body_hash : 93316d8d6adb505a19668f4022e4e33b12aed9c0c4bb97d205b5064257090d34
canonical : POST\n/tool/v1/consents\n1760000000\n3f2b8c1e-5a47-4d0e-9b6a-7c1d2e3f4a5b\n93316d8d6adb505a19668f4022e4e33b12aed9c0c4bb97d205b5064257090d34
signature : e108683927badf35c36d5f467f63a9704c131d0102f14b06af7ed3046d44a8be

(\n représente un unique caractère de saut de ligne ; le corps est en UTF-8.) Si votre implémentation produit cette signature pour ces entrées, elle est correcte.

Node.js
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}`, // routes S2S uniquement
...sign(apiKey, 'POST', '/tool/v1/s2s/consents', body),
};

La signature prouve que la requête a été effectuée avec la clé API et qu’elle n’a pas été rejouée. Comme le SDK navigateur utilise la même clé, qui est visible dans le code source de la page, la signature seule ne prouve pas qu’une requête provient de votre backend — c’est à cela que servent les routes S2S.

Les requêtes S2S nécessitent trois éléments : une clé API write, la signature de la requête et un JWT d’un utilisateur ayant le rôle api_user :

  1. Invitez un membre de l’équipe avec le rôle api_user dans le tableau de bord. Ce rôle ne peut utiliser que l’API S2S ; il ne peut pas se connecter au panneau.
  2. Connectez-vous avec POST /api/v1/auth/login pour obtenir un JWT ; renouvelez-le avec POST /api/v1/auth/refresh.
  3. Envoyez Authorization: Bearer <jwt> avec X-API-Key et les en-têtes de signature. L’utilisateur doit appartenir au tenant propriétaire du site.

Seules les requêtes S2S peuvent définir ip_address, user_agent, otp_verified et otp_reference. Sur la route navigateur, l’adresse IP et le user agent sont toujours tirés de la connexion, et les champs OTP sont ignorés.


POST /tool/v1/consents (ou /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" }
}
ChampRègles
consent_typeRequis. Jusqu’à 50 caractères (A–Z a–z 0–9 _ - .). Utilisés actuellement : cookie, document_approval, do_not_sell_opt_out.
decisionRequis. accepted, rejected, partial ou withdrawn.
document_version_idUUID d’une version active d’un document rattaché au site. Sans lui — ou avec une version qui n’est pas active — la requête est rejetée avec 409 CONFIG_OUTDATED. Un consentement aux cookies référence le document cookie_policy du site.
languageS’il est fourni, il doit correspondre à la langue de cette version.
categoriesObjet clé de catégorie → booléen. Seules les catégories définies dans la configuration du bandeau sont stockées.
idempotency_key8 à 64 caractères (A–Z a–z 0–9 . _ : -). Voir ci-dessous.
visitor_idVotre référence pour la personne, jusqu’à 100 caractères.
visitor_region, visitor_countryJusqu’à 10 / 2 caractères.
compliance_frameworkkvkk, gdpr, uk_gdpr, ccpa, lgpd, popia, pipeda, pipl, pipa ou none.
sourceJusqu’à 20 caractères, par exemple client, server.
metadata, extra_fieldsObjets comportant au plus 30 clés et environ 16 Ko chacun.
approval_tokenJeton à usage unique pour un document_approval (voir ci-dessous).
Champs de signature / de document personnalisé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 — tous de premier niveau. Voir Approbation de documents et signature.
ip_address, user_agent, otp_verified, otp_referenceS2S uniquement.

Obtenez le document_version_id à partir de GET /tool/v1/config?language=en (data.documents[].version.id) ou de GET /tool/v1/documents/{slug}/active?language=en.

{
"message": "…",
"data": {
"consent_token": "ct_…",
"proof_hash": "…64 hex characters…",
"created_at": "2026-10-10T10:00:00+00:00",
"duplicate": false
}
}

202 signifie que la requête a passé tous les contrôles et que l’enregistrement a été accepté pour écriture. Le jeton, le hash de preuve et l’horodatage sont définitifs à ce stade, mais la ligne est écrite par un worker en arrière-plan ; status et verify peuvent donc ne pas la trouver pendant un court instant.

Envoyez une clé par action utilisateur et réutilisez-la à chaque nouvelle tentative de cette action. Une requête répétée avec la même clé ne crée pas de second enregistrement et ne consomme pas de quota ; elle renvoie la réponse d’origine avec "duplicate": true. Les clés sont mémorisées pendant 24 heures. Le SDK navigateur génère un UUID par décision et l’utilise pour la première tentative, pour les nouvelles tentatives côté client et pour les renvois depuis sa file d’attente hors ligne.

POST /tool/v1/consents/batch avec { "consents": [ { … }, … ] } (1 à 100 éléments, mêmes champs que ci-dessus).

{
"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 }
]
}
}

Le statut est 202 lorsque le lot a été traité ; vérifiez chaque entrée de results. Les erreurs de validation et un élément dont la version de document est inconnue ou inactive entraînent le rejet de tout le lot (422 / 409).


POST /tool/v1/s2s/approval-tokens — S2S uniquement.

{
"document_slug": "service-contract",
"subject": "user-4711",
"signer_name": "Jane Doe",
"signer_email": "[email protected]",
"expires_in": 600
}
ChampRègles
document_slug / document_version_idAu moins l’un des deux. Avec un identifiant de version, le jeton n’est valable que pour cette version.
subjectRequis. Votre référence pour la personne (100 caractères max.) ; stockée comme visitor_id de l’enregistrement.
signer_name, signer_email, signer_phone, signer_id_numberFacultatifs. Informations d’identité dont vous vous portez garant.
otp_verified, otp_referenceFacultatifs. Déclarent une vérification OTP que vous avez effectuée.
custom_document_hashFacultatif. SHA-256 (hex) d’un document généré par votre système ; l’approbation doit alors porter exactement ce contenu.
expires_inSecondes, 30 à 3600. 600 par défaut.

Réponse 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
}
}

Remettez approval_token à la page et transmettez-le au SDK (approvalToken), ou envoyez-le en tant que champ de premier niveau approval_token d’un consentement document_approval. Le serveur le consomme une seule fois, tire l’identité du jeton et enregistre identity_assurance: tenant_attested avec l’identifiant non secret approval_token_id. Une requête rejetée pour une autre raison (limite de débit, quota) ne consomme pas le jeton. HashEntry ne stocke qu’un hash du jeton.


Renvoie jusqu’à 20 consentements non expirés d’un visitor_id, du plus récent au plus ancien. Filtres facultatifs : consent_type, document_slug, document_version_id.

{id} est un UUID de consentement, un consent_token (ct_…) ou 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"
}
}

Ce que cette réponse signifie — et ce qu’elle ne signifie pas :

  • valid: true signifie : l’enregistrement appartient à votre tenant, HashEntry a recalculé le hash de preuve à partir de l’enregistrement stocké avec la clé secrète de votre tenant et il correspond, et — si l’enregistrement fait partie d’un lot Merkle — sa preuve d’inclusion est correcte. L’existence d’une ligne ne suffit pas.
  • merkle_valid vaut null lorsque l’enregistrement n’a pas de preuve Merkle.
  • is_tsa_stamped indique qu’un jeton d’horodatage existe pour l’enregistrement (directement ou par l’intermédiaire de son lot Merkle). Il n’indique pas que la signature du jeton a été vérifiée lors de cet appel.
  • Il s’agit d’un contrôle effectué par HashEntry sur les données de HashEntry. Pour vérifier sans vous fier à la réponse du serveur, exportez un paquet de preuves et vérifiez-le vous-même : Vérifier une preuve hors ligne.
  • Un identifiant inconnu renvoie 404 avec "valid": false et code: "CONSENT_NOT_FOUND".

Pas de clé API ; débit limité. Paramètres : token (un jeton ct_… ou la valeur du cookie _he_consent du SDK), proof_hash ou id. Il renvoie la décision, les catégories, le domaine du site, les horodatages, l’état du hash et les informations d’horodatage, et jamais l’adresse IP, le user agent ou l’identifiant du visiteur.

  • Un enregistrement qui existe : 200 avec valid tel que décrit ci-dessus.
  • Une valeur de cookie de navigateur qui ne peut être rapprochée d’aucun enregistrement serveur : 200 avec "valid": false, "source": "client_token", "server_record_found": false et les champs décodés du cookie. Le cookie n’est pas signé ; il n’est donc jamais signalé comme valide à lui seul.
  • Rien de trouvé : 404 avec "valid": false. Aucun identifiant : 422.

Les réponses d’erreur sont en JSON et portent un code machine stable dans code (également error_code et error.code ; certaines erreurs d’authentification n’utilisent que error.code).

HTTPCodeSignification
401MISSING_API_KEY, INVALID_API_KEYClé API absente ou inconnue.
401MISSING_SIGNATURE_HEADERS, TIMESTAMP_EXPIRED, INVALID_SIGNATURE, INVALID_NONCE, NONCE_REPLAYEDProblèmes de signature de la requête.
401MISSING_BEARER_TOKEN, INVALID_BEARER_TOKENS2S : JWT manquant ou invalide.
403SITE_NOT_VERIFIEDVérifiez le site dans le tableau de bord.
403ORIGIN_NOT_ALLOWED, IP_NOT_ALLOWEDAbsent de la liste d’autorisation du site.
403INSUFFICIENT_ROLE, TENANT_MISMATCHS2S : l’utilisateur n’est pas un api_user de ce tenant.
403—La portée de la clé API n’autorise pas la méthode.
404DOCUMENT_NOT_FOUND, VERSION_NOT_FOUND, CONSENT_NOT_FOUND
409CONFIG_OUTDATEDdocument_version_id manquant, non actif, de la mauvaise langue, ou le cache de configuration du serveur est à froid. Relisez /tool/v1/config ; ne renvoyez la requête que si la version est toujours la version active.
422VALIDATION_ERRORLes erreurs de champ se trouvent dans errors.
422APPROVAL_TOKEN_REQUIRED, APPROVAL_TOKEN_INVALID, APPROVAL_TOKEN_CONFLICTVoir jetons d’approbation à usage unique.
422INVALID_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_LARGELimites de la charge utile.
429RATE_LIMIT_EXCEEDEDLimite de débit propre à l’offre. Voir Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining.
429CONSENT_LIMIT_EXCEEDED, NO_CONSENT_SUBSCRIPTIONLe quota de consentements de l’offre est épuisé / il n’y a pas d’offre active.
429TOO_MANY_INVALID_API_KEYSTrop de requêtes avec des clés invalides depuis une même adresse IP.
503QUEUE_OVERLOADED, ADMISSION_UNAVAILABLE, API_KEY_LOOKUP_UNAVAILABLETemporaire. Réessayez après Retry-After avec le même idempotency_key.

Réessayez les réponses 408, 425, 429 et 5xx avec le même corps et le même idempotency_key (et un nouvel horodatage, un nouveau nonce et une nouvelle signature). Ne réessayez pas les autres réponses 4xx sans modification.