Protocole

Signature

Signer chaque requête et chaque réponse avec HMAC-SHA256, et vérifier celles de Kult Lab.

La clé dit qui parle ; la signature prouve que le corps n'a pas été modifié et que la requête n'est pas rejouée. Elle est exigée sur chaque appel, dans les deux sens : vos requêtes, les commandes que Kult Lab vous envoie, et vos réponses à ces commandes.

Le schéma

Authorization: Bearer kl_<préfixe>.<secret>
X-KultLab-Timestamp: 1793459000
X-KultLab-Signature: sha256=<hmac_sha256(KULTLAB_SIGNING_SECRET, "<timestamp>.<corps brut>")>
  • X-KultLab-Timestamp est l'heure Unix en secondes. Kult Lab refuse un écart de plus de 300 secondes avec son horloge.
  • La chaîne signée est le timestamp, un point, puis les octets exacts du corps envoyé. Un GET signe un corps vide : "1793459000.".
  • La signature est l'empreinte HMAC-SHA256 en hexadécimal minuscule, précédée de sha256=.

Signez le corps après l'avoir sérialisé, et envoyez ces mêmes octets. Un client HTTP qui re-sérialise le JSON (espaces, échappement de é en é) casse la signature.

$body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
$timestamp = (string) time();
$signature = 'sha256='.hash_hmac('sha256', $timestamp.'.'.$body, $_ENV['KULTLAB_SIGNING_SECRET']);

$headers = [
    'Authorization' => 'Bearer '.$_ENV['KULTLAB_API_KEY'],
    'Content-Type' => 'application/json',
    'X-KultLab-Timestamp' => $timestamp,
    'X-KultLab-Signature' => $signature,
];

Vérifier ce que Kult Lab vous envoie

Une commande arrive avec les mêmes en-têtes, signés avec le même secret. Votre application doit :

  1. refuser un timestamp à plus de 300 secondes du sien ;
  2. recalculer la signature sur le corps brut reçu, avant tout décodage ;
  3. la comparer en temps constant (hash_equals, timingSafeEqual) ;
  4. répondre 401 sans rien faire si elle ne correspond pas.

Kult Lab le vérifie lui-même : la vérification delivery.signature vous envoie une commande mal signée et attend un 401 ou un 403. Une application qui l'accepte ne peut pas être activée.

L'en-tête Authorization d'une commande porte une clé que vous pouvez ignorer : c'est la signature qui prouve que l'appel vient de Kult Lab.

Signer vos réponses

Votre réponse à une commande porte elle aussi X-KultLab-Timestamp et X-KultLab-Signature, calculés sur le corps brut de la réponse. Une réponse non signée est lue, mais delivery.ping échoue et la connexion ne peut pas être activée.

Les exemples de référence

Ces valeurs ont été calculées indépendamment de Kult Lab. Faites-en des tests de votre signeur.

SecretTimestampCorpsSignature
klsec_fixture_not_a_real_secret1793459000(vide, un GET)sha256=8449cc73b37ae562f2373402666784f3d163be22f65519071a93883752ce7fdc
klsec_fixture_not_a_real_secret1793459000{"protocol":1,"app":{"name":"HomieSeeds"},"kind":"shop"}sha256=39fc046e9ce468909390398397ae59b219dce93d65e99fdce62192bc55f9c2ef

Les refus

Un 401 dit ce qui a échoué dans son code :

codeCause
unauthorizedaucune clé
invalid_keyclé inconnue, révoquée ou expirée
signature_missingun des deux en-têtes manque
signature_malformedle timestamp n'est pas un nombre
signature_expiredle timestamp est à plus de 300 secondes
signature_mismatchla signature ne correspond pas ; bodySha256 donne l'empreinte du corps reçu
signature_other_secretpendant un renouvellement, la requête est signée avec le secret de l'autre clé

Sur un signature_mismatch, comparez bodySha256 avec le SHA-256 du corps que vous pensiez envoyer : s'ils diffèrent, quelque chose a re-sérialisé le corps entre votre signeur et le réseau.

Un 401 ne se réessaie pas tel quel : arrêtez la file d'envoi et alertez.

Copyright © 2026