Přejít na obsah
Dokumentace

Řešení problémů

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.

Aktualizováno 1. 10. 2026

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:

    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, 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. Formát těla popisuje stránka Požadavky webhooků.

Stále si nevíte rady?

Napište podpoře nebo se zeptejte na Discordu. Uveďte ID webhooku (wh_…) a ID neúspěšné události, nikdy ne tajný klíč.

Byla tato stránka užitečná?

Děkujeme za zpětnou vazbu.

Děkujeme, čteme každou zprávu.