Files
llm-wiki/raw/branch-notes/feature-webhook-outbound-contract.md
T

28 KiB

title, source_type, status, branch, parent_branch, governing_docs, related_projects, tags, created, last_reviewed, target_merge, status_label, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, contract_packet_sha256
title source_type status branch parent_branch governing_docs related_projects tags created last_reviewed target_merge status_label id kind project work_item inherits refines overrides depends_on contract_packet contract_packet_sha256
branch / feature-webhook-outbound-contract branch-note raw feature-webhook-outbound-contract
wiki/projects/ca-tmpl/data-layer-persistence-cache-outbound
ca-skeleton
branch
ca-skeleton
security
observability
messaging
event-schema
retry-policy
2026-05-31 2026-06-29 in-progress BR-CA-SKELETON-OPERATIONAL-CONTRACT-044 project-work-item ca-skeleton-operational-contract WI-CA-SKELETON-OPERATIONAL-CONTRACT-044
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-RESILIENCE-001@1
1 b51881dc440e5170e65a78716a673c73ecce8c17900e7ee80fd4c9ba5f778a67

branch: feature-webhook-outbound-contract

Layer: raw/branch-notes/ — outbound webhook (서버 → 외부 consumer) 발송의 signature/replay/retry/observability/security 계약을 정의합니다.

범위 정합 (2026-06-29 ground-truth 대조): outbound HTTP 클라이언트의 공통 factory (OutboundHttpRestClientFactory.java) 및 설정 객체 (OutboundHttpSettings.java)는 adapter-outbound 모듈 내에 이미 구현되어 있으며 (Phase C2), 본 branch는 Webhook 발송 특유의 보안 및 신뢰성 정책을 얹기 위해 (a) Egress Proxy 설정 추가, (b) Redirect 강제 차단 설정, (c) HMAC-SHA256 서명 계산 모듈 및 (d) Full Jitter 재시도 백오프를 주입하는 구체적 구현 사양을 규정합니다.

부모 (필수)

형제 branch (cross-cite)

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 1
  • 패킷 스키마: contract_packet: 1
  • 완료 조건: signature·replay·retry·observability contract test가 통과한다

상속한 프로젝트 결정

Decision Ref Project Summary Branch Application Source
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-RESILIENCE-001@1 Resilience4j가 default이며 Spring Retry는 simple blocking retry에만 허용한다 Work Item 완료 조건에 적용 raw/project-notes/ca-skeleton-operational-contract

브랜치 지역 결정

기존 branch-local 결정은 아래 ## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.

Decision ID Decision Relation Supporting Claims Status

선언한 예외

Override ID Overrides Reason Approval Status

목표

서버가 외부 consumer 에게 webhook 을 발송할 때 다음을 임의 결정 없이 일관되게 제공해야 합니다:

  1. Signature 검증 — payload 변조 감지 (consumer 가 발송 서버를 인증)
  2. Replay protection — 동일 webhook 의 중복 수신을 consumer 가 감지/거부할 수 있는 식별자
  3. Retry semantics — consumer 의 일시 장애 시 재발송 정책 (간격 / 횟수 / DLQ)
  4. Delivery observability — 발송 시도/성공/실패의 로그/메트릭/runbook
  5. Endpoint registration / management — consumer 의 webhook URL 등록·검증·rotation 절차
  6. Payload contract — webhook body 의 envelope shape (inbound API envelope 와 다른가? versioning?)
  7. SSRF Defence — 외부 사용자가 입력한 엔드포인트 URL 호출 시 내부망 자원 보호
  • 이슈:
  • PR:

범위

포함 범위

  • webhook payload signature scheme (HMAC algorithm + header name + timestamp inclusion)
  • replay protection identifier (X-Webhook-Id UUID + 5분 skew tolerance)
  • consumer endpoint registration 절차 (https 강제, Smokescreen egress proxy 활용 SSRF 방어, redirects 차단)
  • retry policy (exponential backoff + Full Jitter, 최대 5회 시도 후 DLQ)
  • delivery status state machine 정의 (PENDING / SENT / DELIVERED / FAILED / RETRYING / DEAD_LETTERED)
  • webhook event versioning 정책 수립 (header X-Webhook-Version 지정)
  • webhook payload envelope shape 정의 (event_type, event_id, timestamp, data 구조)
  • observability 메트릭 및 로그 계약 수립 (webhook.delivery.requests, webhook.dlq.size)
  • consumer timeout 정책 결정 (최대 5초 커넥션/응답 제한)

제외 범위

근거 (필수, 최소 1개+)

Source 정당화하는 결정
raw/official-docs/stripe-webhook-signature D1, D2 — HMAC-SHA256 signature scheme 및 replay protection 5분 window 설계
raw/official-docs/github-webhook-signature D1 — X-Hub-Signature-256 헤더 명명 및 raw body HMAC 검증 설계
raw/official-docs/svix-webhook-best-practices D1, D2 — timestamp + message ID + body를 마침표(.)로 결합하는 서명 payload 포맷
raw/official-docs/rfc9421-http-message-signatures D1 대안 — IETF HTTP Message Signatures 표준 대비 단순 vendor HMAC의 한계 비교
raw/official-docs/aws-builders-retry-jitter D3 — exponential backoff와 Full Jitter 조합을 통한 재시도 폭풍 방지 설계
raw/official-docs/owasp-ssrf-prevention D4 — redirect 비활성화 및 egress proxy(Smokescreen) 활용을 통한 SSRF 방어

TODO

  • D1: HMAC-SHA256 signature scheme helper (WebhookSignatureCalculator) 구현 — 등급: planned
  • D2: client call 시 Replay protection window validation 및 타임스탬프 계산 바인딩 — 등급: planned
  • D3: Full Jitter Exponential Backoff calculator (WebhookRetryBackoffCalculator) 구현 — 등급: planned
  • D4: OutboundHttpRestClientFactory 내 Egress Proxy 및 Redirects NEVER 설정 수정 — 등급: planned
  • Registry Updates (error-codes.yaml, env-keys.yaml, headers.yaml, metrics.yaml 업데이트) — 등급: planned

진행 중 메모

  • OutboundHttpRestClientFactoryHttpClient.newBuilder().followRedirects(HttpClient.Redirect.NEVER) 설정을 명시해 JDK 기본값 의존을 줄이고, 리다이렉트 차단 계약을 테스트로 고정해야 한다.
  • OutboundHttpSettings에서 app.outbound.http.egress-proxy 설정을 Fail-fast 생성자로 검증하도록 조치할 예정이다.

결정 사항

  • 2026-06-29: HMAC-SHA256 서명 스키마 결정 / 이유: payload 변조 방지 및 consumer 수신 신뢰성 확보 / 검토한 대안: IETF HTTP Message Signatures (복잡하여 기각) / 근거: raw/official-docs/stripe-webhook-signature
  • 2026-06-29: 타임스탬프 및 Unique Message ID 기반 Replay 방지 결정 / 이유: replay attack 방지 / 검토한 대안: UUID 단독 사용 (stateful 중복 체크 비용 증가로 기각) / 근거: raw/official-docs/svix-webhook-best-practices
  • 2026-06-29: Full Jitter 백오프 재시도 및 DLQ 적용 결정 / 이유: retry storms 방지 및 consumer 부하 분산 / 검토한 대안: 단순 선형 재시도 (재장애 유발 위험으로 기각) / 근거: raw/official-docs/aws-builders-retry-jitter
  • 2026-06-29: Egress Proxy 라우팅 및 Redirect 차단 결정 / 이유: 내부 IP 노출 및 SSRF 우회 경로 축소 / 검토한 대안: Application level DNS lookup 검증 (DNS rebinding 취약성으로 기각) / 근거: raw/official-docs/owasp-ssrf-prevention

결정-근거 매핑

Decision ID Decision 선택 조건 (언제 이 결정 / 언제 대안) Supporting Claims Evidence Strength Open Risk
D1 HMAC-SHA256 서명 스키마 (X-Webhook-Signature: t=...,v1=...) 일반 B2B/B2C webhook 아웃바운드 발송에 기본 적용 raw/official-docs/stripe-webhook-signature.md#STRIPE-WEBHOOK-C3, raw/official-docs/stripe-webhook-signature.md#STRIPE-WEBHOOK-C4, raw/official-docs/github-webhook-signature.md#GITHUB-WEBHOOK-C3, raw/official-docs/svix-webhook-best-practices.md#SVIX-WEBHOOK-C2 official-vendor-doc + project-local-convention X-Webhook-Signature 헤더명과 Hex 인코딩은 프로젝트 로컬 convention 이므로 consumer 문서/샘플과 동기화 필요
D2 Replay protection & Message ID replay attack 및 수신 멱등성 보장이 필수적인 금융/결제/주요 상태 동기화 webhook raw/official-docs/stripe-webhook-signature.md#STRIPE-WEBHOOK-C7, raw/official-docs/svix-webhook-best-practices.md#SVIX-WEBHOOK-C2 official-vendor-doc 송수신 시스템 간 clock skew 오차 (5분 초과 시 실패)
D3 Retry backoff with Full Jitter 아웃바운드 비동기 발송의 일시적 장애 복원력이 필요할 때 기본 적용 raw/official-docs/aws-builders-retry-jitter.md#AWS-JITTER-C4, raw/official-docs/aws-builders-retry-jitter.md#AWS-JITTER-C7 official-vendor-doc 재시도 중 지연 시간 증가로 인한 즉각성 저하
D4 SSRF 방어 및 리다이렉트 차단 외부 사용자가 등록하는 임의의 URL 엔드포인트 호출 시 기본 적용 raw/official-docs/owasp-ssrf-prevention.md#OWASP-SSRF-C3, raw/official-docs/owasp-ssrf-prevention.md#OWASP-SSRF-C4, raw/official-docs/owasp-ssrf-prevention.md#OWASP-SSRF-C2 official-standard egress proxy 추가 인프라 비용 및 단일 장애점(SPOF) 위험

구현 가이드

본 구현 가이드는 adapter-outbound 모듈 내 HTTP 클라이언트 팩토리와 설정 파일에 대한 구체적인 수정 방향과 설계 규칙을 정의합니다. (R1, R2, R3, R4 준수)

1. HTTP Client 및 Egress Proxy 설정 수정 (D4, OWASP-SSRF-C2, C3)

  • 수정 대상 파일:
    • src/adapter-outbound/src/main/java/dev/caskeleton/adapter/outbound/httpclient/OutboundHttpSettings.java
    • src/adapter-outbound/src/main/java/dev/caskeleton/adapter/outbound/httpclient/OutboundHttpRestClientFactory.java
  • UNSUPPORTED_IMPL_DECISION:
    • java.net.http.HttpClient를 빌드할 때, app.outbound.http 설정 하위에 egress-proxy 설정을 결합하여 ProxySelector를 직접 바인딩하도록 설계함 / trade-off: Spring Cloud Gateway 등의 전역 프록시 설정을 타지 않고, 외부 아웃바운드 템플릿용 RestClient만 격리하여 프록시를 태움으로써 내부 통신(Kafka, DB 등)이 프록시 영향으로 단절되는 것을 방지함.
    • followRedirects(HttpClient.Redirect.NEVER)를 명시적으로 호출함 / trade-off: java.net.http.HttpClient 기본값도 Redirect.NEVER로 확인되어 보안 요구에 부합하지만, 코드 리뷰와 회귀 테스트에서 redirect 차단 계약이 드러나도록 명시성을 선택함.
  • 수정 사양:
    • OutboundHttpSettings 레코드에 boolean egressProxyEnabled, String egressProxyHost, Integer egressProxyPort 필드를 추가하고, compact constructor에서 egressProxyEnabledtrue일 때 host 및 port의 null/blank/범위 초과 여부를 Fail-Fast로 검증함.
    • OutboundHttpRestClientFactory.create 메서드를 다음과 같이 리다이렉트 차단 및 프록시 주입이 가능하도록 수정함:
      // dev.caskeleton.adapter.outbound.httpclient.OutboundHttpRestClientFactory.java
      static Clients create(String dependencyName, String baseUrl, OutboundHttpSettings settings) {
        HttpClient.Builder builder = HttpClient.newBuilder()
            .connectTimeout(settings.connectTimeout())
            .followRedirects(HttpClient.Redirect.NEVER); // D4: Redirects disabled
      
        // D4: Route all outbound requests through Smokescreen Egress Proxy if enabled
        if (settings.egressProxyEnabled()) {
          builder.proxy(ProxySelector.of(
              new InetSocketAddress(settings.egressProxyHost(), settings.egressProxyPort())
          ));
        }
      
        HttpClient httpClient = builder.build();
        JdkClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory(httpClient);
        requestFactory.setReadTimeout(settings.readTimeout());
        // rest client 빌드 생략...
      }
      

2. Webhook 서명 생성기 구현 (D1, D2, STRIPE-WEBHOOK-C4, SVIX-WEBHOOK-C2)

  • 신규 추가 클래스:
    • dev.caskeleton.adapter.outbound.httpclient.webhook.WebhookSignatureCalculator (application layer 또는 outbound helper)
  • UNSUPPORTED_IMPL_DECISION:
    • 서명 대상 payload 조립 시 JSON Body of serialization 형태 변형으로 인한 서명 깨짐을 막기 위해, 반드시 RestClient에서 송신하기 직전의 raw byte array를 그대로 활용하도록 서명 계산 유틸을 바이트 단위로 설계함.
    • 서명 헤더명을 X-Webhook-Signature로 정하고, 서명 결과를 Hex 문자열로 인코딩함 / trade-off: Stripe/GitHub/Svix 문서는 HMAC-SHA256과 raw payload 기반 서명을 뒷받침하지만, Svix는 Base64 인코딩을 사용하므로 Hex vs Base64 및 자체 헤더명은 프로젝트 로컬 convention 으로 문서화하고 consumer 검증 샘플을 함께 제공해야 함.
  • 서명 조립 알고리즘:
    • SignaturePayload (bytes) = (X-Webhook-Id + "." + X-Webhook-Timestamp + ".").getBytes(StandardCharsets.UTF_8) + rawBodyBytes
    • 이 페이로드를 shared secret key(HMAC-SHA256)로 해싱하고, 결과값을 프로젝트 로컬 convention 인 16진수(Hexadecimal) 문자열로 변환하여 헤더에 바인딩함.
    • 서명 헤더 구조: X-Webhook-Signature: t=1672531199,v1=a1b2c3d4...

3. Full Jitter 백오프 계산식 구현 (D3, AWS-JITTER-C4)

  • 신규 추가 클래스:
    • dev.caskeleton.adapter.outbound.httpclient.webhook.WebhookRetryBackoffCalculator
  • 백오프 수식:
    • Interval = \text{random}(0, \min(\text{cap}, \text{base} \times 2^{\text{attempt}}))
    • base = 10,000ms (10초), cap = 3,600,000ms (1시간), maxAttempts = 5
    • Java 구현 예시:
      public static long calculateBackoff(int attempt, long baseMs, long capMs) {
        long temp = Math.min(capMs, baseMs * (1L << attempt));
        return ThreadLocalRandom.current().nextLong(0, temp);
      }
      

엣지·실패·의존

  • 실패·엣지 경로:
    • Egress Proxy 장애 (SPOF): Smokescreen 프록시가 다운되는 경우 모든 외부 웹훅 발송이 즉시 차단됨. 이 경우 retryable 에러(WEBHOOK_DELIVERY_FAILED)로 로깅 및 메트릭 기록을 남겨 재시도 큐에 보관해야 함.
    • Redirect 우회 시도: 수신 서버가 정상적인 퍼블릭 IP를 제공한 후, HTTP 응답 시 302 Found 등의 리다이렉션을 반환하여 내부 http://169.254.169.254로 우회를 유도할 때, HTTP 클라이언트가 리다이렉션 추적을 금지(Redirect.NEVER)했으므로 302 응답을 그대로 받아 WEBHOOK_REDIRECT_BLOCKED 에러로 격리하고 전송을 영구 중단함.
    • Clock Skew 엣지: 송신 서버와 수신 서버의 NTP 동기화가 깨져 시각 차이가 5분을 초과하는 경우 서명 검증은 통과하나 타임스탬프 스큐 검증에서 거절당함. 이를 모니터링하기 위해 X-Webhook-Timestamp 값이 수신 측 시간 대비 300초 이상 벗어난 경우의 예외 처리를 디버깅할 수 있도록 로깅해야 함.
  • 다른 계약 의존:

검증해야 할 주장

Claim Why uncertain How to verify Status
HttpClient의 followRedirects(Redirect.NEVER)가 실제로 3xx 리다이렉션을 따라가지 않고, 3xx 응답을 WEBHOOK_REDIRECT_BLOCKED로 매핑할 수 있는가 JDK 기본값은 Redirect.NEVER로 확인됐지만, RestClient/JdkClientHttpRequestFactory 조합에서 응답 처리 경로를 프로젝트 테스트로 고정해야 함 Testcontainers에 MockWebServer를 띄우고 301/302 Redirect 응답을 던져 리다이렉션을 따라가지 않으며 3xx 응답을 차단 에러로 매핑하는지 JUnit 테스트로 검증 planned
Smokescreen Egress Proxy가 사설 IP 대역 호출 시도를 정책대로 차단하고 차단 응답을 반환하는가 프록시 룰셋이 잘못 설정되어 우회 경로가 존재할 위험이 있음 로컬 docker-compose에 Smokescreen을 띄우고 http://10.0.0.1로의 웹훅 발송이 프록시에 의해 차단됨을 확인 planned
Full Jitter Exponential Backoff 난수 분포가 편향 없이 고르게 분포하는가 Java의 ThreadLocalRandom 사용 시 특정 스레드 경쟁 조건에서 Jitter가 편향되어 스파이크 부하를 일으킬 수 있음 시뮬레이션을 통해 1,000회 재시도 대기시간의 표준 편차 및 분포 균일성을 검증 planned
shared secret key rotation 시 헤더에 다중 서명이 들어올 때 수신 측이 순회하며 성공적으로 하나라도 매칭하는가 다중 서명 파싱 및 서명 목록 추출 파서가 예외를 던질 위험이 있음 두 개 이상의 active secret을 임의로 생성하고 파싱 로직을 통과하는지 검증 planned

관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)

2026-06-29 보정: governing_docs는 현재 존재하는 outbound HTTP canonical인 wiki/projects/ca-tmpl/data-layer-persistence-cache-outbound를 가리킨다. 아래 표는 webhook outbound 세부 관심사 초안이며, /coverage feature-webhook-outbound-contract 재실행으로 canonical 요구사항 대비 covered/delegated/missing 판정을 갱신해야 한다.

관심사 상태 owner 심각도 근거
C1: HMAC-SHA256 서명 알고리즘 설계 및 구현 covered-here D1
C2: Replay attack 방지를 위한 타임스탬프 결합 포맷 covered-here D2
C3: Full Jitter Exponential Backoff 공식 covered-here D3
C4: Egress Proxy (Smokescreen) 라우팅 주입 covered-here D4
C5: HTTP Client Redirect 강제 차단 covered-here D4
C6: DB 기반 Key Rotation 24시간 오버랩 윈도우 delegated raw/branch-notes/feature-security-operational-baseline OK §엣지·의존 링크
C7: 비동기 발송 멱등성 및 DLQ 아키텍처 delegated raw/branch-notes/feature-background-job-async-contract OK §엣지·의존 링크

Audit & Findings (2026-06-29 ground-truth 대조)

  • FINDING-1 — Outbound HTTP Client 내 Redirect / Proxy 바인딩 코드 부재:
    • ca-tmpl 의 src/adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/OutboundHttpRestClientFactory.java:21 을 확인한 결과, 단순히 HttpClient.newBuilder().connectTimeout(settings.connectTimeout()).build() 로 HTTP 클라이언트를 생성하고 있음. (2026-07-21 실물 소스에서 재확인. 원래 인용은 repomix 덤프 ca-tmpl코드내용.xml 의 병합 행번호 L39733-39735 를 가리켰으나, 덤프는 재생성 시 행번호가 바뀌는 일회성 산출물이라 정본 경로로 교체.)
    • 리다이렉트 정책은 JDK 기본값(Redirect.NEVER)에 의존해도 요구를 만족할 수 있으나, 코드에 명시되어 있지 않아 보안 계약이 리뷰/테스트 표면에 드러나지 않는다. Egress Proxy 설정을 바인딩하는 builder.proxy(...) 코드는 누락된 상태임.
    • 권고: 본 branch note의 §구현 가이드 1에 명시된 대로 OutboundHttpSettingsOutboundHttpRestClientFactory 에 Egress Proxy 바인딩을 추가하고, redirect 차단은 명시 설정 + 테스트로 회귀를 방지해야 함.
  • FINDING-2 — Webhook 관련 에러 코드 및 레지스트리 설정 부재:
    • docs/registries/error-codes.yaml 에 webhook 전송 실패, SSRF 차단, 리다이렉트 차단과 관련된 에러 코드가 정의되지 않음.
    • 권고: 본 branch note의 §Registry Updates 에 정의된 신규 YAML 설정을 레지스트리 파일에 통합해야 함.

Registry Updates (자체 명세)

본 branch merge 시, docs/registries/ 하위 파일들에 아래 항목을 반드시 추가/업데이트해야 합니다.

1. docs/registries/error-codes.yaml

  # ============================================================
  # WEBHOOK OUTBOUND (feature-webhook-outbound-contract)
  # ============================================================
  - code: WEBHOOK_DELIVERY_FAILED
    category: TRANSIENT_DEPENDENCY
    http_status: 500
    retryable: true
    retry_after_seconds: 10
    owner_branch: feature-webhook-outbound-contract
    owner_layer: infrastructure
    client_safe_message: "Webhook delivery attempt failed. Retrying..."
    log_level: WARN
    runbook_link: "runbook://webhook/delivery-failed"
    compatibility_impact: none
    required_test: contract-verification:webhook-retry-policy

  - code: WEBHOOK_SSRF_BLOCKED
    category: CONFLICT
    http_status: 400
    retryable: false
    retry_after_seconds: null
    owner_branch: feature-webhook-outbound-contract
    owner_layer: infrastructure
    client_safe_message: "Webhook target endpoint blocked due to SSRF policy"
    log_level: ERROR
    runbook_link: "runbook://webhook/ssrf-blocked"
    compatibility_impact: none
    required_test: contract-verification:webhook-ssrf-prevention

  - code: WEBHOOK_REDIRECT_BLOCKED
    category: CONFLICT
    http_status: 400
    retryable: false
    retry_after_seconds: null
    owner_branch: feature-webhook-outbound-contract
    owner_layer: infrastructure
    client_safe_message: "Webhook target redirected. Redirects are forbidden."
    log_level: ERROR
    runbook_link: "runbook://webhook/redirect-blocked"
    compatibility_impact: none
    required_test: contract-verification:webhook-redirect-blocked

2. docs/registries/env-keys.yaml

  # === Webhook Egress Proxy (feature-webhook-outbound-contract) ===
  - name: APP_WEBHOOK_EGRESS_PROXY_ENABLED
    type: boolean
    default: false
    allowed_values: [true, false]
    classification: public-config
    required: false
    reload_policy: restart-only
    owner_branch: feature-webhook-outbound-contract
    validation: boolean_only
    compatibility_impact: behavior-change
    required_test: env-contract:webhook-proxy

  - name: APP_WEBHOOK_EGRESS_PROXY_HOST
    type: string
    default: null
    allowed_values: null
    classification: public-config
    required: false
    reload_policy: restart-only
    owner_branch: feature-webhook-outbound-contract
    validation: non_empty_string
    compatibility_impact: behavior-change
    required_test: env-contract:webhook-proxy

  - name: APP_WEBHOOK_EGRESS_PROXY_PORT
    type: int
    default: null
    allowed_values: null
    classification: public-config
    required: false
    reload_policy: restart-only
    owner_branch: feature-webhook-outbound-contract
    validation: port_range_1_65535
    compatibility_impact: behavior-change
    required_test: env-contract:webhook-proxy

3. docs/registries/headers.yaml

  # === Webhook Outbound Headers (feature-webhook-outbound-contract) ===
  - name: X-Webhook-Signature
    direction: outbound
    type: string
    required: true
    generated_if_missing: true
    mdc_key: null
    envelope_meta_field: null
    owner_branch: feature-webhook-outbound-contract
    case_style: kebab
    compatibility_impact: additive
    required_test: contract-verification:webhook-headers

  - name: X-Webhook-Id
    direction: outbound
    type: uuid
    required: true
    generated_if_missing: true
    mdc_key: null
    envelope_meta_field: null
    owner_branch: feature-webhook-outbound-contract
    case_style: kebab
    compatibility_impact: additive
    required_test: contract-verification:webhook-headers

  - name: X-Webhook-Timestamp
    direction: outbound
    type: numeric-seconds
    required: true
    generated_if_missing: true
    mdc_key: null
    envelope_meta_field: null
    owner_branch: feature-webhook-outbound-contract
    case_style: kebab
    compatibility_impact: additive
    required_test: contract-verification:webhook-headers

4. docs/registries/metrics.yaml

  # === Webhook Outbound Metrics (feature-webhook-outbound-contract) ===
  - name: webhook.delivery.requests
    type: timer
    unit: seconds
    tags:
      - name: outcome
        cardinality_limit: 5
        allowed_values: [SUCCESS, FAILURE, TIMEOUT, RETRYING, BLOCKED]
      - name: event_type
        cardinality_limit: 20
    percentiles: [0.5, 0.9, 0.95, 0.99]
    histogram_buckets: slo_driven
    alert_severity_thresholds:
      p1: "error_rate > 5% for 5m"
      p2: "error_rate > 1% for 10m"
    owner_branch: feature-webhook-outbound-contract
    log_field_mapping: [outcome, event_type]
    compatibility_impact: additive
    required_test: contract-verification:webhook-metrics

  - name: webhook.dlq.size
    type: gauge
    unit: total
    tags:
      - name: event_type
        cardinality_limit: 20
    percentiles: null
    histogram_buckets: null
    alert_severity_thresholds:
      p1: "webhook.dlq.size > 100"
      p2: "webhook.dlq.size > 10"
    owner_branch: feature-webhook-outbound-contract
    log_field_mapping: [event_type]
    compatibility_impact: additive
    required_test: contract-verification:webhook-metrics

마주친 문제

  • 아직 없음.

묶음 (이 branch에서 파생된 자료)

Sub-branches (세부 작업)

  • 없음

오류 기록 (이 branch 작업 중 발생)

  • 없음

면접 준비 (이 작업에서 나올 수 있는 면접 질문)

  • 없음

강의 (이 작업을 위해 학습한 강의)

  • 없음

job-posting tie-ins (이 작업에서 파생된 글감)

  • 없음

관련 일일 노트

  • [[raw/daily-notes/2026-06-29]]

완료 후 정리

  • PR 링크:
  • 리뷰 메모:
  • 머지 결과 / 배포 환경:
  • wiki 추출 대상 (verified만, wiki/projects/로만 추출):
    • actually-implemented 항목:
    • locally-verified 항목:
    • prod-verified 항목:
  • 추출하지 않을 항목 (planned / documented-only / abandoned):