# Pourquoi la signature de mon webhook ne correspond-elle pas ?

> Corrigez les échecs de vérification de signature des webhooks. Signez le corps brut avec le secret whsec_ complet et la valeur de X-Emailit-Timestamp.

Cet article vous aide quand votre endpoint rejette les webhooks d’Emailit parce que la signature que vous calculez ne correspond pas à l’en-tête `X-Emailit-Signature`. Dans presque tous les cas, les octets que vous signez ou le secret que vous utilisez diffèrent de ceux qu’a utilisés Emailit.

## Symptômes

- Votre gestionnaire renvoie `401` ou `400` avec « invalid signature » pour chaque webhook, y compris les événements **Send test** du tableau de bord.
- Les requêtes de l’onglet **Requests** du webhook affichent des tentatives en échec avec le code de statut d’erreur de votre endpoint.
- La vérification fonctionne dans un test unitaire avec un payload écrit à la main, mais échoue avec les vraies livraisons.

## Cause

Emailit signe chaque requête ainsi :

```text
signature = hex( HMAC-SHA256( secret, "<X-Emailit-Timestamp>.<raw request body>" ) )
```

La signature échoue lorsque l’une des trois entrées diffère :

- **Le corps a été resérialisé.** Les frameworks analysent souvent le JSON avant l’exécution de votre gestionnaire. Appeler `JSON.stringify(req.body)` produit des espaces et un ordre des clés différents des octets envoyés par Emailit. Le corps est un **tableau** JSON de 100 événements au maximum : un code qui attend un objet risque donc aussi de le remanier.
- **Le secret est incorrect.** Utilisez la valeur complète, préfixe `whsec_` compris. Chaque webhook a son propre secret, et sa réinitialisation invalide immédiatement l’ancien, y compris pour les nouvelles tentatives en attente.
- **L’horodatage est absent ou différent.** Utilisez la valeur exacte de l’en-tête `X-Emailit-Timestamp` et joignez-la au corps avec un seul point.
- **La comparaison est incorrecte.** L’en-tête est en hexadécimal minuscule, pas en Base64, et n’a pas de préfixe `sha256=`.
- **Un élément placé devant votre application a modifié le corps**, comme un proxy qui le réencode ou le décompresse.

## Solution

1. **Lisez le corps brut.** Capturez les octets avant l’exécution de tout analyseur JSON. Dans Express, montez `express.raw()` uniquement sur la route du webhook :

```javascript title="webhook.js"
import crypto from 'node:crypto';

app.post('/webhooks/emailit', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = req.get('X-Emailit-Timestamp');
  const expected = crypto
    .createHmac('sha256', process.env.EMAILIT_WEBHOOK_SECRET) // whsec_...
    .update(`${timestamp}.${req.body.toString('utf8')}`)
    .digest('hex');
  const received = req.get('X-Emailit-Signature') ?? '';
  const valid = received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
  if (!valid) return res.sendStatus(401);

  const events = JSON.parse(req.body); // an array of events
  res.sendStatus(200);
});
```

   Dans les gestionnaires de route Next.js, utilisez `await request.text()`. Dans Laravel, utilisez `$request->getContent()`. Dans Flask, utilisez `request.get_data()`.

2. **Obtenez le bon secret.** Appelez [Récupérer un webhook](/fr/docs/api-reference/webhooks/get/), qui renvoie le `secret` actuel. Dans le tableau de bord, le secret n’est affiché qu’une seule fois : si vous l’avez perdu, ouvrez le webhook, choisissez **Webhook secret** pour le réinitialiser et mettez immédiatement à jour votre variable d’environnement.

3. **Envoyez un événement de test.** Sur la page du webhook, choisissez **Send test** et sélectionnez un type d’événement. Le test est signé avec le même secret, et la boîte de dialogue affiche le code de statut et le corps de réponse de votre endpoint.

4. **Vérifiez votre tolérance d’horloge.** Si vous rejetez les horodatages anciens pour empêcher les rejeux, accordez une marge de quelques minutes et gardez l’horloge de votre serveur synchronisée. Chaque tentative de livraison, nouvelles tentatives comprises, porte un nouvel horodatage.

5. **Relancez ce qui a échoué.** Une fois la vérification fonctionnelle, choisissez **Retry failed** pour renvoyer les livraisons en échec des 7 derniers jours.

Pour du code dans d’autres langages, consultez [Signature des requêtes de webhook](/fr/docs/webhooks/request-signature/). Pour le format du corps, consultez [Requêtes de webhook](/fr/docs/webhooks/webhook-requests/).

## Le problème persiste ?

[Contactez le support](/contact/) ou posez votre question sur [Discord](https://discord.emailit.com). Indiquez l’ID du webhook (`wh_…`) et l’ID d’un événement en échec, jamais le secret.

---
Source: https://emailit.com/fr/docs/kb/webhook-signature-mismatch/
