Saltar para a documentação
SDK

Verificar uma entrega

`verifyWebhookSignature`: tempo constante, com uma janela de replay, e o evento já analisado de volta.

Num handler de pedidos

Um URL de webhook é público. Qualquer coisa na internet lhe pode fazer POST de JSON com a forma certa, por isso um handler que lê payload.type sem verificar a assinatura é uma API de escrita aberta.

webhook-handler.ts
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 })}

Passe o corpo EM BRUTO. Analisá-lo e voltar a serializá-lo altera a ordem das chaves e os espaços, e a assinatura não vai corresponder. headers aceita um objeto Headers ou um objeto simples como o req.headers do Node, e X-OpenEmail-Signature é encontrado seja qual for a caixa das letras.

Duas coisas que isto trata e que uma verificação feita à mão costuma não tratar: compara o MAC em tempo constante, para que o prefixo correto não possa ser recuperado cronometrando-o, e rejeita uma entrega com mais de toleranceSeconds em qualquer das direções, cinco minutos salvo indicação em contrário, para que um pedido capturado não seja reproduzível para sempre. toleranceSeconds: 0 desliga a verificação de replay. Ambos os bugs são silenciosos. Um handler com qualquer um deles passa todos os testes que lhe ocorreria escrever.

Lança em todas as falhas: um cabeçalho X-OpenEmail-Signature em falta, um que não esteja na forma t=<seconds>,v1=<hex>, um timestamp fora da janela, ou uma assinatura que não corresponde. Em caso de sucesso resolve para o corpo analisado como um WebhookPayload, por isso não há um segundo JSON.parse para errar. Passe um argumento de tipo como EmailOpenedData para tipar data.

Precisa de globalThis.crypto.subtle, que o Node 20+, o Bun, o Deno e os Cloudflare Workers fornecem todos.