콘텐츠로 이동

웹훅 모범 사례

빠르게 응답하고 나중에 처리하기

섹션 제목: “빠르게 응답하고 나중에 처리하기”

서명을 검증하고, 이벤트를 큐에 넣은 뒤(데이터베이스 행, 큐, 백그라운드 작업) 즉시 2xx를 반환하세요. SpacePost는 최대 15초까지만 기다리며, 느린 핸들러는 재시도와 중복으로 이어집니다.

전달은 최소 한 번(at least once) 보장됩니다. 처리한 전달마다 webhook-id를 저장하고(고유 제약 조건이면 충분합니다) 반복된 것은 건너뛰세요.

const inserted = await db.insert(processedWebhooks).values({ id: webhookId }).onConflictDoNothing().returning();
if (inserted.length === 0) return new Response("duplicate", { status: 200 });

원본(raw) 본문을 사용하고, 5분보다 오래된 타임스탬프는 거부하고, 서명은 일정 시간(constant time) 비교로 확인하세요(SDK와 라이브러리가 처리합니다). 서명 검증을 참고하세요.

이벤트는 특히 재시도 이후에 순서가 바뀌어 도착할 수 있습니다. 페이로드의 created_at을 저장된 값과 비교하고, 상태를 앞으로 진행시키는 변경만 적용하세요. SpacePost의 last_event도 같은 규칙을 따릅니다.

이벤트 일반적인 대응
email.bounced 데이터베이스에서 해당 주소를 유효하지 않음으로 표시하고 더 이상 보내지 않습니다.
email.complained 필수가 아닌 메일은 즉시 해당 사용자의 수신을 거부 처리합니다.
email.delivery_delayed 보통은 아무것도 하지 않습니다. 비밀번호 재설정처럼 시간이 중요한 이메일이면 알림을 보냅니다.
email.failed data.failed.reason을 기록하고 알림을 보내며, 필요하면 수정 후 재시도합니다.
email.suppressed 수신자가 발송 제외 목록에 있었습니다. 관리자 UI에 표시하세요.

처리하는 이벤트만 구독하세요. 이벤트가 적을수록 트래픽과 실패도 줄어듭니다.

  • 유효한 인증서와 함께 HTTPS로 제공하세요.
  • 시크릿이나 개인 정보가 담긴 전체 페이로드를 로그에 남기지 마세요.
  • 다른 동작이 없는 전용 경로를 사용하고 일반적인 본문을 반환하세요.
  • signing_secret은 소스 관리가 아니라 시크릿 매니저에 보관하세요.

대시보드의 전달 로그를 확인하거나 반복되는 실패에 알림을 설정하세요. 엔드포인트가 약 27시간 넘게 중단되면 모든 재시도가 소진되며, 그 뒤에는 직접 재전송해야 합니다.