전달 검증하기
`verifyWebhookSignature`: 재전송 허용 창을 둔 상수 시간 비교, 그리고 파싱된 이벤트 반환.
요청 핸들러에서
웹훅 URL은 공개되어 있습니다. 인터넷에 있는 누구든 형태만 맞는 JSON을 그 주소로 POST할 수 있으므로, 서명을 확인하지 않고 payload.type을 읽는 핸들러는 누구에게나 열린 쓰기 API입니다.
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 })}원본 본문을 그대로 전달하십시오. 파싱했다가 다시 직렬화하면 키 순서와 공백이 달라져 서명이 맞지 않습니다. headers는 Headers 객체나 Node의 req.headers 같은 평범한 객체를 받으며, X-OpenEmail-Signature는 대소문자와 무관하게 찾아냅니다.
직접 구현한 검사에서 보통 빠지는 두 가지를 이 함수가 처리합니다. MAC을 상수 시간으로 비교하므로 타이밍으로 올바른 접두사를 알아낼 수 없고, toleranceSeconds(따로 지정하지 않으면 5분)보다 앞뒤로 벗어난 오래된 전달을 거부하므로 가로챈 요청을 영원히 재전송할 수 없습니다. toleranceSeconds: 0으로 두면 재전송 검사가 꺼집니다. 두 결함 모두 조용합니다. 둘 중 하나를 안고 있는 핸들러도 여러분이 떠올릴 만한 테스트는 전부 통과합니다.
실패하면 언제나 예외를 던집니다. X-OpenEmail-Signature 헤더가 없거나, t=<seconds>,v1=<hex> 형식이 아니거나, 타임스탬프가 허용 창을 벗어났거나, 서명이 맞지 않는 경우입니다. 성공하면 본문을 WebhookPayload로 파싱한 결과로 resolve되므로 두 번째 JSON.parse를 잘못할 일이 없습니다. data에 타입을 붙이려면 EmailOpenedData 같은 타입 인자를 전달하십시오.
globalThis.crypto.subtle이 필요하며, Node 20+, Bun, Deno, Cloudflare Workers가 모두 제공합니다.