Vérifier une livraison
`verifyWebhookSignature` : à temps constant, avec une fenêtre de rejeu, et l'événement analysé en retour.
Dans un gestionnaire de requêtes
Une URL de webhook est publique. N'importe quoi sur Internet peut y envoyer en POST du JSON de la bonne forme : un gestionnaire qui lit payload.type sans vérifier la signature est donc une API d'écriture ouverte.
import { verifyWebhookSignature } from '@openemail/sdk' export async function POST(request: Request) { try { const event = await verifyWebhookSignature({ payload: await request.text(), headers: request.headers, secret: process.env.OPENEMAIL_WEBHOOK_SECRET!, toleranceSeconds: 300, }) console.log(event.type, event.data) } catch { return new Response('bad signature', { status: 400 }) } return new Response(null, { status: 204 })}Transmettez le corps BRUT. L'analyser puis le re-sérialiser modifie l'ordre des clés et les espaces, et la signature ne correspondra plus. headers accepte un objet Headers ou un objet simple comme le req.headers de Node, et X-OpenEmail-Signature est trouvé quelle que soit sa casse.
Deux points dont cette vérification s'occupe et qu'un contrôle écrit à la main néglige d'ordinaire : elle compare le MAC en temps constant, si bien qu'on ne peut pas en retrouver le préfixe correct en mesurant le temps de réponse, et elle rejette une livraison décalée de plus de toleranceSeconds dans un sens comme dans l'autre — cinq minutes sauf indication contraire —, de sorte qu'une requête capturée ne reste pas rejouable indéfiniment. toleranceSeconds: 0 désactive le contrôle anti-rejeu. Ces deux bogues sont silencieux. Un gestionnaire qui présente l'un ou l'autre passe tous les tests que vous auriez l'idée d'écrire.
Elle lève une exception à chaque échec : en-tête X-OpenEmail-Signature absent, en-tête qui ne respecte pas la forme t=<seconds>,v1=<hex>, horodatage hors de la fenêtre, ou signature qui ne correspond pas. En cas de succès, elle résout avec le corps analysé sous forme de WebhookPayload : il n'y a donc pas de second JSON.parse à rater. Passez un argument de type tel que EmailOpenedData pour typer data.
Elle nécessite globalThis.crypto.subtle, que fournissent aussi bien Node 20+ que Bun, Deno et Cloudflare Workers.