Signature
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-Timestampest 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
GETsigne 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,
];
import { createHmac } from 'node:crypto'
const body = JSON.stringify(payload)
const timestamp = Math.floor(Date.now() / 1000).toString()
const signature = 'sha256=' + createHmac('sha256', process.env.KULTLAB_SIGNING_SECRET!)
.update(`${timestamp}.${body}`, 'utf8')
.digest('hex')
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 :
- refuser un timestamp à plus de 300 secondes du sien ;
- recalculer la signature sur le corps brut reçu, avant tout décodage ;
- la comparer en temps constant (
hash_equals,timingSafeEqual) ; - répondre
401sans 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.
| Secret | Timestamp | Corps | Signature |
|---|---|---|---|
klsec_fixture_not_a_real_secret | 1793459000 | (vide, un GET) | sha256=8449cc73b37ae562f2373402666784f3d163be22f65519071a93883752ce7fdc |
klsec_fixture_not_a_real_secret | 1793459000 | {"protocol":1,"app":{"name":"HomieSeeds"},"kind":"shop"} | sha256=39fc046e9ce468909390398397ae59b219dce93d65e99fdce62192bc55f9c2ef |
Les refus
Un 401 dit ce qui a échoué dans son code :
code | Cause |
|---|---|
unauthorized | aucune clé |
invalid_key | clé inconnue, révoquée ou expirée |
signature_missing | un des deux en-têtes manque |
signature_malformed | le timestamp n'est pas un nombre |
signature_expired | le timestamp est à plus de 300 secondes |
signature_mismatch | la signature ne correspond pas ; bodySha256 donne l'empreinte du corps reçu |
signature_other_secret | pendant 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.