Signatures webhooks
Vérifier les signatures webhook Kadryn avec raw body, tolérance de timestamp et comparaison constant-time.
Kadryn signe les livraisons webhook pour que votre endpoint puisse vérifier qu’un événement vient de Kadryn et n’a pas été modifié en transit.
Vérifiez every webhook avant processing it.
Entrées de vérification
A secure recevoirr needs:
- le raw request body;
- le Kadryn timestamp header;
- le Kadryn signature header;
- le endpoint signing secret.
Ne parsez pas et ne resérialisez pas le JSON avant la vérification de signature.
Étapes de vérification
- Lisez la raw body.
- Lisez la timestamp header.
- Reject obsolète timestamps.
- Build le signed payload depuis timestamp et raw body.
- Compute le HMAC avec le endpoint secret.
- Compare signatures dans constant time.
- Process le événement seulement après verification succeeds.
Exemple Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
type VerifyKadrynWebhookInput = {
readonly rawBody: string;
readonly timestamp: string;
readonly signature: string;
readonly secret: string;
readonly toleranceSeconds?: number;
};
function verifyKadrynWebhook({
rawBody,
timestamp,
signature,
secret,
toleranceSeconds = 300,
}: VerifyKadrynWebhookInput): boolean {
const timestampSeconds = Number.parseInt(timestamp, 10);
if (!Number.isSafeInteger(timestampSeconds)) {
return false;
}
const nowSeconds = Math.floor(Date.now() / 1000);
const ageSeconds = Math.abs(nowSeconds - timestampSeconds);
if (ageSeconds > toleranceSeconds) {
return false;
}
const signedPayload = `${timestamp}.${rawBody}`;
const expected = createHmac("sha256", secret)
.update(signedPayload)
.digest("hex");
const actualBuffer = Buffer.from(signature, "hex");
const expectedBuffer = Buffer.from(expected, "hex");
if (actualBuffer.length !== expectedBuffer.length) {
return false;
}
return timingSafeEqual(actualBuffer, expectedBuffer);
}
Exemple de style Express
app.post(
"/webhooks/kadryn",
express.raw({ type: "application/json" }),
(request, response) => {
const rawBody = request.body.toString("utf8");
const timestamp = request.header("Kadryn-Webhook-Timestamp");
const signature = request.header("Kadryn-Webhook-Signature");
if (!timestamp || !signature) {
response.status(400).send("Missing signature headers");
return;
}
const valid = verifyKadrynWebhook({
rawBody,
timestamp,
signature,
secret: process.env.KADRYN_WEBHOOK_SECRET ?? "",
});
if (!valid) {
response.status(400).send("Invalid signature");
return;
}
const event = JSON.parse(rawBody);
response.status(200).json({ received: true });
},
);
Ajustez les noms de headers pour correspondre à la configuration de votre workspace si votre implémentation utilise un format de header préfixé.
Tolérance de timestamp
Reject old timestamps à reduce replay risk.
Recommended default:
5 minutes
Ne définissez pas une tolérance illimitée en production.
Avertissement raw body
Beaucoup de frameworks parsèrent le JSON avant l’exécution de votre handler.
Si votre framework fait cela, la vérification de signature peut échouer parce que le body a changé.
Configurez le route à expose le raw request body.
Erreurs fréquentes
- vérifiering le parsed JSON plutôt de raw body;
- comparing signatures avec normal chaîne equality;
- accepting timestamps avec no tolerance;
- processing le événement avant verification;
- logging webhook secrets;
- treating test événements as proof de production delivery.
Après vérification
After verification succeeds:
- parse le JSON;
- dedupe par événement ID ou delivery ID;
- enqueue heavy work;
- respond avec 2xx quickly;
- log sûr delivery métadonnées.