Kadryza Pay est en preview privée au Tchad. Demander un accès anticipé →
Guides d'intégrationVérification des webhooks

Vérification des webhooks

Chaque webhook Kadryza est signé avec le secret de l’endpoint.

Header confirmé :

X-Kadryza-Signature: sha256=<hmac_hex>

Algorithme :

  1. récupérer le body JSON brut ;
  2. calculer HMAC-SHA256(rawBody, webhookSecret) ;
  3. encoder en hex ;
  4. 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: true

Ce header n’est pas présent sur les livraisons réelles.

Checklist

  • le body brut est disponible ;
  • X-Kadryza-Signature est vérifié ;
  • la comparaison est constant-time ;
  • l’endpoint répond en HTTP 2xx après traitement accepté ;
  • le traitement est idempotent ;
  • le secret webhook n’est jamais loggé.