> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dloopiq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Vérification des signatures webhook

> Comment valider que les webhooks reçus viennent bien de DLoopIQ.

## 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.

***

## Comment ça marche

1. Vous configurez un `secret` lors de la création du webhook
2. DLoopIQ calcule `HMAC-SHA256(payload, secret)` pour chaque requête
3. Le résultat est envoyé dans le header `x-dloopiq-signature: sha256=<hash>`
4. Vous recalculez le HMAC côté serveur et comparez

***

## Implémentation

### Node.js / Express

```typescript theme={null}
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 })
  }
)
```

<Warning>
  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.
</Warning>

### Python / FastAPI

```python theme={null}
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 theme={null}
<?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 :

```bash theme={null}
# 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 :

```typescript theme={null}
// 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
