GitHub 트렌딩 딥다이브 · 2026-06-24 · TrendShift Topics(Self-hosted)

whook 딥다이브
— 웹훅은 단 한 번, 내가 통제 못 하는 순간에 도착한다. 그걸 먼저 '안전히 받아 두고' 재시도·리플레이까지 해 주는 Go 게이트웨이

edaywalid/whook. 내 앱 앞단에 놓이는 셀프호스팅 웹훅 게이트웨이다. Stripe·GitHub 같은 곳에서 보낸 웹훅을 받는 즉시 영구 저장(durable)하고 빠른 200을 돌려준 뒤, 지수 백오프 재시도·dead-letter·여러 목적지로의 fan-out·리플레이(재전송)를 책임진다. 내 앱이 배포 중이거나 잠깐 죽어도 이벤트가 사라지지 않는다. CGO_ENABLED=0순수 Go 단일 정적 바이너리로, SQLite를 내장하고(필요 시 Postgres로 무중단 교체) docker run 한 줄이면 뜬다. (저장소: edaywalid/whook · Go ~5,900줄 · 라이선스 MIT · 스택 net/http · modernc SQLite · pgx · Prometheus · 신생 2026-06 시작, 스타 약 41 — TrendShift Self-hosted 토픽)

목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석 (웹훅 하나가 안전히 전달되기까지)
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 시스템 요구사항 · 옵션
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

"웹훅을 먼저 안전하게 받아 두고(capture), 전송은 내 스케줄로 천천히·끈질기게(delivery) — 둘을 분리한다."

한 줄로

whook는 우편물을 대신 받아 주는 '안전한 사서함'이다 — 택배(웹훅)가 왔는데 내가 집에 없어도(앱이 죽어 있어도) 사서함이 먼저 받아 보관하고, 내가 받을 수 있을 때 끈질기게 다시 갖다준다.

Stripe·GitHub 같은 서비스가 보내는 웹훅은 단 한 번, 내가 통제할 수 없는 순간에 도착합니다. 그때 내 앱이 배포 중이거나 잠깐 죽어 있으면 그 이벤트는 영영 사라지고(많은 서비스가 재시도가 약하거나 없음), 실패해도 원본 요청이 이미 없어 디버깅이 깜깜이가 됩니다. whook는 앱 앞단에 서서 오는 즉시 영구 저장하고 곧장 200을 돌려준 다음, 실제 전달은 자기 페이스로 재시도·dead-letter·여러 목적지 분배·리플레이까지 책임집니다.

용어
웹훅(webhook) / 게이트웨이 / durable capture
웹훅은 어떤 일이 생겼을 때(결제 성공 등) 서비스가 내 URL로 보내는 HTTP POST 알림입니다. 게이트웨이는 내 앱 앞에 서서 그 알림을 가로채 저장·재시도·분배하는 중간 계층입니다. durable capture(영속 캡처)는 보낸 쪽에 "잘 받았다(200)"고 답하기 전에 요청을 먼저 디스크(DB)에 저장하는 것 — 그래야 그 직후 내 앱이 죽어도 이벤트가 사라지지 않습니다.

2왜 주목받는가

"실무 페인포인트 정조준 + 단일 Go 바이너리 + 'Hookdeck이 하는 일을 셀프호스팅으로'."

정조준한 문제

"이벤트 유실"과 "깜깜이 디버깅"

웹훅은 재현이 어렵고, 실패하면 원본이 남지 않아 무엇이 왔는지조차 모릅니다. whook는 오는 모든 요청·모든 시도를 기록해 API/UI에서 조회·재전송하게 합니다. 개발자라면 한 번쯤 당해 본 고통을 정확히 겨냥했다는 점이 트렌딩의 이유입니다.

배포가 쉽다
의존성 없는 단일 정적 바이너리

CGO_ENABLED=0 + 순수 Go SQLite 드라이버(modernc)라 외부 런타임·DB 서버 없이 한 파일로 돕니다. go install ...@latest 또는 docker run ghcr.io/edaywalid/whook 한 줄이면 끝. 트래픽이 커지면 같은 인터페이스 뒤에서 Postgres로 무중단 교체합니다 — "작게 시작해 점진적으로 확장"의 모범.

기능 깊이
유실 0 · fan-out · 리플레이 · 감사추적

capture-before-ack로 이벤트 유실 0, ② 하나의 이벤트를 billing·email·analytics 등 여러 목적지에 독립 트랙(fan-out)으로 전달(한 곳 실패가 다른 곳을 막지 않음), ③ 저장된 과거 이벤트를 같은 큐에 다시 넣는 리플레이(버그 재현·복구), ④ 모든 시도를 1행씩 기록하는 감사추적까지 — 상용 SaaS(Hookdeck)가 하는 일을 셀프호스팅 단일 바이너리로 담았습니다.

경쟁 제품과의 위치

Svix·Hookdeck·ngrok과 무엇이 다른가

Svix는 강력하지만 SaaS 중심이고 주로 "내가 남에게 보내는" 아웃바운드 발신 플랫폼입니다. Hookdeck은 같은 카테고리(인바운드 수신·재시도·리플레이)지만 상용 SaaS입니다. ngrok·smee.io는 로컬 개발용 터널이라 영속 저장·재시도·dead-letter가 없습니다. whook는 이들과 달리 MIT·셀프호스팅·단일 바이너리의 프로덕션 인바운드 게이트웨이라는 빈자리를 노립니다.

기대치 조정 — 솔직한 현실
설계는 알차지만, 아직 '신생·1인·무릴리스'

2026년 6월 시작한 신생 프로젝트로 스타 약 41개, 사실상 1인 개발, 정식 릴리스/버전 태그가 없어(@latest/:latest로만 배포) 안정성 보증은 아직 약합니다. 빌트인 서명 검증도 Stripe·GitHub 2종뿐입니다(나머지는 직접 Verifier 등록). 다만 코드 밀도·테스트(17개 테스트 파일)·문서·CI가 갖춰져 "장난감"이 아니라 설계 밀도 높은 양질의 소형 프로젝트이며, 학습 교재로서의 가치가 특히 큽니다.

3기술 스택 전체 지도

"백엔드는 표준 라이브러리(net/http)만으로. 프레임워크 없음. 대시보드는 SPA가 아니라 Go html/template 서버렌더."

① 백엔드 — Go (게이트웨이 본체)

기술역할 / 메모
Go 1.25모듈 github.com/edaywalid/whook
net/http (표준)HTTP 서버. chi/gin/echo 없음 — Go 1.22+ 메서드 패턴 라우팅(POST /ingest/{source}, r.PathValue()) 활용
modernc.org/sqlite순수 Go SQLite(cgo 불필요). 기본 스토리지
jackc/pgx v5Postgres 드라이버(수평 확장용)
prometheus/client_golang메트릭(/metrics): events_captured·delivery_attempts·dead_letters 등
표준 cryptoAES-256-GCM(시크릿 암호화) + HMAC-SHA256(서명 검증) + crypto/rand(ID). 외부 crypto 라이브러리 0
html/template대시보드(/ui) 서버렌더 — SPA 아님
임베드 마이그레이션dbmate 포맷 SQL을 db/embed.go로 바이너리에 임베드, 기동 시 자동 적용
혼동 주의
web/의 React는 '게이트웨이'가 아니라 '랜딩/문서 사이트'다

저장소의 web/ 폴더에 TanStack Start + React 19 + Tailwind 4(+MDX 문서)가 있지만, 이건 마케팅·문서 사이트(Netlify 배포)일 뿐입니다. 실제 게이트웨이 대시보드는 위에서 말한 Go html/template 서버렌더입니다. "프론트엔드 스택"으로 오해하지 마세요 — 본체는 100% Go입니다.

② 빌드 · 배포

구성역할 / 메모
Dockerfile멀티스테이지·멀티아치. golang:1.25-alpine에서 CGO_ENABLED=0 크로스컴파일 → alpine:3.20, non-root(uid 10001), /data 볼륨
docker-composewhook 서비스 + 선택적 postgres:16-alpine(profile postgres). /healthz healthcheck
Makefile / CIbuild/test/run/docker-* 타깃 + GitHub Actions(lint·test·GHCR 이미지 푸시) + golangci-lint
산출물GHCR 멀티아치 이미지 ghcr.io/edaywalid/whook:latest

4아키텍처 심화 분석 (웹훅 하나가 안전히 전달되기까지)

"핵심 원칙은 capture와 delivery의 완전 분리. 인제스트는 최소한만 동기로, 전송은 별도 워커가 비동기로."

POST /ingest/{source} Provider(Stripe/GitHub) ─────────────────────► [ HTTP 서버 (net/http) ] │ ① per-source 레이트리밋(토큰버킷) ② MaxBytesReader(기본 1MiB, 초과 413) ▼ [ ingest.Capturer ] ┌────────────────────┼─────────────────────┐ │ ③ 서명검증(있으면) HMAC-SHA256 │ │ 실패 → status=rejected (저장은 함) │ └────────────────────┼─────────────────────┘ ▼ ④ SaveEvent ── DEDUP(dedup_key) ─► 같은 키면 기존 id 반환 │ (durable: events 테이블에 raw 통째 저장) ┌──────────┴─────────────────────────┐ │ accepted && 신규일 때만: │ │ ⑤ dispatch.Enqueue │ │ routing.Resolve(enabled+필터통과) │ │ → 목적지마다 pending_delivery 1행 │ ◄ FAN-OUT └──────────┬─────────────────────────┘ ▼ ⑥ 빠른 200 {event_id} 반환 (rejected=401, unknown source=404) [ pending_deliveries 큐 ] (durable: state=pending|delivered|dead_letter) ▲ │ ⑦ worker.tick (1s) │ ⑨ Reschedule(next=now+backoff) ▼ │ / MarkDeadLetter / MarkDelivered [ ClaimDue: 원자적 lease ] │ SQLite: UPDATE..RETURNING(단일 writer) │ Postgres: FOR UPDATE SKIP LOCKED │ ┌─────────┴──────────┐ │ │ 워커풀(sem=동시성) │ goroutine per delivery │ ▼ ▼ │ [ delivery.Engine: POST event.Body → dest.URL ] │ ⑧ 상태분류: 2xx→성공 / 429·5xx→재시도(429는 Retry-After 우선) │ 그 외 4xx·전송오류→permanent→DEAD-LETTER └──── RecordAttempt (delivery_attempts: 모든 시도 1행 = 감사추적) [ Replay ] POST /events/{id}/replay → 같은 큐로 재투입(isReplay=true) [ Pruner ] RetentionAge 경과 이벤트 정리 (실패분은 더 오래 보존) [ Metrics ] /metrics (Prometheus)

핵심 설계 패턴 (이 레포의 학습 정수)

① capture/delivery 분리 + graceful drain: 인제스트는 "서명검증→저장→큐잉"만 동기로 하고 즉시 200을 줍니다. 실제 전송은 워커가 자기 스케줄로 수행. 종료 신호가 와도 진행 중 전송은 ctx와 독립된 workCtx에서 돌아 DrainTimeout(기본 25s)까지 끝내고 떠납니다.

② 원자적 lease로 중복 전송 방지(백엔드별 다른 전략): SQLite엔 SKIP LOCKED가 없어, 풀을 단일 커넥션으로 고정 + UPDATE ... LIMIT ... RETURNING을 한 문장으로 실행해 두 워커가 같은 행을 못 가져가게 합니다(throughput을 희생한 의도적 선택). Postgres는 FOR UPDATE SKIP LOCKED여러 워커 수평 확장. 같은 Store 인터페이스, 다른 동시성 전략이라는 대조가 압권입니다.

③ 멱등성(idempotency): (source, dedup_key) 유니크 인덱스 + insert 경쟁에서 진 쪽은 기존 행을 재조회 → "정확히 한 번 capture". dedup_key가 없으면(NULL) 충돌하지 않게 partial index를 씁니다.

④ 지수 백오프 + Retry-After + dead-letter: 백오프 계산은 순수 함수(base × mult^(attempt-1) capped, 기본 base=2s·mult=3·max=1h·maxAttempts=8). 429의 Retry-After는 초("120")와 HTTP-date 둘 다 파싱해 우선 적용, 예산 소진 시 dead-letter로 보냅니다.

5디렉토리 구조 해부

"Go 표준 레이아웃(cmd·internal). 각 책임이 한 패키지로 깔끔히 분리 — 의존성 역전의 교과서."

경로역할
cmd/whook/main.go엔트리포인트. config 로드 → app.Run. 매우 얇음(~35줄)
internal/app/app.go조립부. 모든 컴포넌트 와이어링 + 3개 고루틴 기동 + graceful shutdown. DSN 스킴으로 SQLite/Postgres 자동 선택
internal/core/types.go도메인 타입 허브. Event·Source·Destination·PendingDelivery·DeliveryAttempt·BackoffConfig·FilterSpec + 상태 enum
internal/storage/storage.go단일 영속성 인터페이스 Store + ErrNotFound
internal/storage/{sqlite,postgres}/같은 Store의 두 구현. SQLite=단일 writer+UPDATE..RETURNING, Postgres=FOR UPDATE SKIP LOCKED
internal/ingest/capture 경로: 서명검증 → SaveEvent(dedup) → Enqueue
internal/dispatch/ · routing/ · filter/큐잉 / 목적지 부분집합 결정 / JSON body 경로 필터(equals·exists·in·prefix·not)
internal/worker/전송 워커풀: claim→deliver→record→reschedule/dead-letter + drain 로직
internal/delivery/ · retry/HTTP 전송 1회 + 상태 분류(Classify) / 순수 백오프 계산
internal/signature/ · crypto/Stripe/GitHub HMAC 검증(상수시간 비교) / AES-256-GCM 시크릿 봉인
internal/{ratelimit,pruner,metrics,migrate,id}/토큰버킷 / 보존정리 / Prometheus / 임베드 마이그레이션 / prefix_16hex ID
internal/api/server.go(라우팅·admin 인증) + handlers.go(JSON API) + ui.go(html/template 대시보드)
db/migrations/{sqlite,postgres}/dbmate 포맷 스키마 SQL. db/embed.go로 임베드
web/ · docs/TanStack 랜딩/문서 사이트(별도 배포) / configuration·deployment·http-api 마크다운
구조의 미덕
"한 책임 = 한 패키지", 순수 로직은 따로

약 5,900줄 Go가 13개+ 패키지로 잘게 나뉘고, retry(백오프 계산)·routing·filter 같은 부수효과 없는 순수 로직은 별도 패키지로 떼어 테스트가 쉽습니다. 17개 테스트 파일(단위 + worker drain 통합 + postgres 통합)이 붙어 있어 "읽고 따라 만들기" 좋은 교본입니다.

6학습 포인트 (기술별 — 배울 것 + 실습 아이디어)

"표준 라이브러리 Go·DB 기반 job 큐·멱등성·백오프·graceful drain·시크릿 보안이 한 레포에 압축."

① 표준 라이브러리만으로 프로덕션 Go 서비스

배울 것: 프레임워크 없이 net/http + Go 1.22 메서드 라우팅 + html/template 서버렌더로 충분히 단단한 서비스를 짜는 법. "프레임워크가 꼭 필요하진 않다"의 모범 사례.

실습: mux.HandleFunc("POST /items/{id}", ...) + r.PathValue("id")로 작은 REST API를 프레임워크 없이 만들어 보기.

② DB 기반 durable job 큐 직접 구현

배울 것: pending_deliveries 테이블 + locked_until lease 컬럼 + 원자적 claim. SQLite(단일 writer)와 Postgres(SKIP LOCKED)에서 같은 큐를 다르게 안전하게 만드는 대조가 이 레포의 백미. Redis·Kafka 없이 DB만으로 작업 큐를 만드는 경험.

실습: 작업 테이블을 만들고 UPDATE ... WHERE state='pending' LIMIT 1 RETURNING로 워커가 하나씩 집어가게 구현. 워커 2개를 동시에 돌려 중복 처리가 없는지 확인.

③ 멱등성(idempotency) 설계

배울 것: (source, dedup_key) 유니크 인덱스 + 경쟁 패배 시 기존 행 재조회로 "정확히 한 번 capture". NULL 키는 충돌 안 하게 partial index. 분산 시스템의 핵심 개념을 작은 코드로.

실습: 같은 요청을 빠르게 두 번 보내도 한 번만 저장되게, 유니크 제약 + insert 충돌 처리로 멱등 엔드포인트를 만들기.

④ 지수 백오프 + Retry-After + dead-letter

배울 것: 재시도 간격을 순수 함수로 계산(base × mult^(n-1) capped), 429의 Retry-After(초 + HTTP-date) 존중, 예산 소진 시 dead-letter로 격리. "끈질기되 무한루프는 안 도는" 재시도의 정석.

실습: 실패하는 HTTP 호출을 백오프로 재시도하되, N회 초과 시 "데드레터 목록"에 옮기는 작은 워커를 작성.

⑤ graceful drain + 시크릿 보안

배울 것: SIGINT/SIGTERM에 새 작업 claim을 멈추고 in-flight 전송은 끝내는 우아한 종료(workCtx 독립 컨텍스트 트릭), 그리고 AES-256-GCM으로 시크릿을 at-rest 암호화 + HMAC 상수시간 비교로 타이밍 공격 방어.

실습: signal.NotifyContext로 종료 신호를 받아 "받던 작업은 마무리, 새 작업은 거절"하는 서버 종료 루틴을 구현.

⑥ 단일 정적 바이너리 + 테스트 가능한 설계

배울 것: CGO_ENABLED=0 + 순수 Go SQLite + 멀티아치 크로스컴파일 Dockerfile. 그리고 Doer 인터페이스로 HTTP 클라이언트 주입, now func() time.Time 주입으로 시간 제어 — "테스트하기 쉬운 구조"를 설계로 확보.

실습: 시간에 의존하는 함수에 now func() time.Time를 주입형으로 바꿔, 테스트에서 가짜 시계를 넣어 백오프를 단언.

7시스템 요구사항 · 옵션

"사실상 무의존. 단일 바이너리 + SQLite 파일 하나. 라즈베리파이·작은 VPS·컨테이너면 충분."

항목요구사항 / 메모
런타임단일 바이너리. 별도 런타임·의존성 없음(Alpine 베이스, -s -w 스트립)
DB기본 내장 SQLite(별도 서버 불필요). 트래픽↑ 시 Postgres 16+(postgres:// DSN)로 교체
포트 / 볼륨기본 :8080 · SQLite 영속 볼륨 /data
아키텍처멀티아치 이미지(amd64/arm64 크로스컴파일)
확장성SQLite=단일 writer라 단일 인스턴스 수직 확장. 수평 확장 필요 시 Postgres(SKIP LOCKED로 다중 인스턴스가 같은 큐 안전 소비)
주요 환경변수WHOOK_ADMIN_TOKEN(빈 값=관리API 무방비, 로컬 전용)·WHOOK_SECRET_KEY(시크릿 암호화)·WHOOK_WORKERS(4)·WHOOK_MAX_BODY_BYTES(1MiB)·WHOOK_RETENTION_AGE
함정 — 운영 전 점검
WHOOK_ADMIN_TOKEN을 비워 두면 관리 API가 무방비

관리 엔드포인트(/events·/sources·/destinations 등)는 admin 토큰으로 보호되는데, 토큰을 설정하지 않으면 인증 없이 노출됩니다(로컬 개발용 기본값). 공개망에 띄울 때는 반드시 WHOOK_ADMIN_TOKEN과 시크릿 암호화용 WHOOK_SECRET_KEY를 설정하세요. 또 /ingest는 의도적으로 인증이 없으니(웹훅 수신구), 서명 검증으로 위조를 막아야 합니다.

8직접 해볼 수 있는 실습 과제

"띄우고 첫 웹훅 → 실패 재시도 관찰 → 리플레이 → 새 서명 검증기 추가 → 미니 게이트웨이 만들기."

과제 1난이도 ★☆☆☆☆ · 입문

띄우고 첫 웹훅 흘려보내기

docker run -p 8080:8080 -v whook-data:/data ghcr.io/edaywalid/whook로 실행 후, source·destination을 만들고 POST /ingest/stripe로 가짜 이벤트를 보내 /ui 대시보드에서 이벤트·전송 시도를 확인.

목표: capture→delivery 흐름을 눈으로 보기. destination URL은 webhook.site 같은 곳으로 잡아 실제 도착을 확인.

과제 2난이도 ★★☆☆☆ · 초급

일부러 실패시켜 재시도·dead-letter 관찰

destination을 일부러 500을 주는 URL로 잡고 이벤트를 보낸 뒤, delivery_attempts(또는 UI 시도 이력)에서 지수 백오프로 간격이 벌어지는 것과, maxAttempts 후 dead-letter로 가는 걸 확인.

목표: 백오프·재시도 예산·데드레터를 실데이터로 체감. WHOOK_* 백오프 설정을 바꿔 간격 변화를 비교.

과제 3난이도 ★★★☆☆ · 중급

리플레이 + fan-out 실험

destination을 2개(예: billing·analytics) 등록해 한 이벤트가 양쪽으로 fan-out 되는지 보고, 죽었던 destination을 살린 뒤 POST /events/{id}/replay로 과거 이벤트를 재전송. ?destination_id=로 한 곳만 리플레이도 시도.

목표: fan-out(독립 트랙)과 리플레이(복구)의 가치를 체감. 한 목적지 실패가 다른 목적지를 막지 않음을 확인.

과제 4난이도 ★★★★☆ · 중상

세 번째 서명 검증기(예: Slack) 추가

internal/signature/providers.go의 Stripe/GitHub 구현을 본떠 Slack의 서명 스킴(v0:{ts}:{body} HMAC-SHA256)을 구현하고 Registry에 등록. hmac.Equal 상수시간 비교를 빠뜨리지 않게.

목표: 서명 검증·Verifier 인터페이스 구조를 손으로 이해. 실제 오픈소스 기여 후보(빌트인은 2종뿐).

과제 5난이도 ★★★★★ · 고급

'미니 웹훅 게이트웨이' 처음부터 만들기

Go 표준 라이브러리만으로 events + pending_deliveries 두 테이블 + 워커 1개를 짜서, capture-before-ack·dedup 멱등성·백오프 재시도·graceful drain을 갖춘 작은 게이트웨이를 완성. SQLite UPDATE..RETURNING lease까지 넣으면 whook의 뼈대가 거의 됩니다.

목표: "DB 기반 durable 큐"를 내 것으로. Postgres로 바꿔 SKIP LOCKED 다중 워커도 실험.

9관련 기술 심화 학습 로드맵 (5주 플랜)

"웹훅 개념에서 출발해 Go HTTP·DB 큐·재시도·동시성·배포까지, whook를 길잡이 삼아."

주차주제핵심 학습 + whook 연결점
1주웹훅 개념 + Go HTTP 기초웹훅이 뭔지, net/http 메서드 라우팅, JSON 핸들러 → internal/api·ingest
2주멱등성 + durable capture유니크 인덱스·partial index·capture-before-ack → storage·core/types
3주DB 기반 작업 큐 + 동시성lease·claim, 단일 writer vs SKIP LOCKED, 세마포어 워커풀 → worker·storage/{sqlite,postgres}
4주재시도 + graceful shutdown지수 백오프·Retry-After·dead-letter·workCtx drain → retry·delivery·app
5주보안 + 배포 + 관측HMAC 검증·AES-GCM·단일바이너리 Docker·Prometheus → signature·crypto·metrics·Dockerfile
비유

이 로드맵은 "분산 시스템의 어려운 개념(멱등성·큐·재시도·우아한 종료)을 작은 Go 코드로 손에 쥐는" 코스입니다. whook는 ~5,900줄로 작고 잘 정돈돼 있어, Kafka·Redis 같은 큰 인프라 없이도 "신뢰성 있는 이벤트 처리"가 어떻게 만들어지는지 읽을 만한 코드로 보여 줍니다 — 백엔드·인프라 입문자에게 알맞은 교재입니다.

10핵심 키워드 사전

"이 문서와 저장소에서 쓰이는 방식 그대로의 웹훅·큐·재시도·보안 용어."

용어의미
웹훅(webhook)서비스가 이벤트 발생 시 내 URL로 보내는 HTTP POST. whook이 그 수신 종단을 대신 맡음
웹훅 게이트웨이앱 앞단에서 웹훅을 가로채 저장·재시도·분배하는 중간 계층 (= whook의 정체)
durable capture보낸 쪽에 2xx 응답하기 전에 요청을 raw로 저장 → 유실 방지
멱등성(idempotency)같은 이벤트가 중복 도착해도 한 번만 처리. (source, dedup_key) 유니크 + 경쟁 패배 시 기존 id 반환
dedup key소스별 지정 헤더에서 뽑은 중복제거 값. 없으면(NULL) 절대 충돌 안 함(partial index)
지수 백오프재시도 간격을 base × mult^(n-1)로 늘리고 max로 상한(기본 2s·×3·1h·8회)
dead-letter재시도 예산 소진 시 전송을 격리 상태로 보내 자원 낭비를 멈추고 검사·리플레이 대상으로 노출
Retry-After429 응답 헤더. whook은 초("120")와 HTTP-date 둘 다 파싱해 계산 백오프보다 우선 적용
fan-out(팬아웃)한 이벤트를 매칭되는 모든 목적지에 독립 트랙으로 전달. 한 곳 실패가 다른 곳을 막지 않음
replay(리플레이)저장된 과거 이벤트를 같은 큐에 다시 넣어 재전송. 버그 재현·복구용
lease / claim워커가 큐 항목을 가져갈 때 locked_until을 미래로 세팅해 중복 처리 차단
SKIP LOCKEDPostgres에서 잠긴 행을 건너뛰고 다음 행을 잠그는 구문 → 여러 워커가 같은 큐를 안전 소비
서명 검증위조 웹훅 차단. Stripe/GitHub HMAC-SHA256 내장, hmac.Equal 상수시간 비교
at-rest 암호화저장 시 암호화. 서명 시크릿을 AES-256-GCM(WHOOK_SECRET_KEY 기반)으로 봉인
graceful drain종료 신호 시 새 작업은 멈추고 진행 중 전송은 끝냄(독립 workCtx, DrainTimeout)

11참고 링크