Pourquoi vérifier les signatures ?
N’importe qui peut envoyer une requête POST à votre endpoint webhook. La signature HMAC-SHA256 garantit que le payload vient bien de DLoopIQ et qu’il n’a pas été modifié en transit.
- Vous configurez un
secret lors de la création du webhook
- DLoopIQ calcule
HMAC-SHA256(payload, secret) pour chaque requête
- Le résultat est envoyé dans le header
x-dloopiq-signature: sha256=<hash>
- Vous recalculez le HMAC côté serveur et comparez
Implémentation
Node.js / Express
import crypto from 'crypto'
import express from 'express'
const WEBHOOK_SECRET = process.env.DLOOPIQ_WEBHOOK_SECRET!
function verifySignature(
payload: Buffer,
signatureHeader: string,
secret: string
): boolean {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
// Comparaison en temps constant pour éviter les timing attacks
return crypto.timingSafeEqual(
Buffer.from(signatureHeader),
Buffer.from(expected)
)
}
app.post(
'/webhook/dloopiq',
express.raw({ type: 'application/json' }), // ← IMPORTANT : raw body
(req, res) => {
const signature = req.headers['x-dloopiq-signature'] as string
if (!signature) {
return res.status(401).json({ error: 'Signature manquante' })
}
if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Signature invalide' })
}
const event = JSON.parse(req.body.toString())
// Traiter l'événement de manière asynchrone
processEvent(event).catch(console.error)
// Répondre immédiatement
res.status(200).json({ received: true })
}
)
Utilisez express.raw() et non express.json() pour parser le body. Le HMAC est calculé sur les bytes bruts — si vous parsez en JSON puis re-sérialisez, la signature ne correspondra plus.
Python / FastAPI
import hmac
import hashlib
from fastapi import FastAPI, Request, HTTPException
WEBHOOK_SECRET = os.environ["DLOOPIQ_WEBHOOK_SECRET"]
@app.post("/webhook/dloopiq")
async def webhook(request: Request):
body = await request.body()
signature = request.headers.get("x-dloopiq-signature", "")
expected = "sha256=" + hmac.new(
WEBHOOK_SECRET.encode(),
body,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
raise HTTPException(status_code=401, detail="Signature invalide")
event = json.loads(body)
# traiter...
return {"received": True}
PHP
<?php
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_DLOOPIQ_SIGNATURE'] ?? '';
$secret = getenv('DLOOPIQ_WEBHOOK_SECRET');
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
die(json_encode(['error' => 'Signature invalide']));
}
$event = json_decode($payload, true);
// traiter...
echo json_encode(['received' => true]);
Tester votre endpoint
Générez une signature de test en local :
# Simuler un webhook DLoopIQ
SECRET="votre_secret"
PAYLOAD='{"event":"task.completed","taskId":"test123","result":"Positif"}'
SIGNATURE="sha256=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)"
curl -X POST https://votre-serveur.com/webhook/dloopiq \
-H "Content-Type: application/json" \
-H "x-dloopiq-signature: $SIGNATURE" \
-d "$PAYLOAD"
Idempotence
En cas de retry (timeout ou 5xx), DLoopIQ peut envoyer le même événement plusieurs fois. Protégez-vous contre les doublons :
// Stocker les taskId déjà traités (Redis, DB...)
async function processEvent(event: any) {
if (event.event === 'task.completed') {
const alreadyProcessed = await redis.get(`webhook:${event.taskId}`)
if (alreadyProcessed) return // ignorer le doublon
await redis.set(`webhook:${event.taskId}`, '1', 'EX', 86400)
// Traiter...
await saveResultToDatabase(event)
}
}
Checklist sécurité
- ✅ Vérifier
x-dloopiq-signature sur chaque requête
- ✅ Utiliser
timingSafeEqual / hash_equals (pas ===)
- ✅ Parser le body en raw bytes, pas en JSON avant vérification
- ✅ Répondre 200 avant de traiter (éviter les timeouts)
- ✅ Gérer l’idempotence (même événement reçu 2 fois)
- ✅ Stocker
DLOOPIQ_WEBHOOK_SECRET dans les variables d’environnement