# Proč podpis webhooku nesouhlasí?

> Opravte selhání ověření podpisu webhooků. Podepište surové tělo požadavku celým tajným klíčem whsec_ a hodnotou X-Emailit-Timestamp.

Tento článek pomůže, když váš endpoint odmítá webhooky Emailitu, protože podpis, který spočítáte, neodpovídá hlavičce `X-Emailit-Signature`. Téměř vždy se bajty, které podepisujete, nebo tajný klíč, který používáte, liší od toho, co použil Emailit.

## Příznaky

- Vaše obsluha vrací u každého webhooku `401` nebo `400` s „invalid signature“, včetně událostí **Send test** z webového rozhraní.
- Požadavky na kartě **Requests** u webhooku ukazují neúspěšné pokusy s chybovým stavovým kódem vašeho endpointu.
- Ověření funguje v jednotkovém testu s ručně vytvořeným obsahem, ale u skutečných doručení selhává.

## Příčina

Emailit podepisuje každý požadavek takto:

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

Podpis selže, když se liší jeden ze tří vstupů:

- **Tělo bylo znovu serializováno.** Frameworky často zpracují JSON dřív, než se spustí vaše obsluha. Volání `JSON.stringify(req.body)` vytvoří jiné mezery a jiné pořadí klíčů než bajty, které Emailit odeslal. Tělo je **pole** JSON až se 100 událostmi, takže kód, který očekává objekt, ho může také přetvořit.
- **Tajný klíč je chybný.** Použijte celou hodnotu včetně předpony `whsec_`. Každý webhook má vlastní tajný klíč a jeho výměnou stará hodnota okamžitě přestane platit, i pro čekající opakování.
- **Časové razítko chybí nebo se liší.** Použijte přesnou hodnotu hlavičky `X-Emailit-Timestamp` a spojte ji s tělem jedinou tečkou.
- **Porovnání je chybné.** Hlavička je hexadecimální řetězec malými písmeny, ne Base64, a nemá předponu `sha256=`.
- **Tělo změnilo něco před vaší aplikací**, například proxy, která ho překóduje nebo dekomprimuje.

## Řešení

1. **Čtěte surové tělo požadavku.** Zachyťte bajty dřív, než se spustí jakýkoli parser JSON. V Expressu připojte `express.raw()` jen na trasu webhooku:

```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);
});
```

   V route handlerech Next.js použijte `await request.text()`. V Laravelu použijte `$request->getContent()`. Ve Flasku použijte `request.get_data()`.

2. **Získejte správný tajný klíč.** Zavolejte [Načtení webhooku](/cs/docs/api-reference/webhooks/get/), které vrací aktuální `secret`. Ve webovém rozhraní se tajný klíč zobrazí jen jednou, takže pokud jste ho ztratili, otevřete webhook, zvolte **Webhook secret** a klíč vyměňte a hned upravte proměnnou prostředí.

3. **Odešlete testovací událost.** Na stránce webhooku zvolte **Send test** a vyberte typ události. Test je podepsaný stejným tajným klíčem a dialogové okno ukazuje stavový kód a tělo odpovědi vašeho endpointu.

4. **Zkontrolujte toleranci času.** Pokud kvůli ochraně proti útokům opakováním odmítáte stará časová razítka, povolte odchylku několika minut a udržujte hodiny serveru synchronizované. Každý pokus o doručení, včetně opakování, nese nové časové razítko.

5. **Zopakujte, co selhalo.** Jakmile ověření funguje, zvolte **Retry failed** a znovu odešlete neúspěšná doručení za posledních 7 dní.

Kód v dalších jazycích najdete na stránce [Podpis požadavku webhooku](/cs/docs/webhooks/request-signature/). Formát těla popisuje stránka [Požadavky webhooků](/cs/docs/webhooks/webhook-requests/).

## Stále si nevíte rady?

[Napište podpoře](/contact/) nebo se zeptejte na [Discordu](https://discord.emailit.com). Uveďte ID webhooku (`wh_…`) a ID neúspěšné události, nikdy ne tajný klíč.

---
Zdroj: https://emailit.com/cs/docs/kb/webhook-signature-mismatch/
