Vérification des webhooks
Chaque webhook Kadryza est signé avec le secret de l’endpoint.
Header confirmé :
X-Kadryza-Signature: sha256=<hmac_hex>Algorithme :
- récupérer le body JSON brut ;
- calculer
HMAC-SHA256(rawBody, webhookSecret); - encoder en hex ;
- comparer au header
sha256=<hex>en constant-time.
⚠️
Ne vérifiez jamais la signature sur un objet JSON déjà parsé puis re-sérialisé. Le HMAC doit être calculé sur le body brut reçu.
Avec le SDK
import { verifyWebhookSignature } from '@kadryza/sdk'
const valid = verifyWebhookSignature({
payload: rawBody,
signature: req.headers['x-kadryza-signature'],
secret: process.env.KADRYZA_WEBHOOK_SECRET!
})
if (!valid) {
throw new Error('Invalid Kadryza webhook signature')
}Vous pouvez aussi utiliser l’instance SDK :
const valid = kadryza.webhooks.verify({
payload: rawBody,
signature: req.headers['x-kadryza-signature'],
secret: process.env.KADRYZA_WEBHOOK_SECRET!
})Le SDK accepte sha256=<hex> et garde la compatibilité avec un hex brut.
Avec Node.js crypto
import crypto from 'node:crypto'
function verifyKadryzaSignature(rawBody: string, signature: string | undefined, secret: string): boolean {
if (!signature) return false
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex')
const actualBuffer = Buffer.from(signature)
const expectedBuffer = Buffer.from(expected)
if (actualBuffer.length !== expectedBuffer.length) {
return false
}
return crypto.timingSafeEqual(actualBuffer, expectedBuffer)
}Express
import express from 'express'
import { verifyWebhookSignature } from '@kadryza/sdk'
const app = express()
app.post(
'/webhooks/kadryza',
express.raw({ type: 'application/json' }),
(req, res) => {
const rawBody = req.body.toString('utf8')
const signature = req.header('X-Kadryza-Signature')
const valid = verifyWebhookSignature({
payload: rawBody,
signature,
secret: process.env.KADRYZA_WEBHOOK_SECRET!
})
if (!valid) {
return res.status(401).send('invalid signature')
}
const event = JSON.parse(rawBody)
// Traitez event.event et event.data de façon idempotente.
return res.sendStatus(200)
}
)Next.js Route Handler
import { NextRequest, NextResponse } from 'next/server'
import { verifyWebhookSignature } from '@kadryza/sdk'
export async function POST(request: NextRequest) {
const rawBody = await request.text()
const signature = request.headers.get('x-kadryza-signature') ?? undefined
const valid = verifyWebhookSignature({
payload: rawBody,
signature,
secret: process.env.KADRYZA_WEBHOOK_SECRET!
})
if (!valid) {
return NextResponse.json({ error: 'invalid signature' }, { status: 401 })
}
const event = JSON.parse(rawBody)
return NextResponse.json({ received: true })
}Idempotence
Kadryza ne confirme pas actuellement de header dédié pour identifier une livraison webhook.
Pour dédupliquer, utilisez une clé issue du payload :
const idempotencyKey = `${event.event}:${event.data.id}:${event.data.status}`Stockez cette clé dans votre base avant d’exécuter une action irréversible.
Headers de test
Les webhooks déclenchés par /v1/webhooks/:id/test incluent :
X-Kadryza-Test: trueCe header n’est pas présent sur les livraisons réelles.
Checklist
- le body brut est disponible ;
X-Kadryza-Signatureest vérifié ;- la comparaison est constant-time ;
- l’endpoint répond en HTTP
2xxaprès traitement accepté ; - le traitement est idempotent ;
- le secret webhook n’est jamais loggé.