Files
llm-wiki/raw/official-docs/stripe-webhook-signature.md
T

101 lines
11 KiB
Markdown

---
title: Stripe — Webhook Signatures (official-vendor-doc)
source_type: official-doc
url: https://stripe.com/docs/webhooks/signatures
archive_url: https://web.archive.org/web/20260629/https://stripe.com/docs/webhooks/signatures
status: raw
confidence: high
tags: [stripe, webhook, signature, hmac, security, replay-protection, timing-attack, key-rotation]
related_projects: [ca-skeleton]
related_branches: [feature-webhook-outbound-contract]
created: 2026-06-29
last_reviewed: 2026-06-29
---
# Stripe — Webhook Signatures (공식)
> Layer: `raw/official-docs/` — Stripe 공식 문서의 **원문 발췌 및 출처 기록**.
> Strength 분류: `official-vendor-doc` — Stripe 공식 개발자 문서 (`stripe.com/docs/webhooks/...`).
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-webhook-outbound-contract]] | **D1 (HMAC-SHA256 서명 스키마)**, **D2 (타임스탬프 기반 Replay Attack 방지)** 및 secret key rotation 정책 결정 근거. |
## 컨텍스트
`feature-webhook-outbound-contract` 의 D1, D2 는 아웃바운드 웹훅의 발송자 인증과 리플레이 공격 방지를 위해 헤더 기반 서명 스키마를 수립한다. 본 문서는 Stripe 가 (a) `Stripe-Signature` 헤더 포맷 (`t=timestamp,v1=sig`), (b) `timestamp + '.' + payload` 형태의 서명 조립식, (c) 5분 오차 허용(tolerance window) 리플레이 검증, (d) constant-time byte comparison 을 통한 timing attack 방지, (e) 키 로테이션 시 복수 서명 포함 등을 직접 진술하는 공식 표준 근거이다.
## 출처 / Source
- 원본 URL: https://stripe.com/docs/webhooks/signatures
- 부속 URL (최선의 조치): https://stripe.com/docs/webhooks/best-practices
- 저자 / 조직: Stripe, Inc. — Stripe Developer Documentation
- 마지막 확인일: 2026-06-29
## 핵심 인용 / Key quotes (verbatim)
> [§Verifying signatures] "Stripe signs the webhook events it sends to your endpoints by including a signature in each event's Stripe-Signature header. This allows you to verify that the events were sent by Stripe, not by a third party."
> [§Verifying signatures] "The Stripe-Signature header contains a timestamp and one or more signatures. The timestamp is prefixed by t=, and each signature is prefixed by a scheme. Schemes start with v. Currently, the only supported live signature scheme is v1."
> [§Verifying signatures] "Stripe generates the signature using a hash-based message authentication code (HMAC) with SHA-256."
> [§Step 1: Extract the timestamp and signatures] "Step 1: Extract the timestamp and signatures: Split the header, using the , character as the separator, to get a list of elements. Then split each element, using the = character as the separator, to get a prefix and value pair."
> [§Step 2: Prepare the signed_payload string] "Step 2: Prepare the signed_payload string: Concatenate: The timestamp (as a string), The character ., The actual JSON payload (that is, the request body)"
> [§Step 3: Determine the expected signature] "Step 3: Determine the expected signature: Compute an HMAC with the SHA256 hash function. Use the endpoint's signing secret as the key, and the signed_payload string as the message."
> [§Step 4: Compare the signatures] "Step 4: Compare the signatures: Compare the signature (or signatures) in the header to the expected signature. To protect against timing attacks, use a constant-time string comparison to compare the expected signature to each of the received signatures."
> [§Preventing replay attacks] "A replay attack is when an attacker intercepts a valid payload and its signature, then re-transmits them. To prevent such attacks, Stripe includes a timestamp in the Stripe-Signature header. When verifying signatures, your integration should check that the timestamp is within a tolerance window (defaulting to 5 minutes) of the current time."
> [§Preventing replay attacks] "Stripe generates a new signature and timestamp for each retry attempt."
> [§Secrets rotation] "If you need to rotate secrets, or if you have multiple active secrets, Stripe includes multiple signatures in the header. For example, if you have two active secrets, the header contains: Stripe-Signature: t=1672531199,v1=sig1,v1=sig2"
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| STRIPE-WEBHOOK-C1 | Stripe는 발신자 인증을 위해 모든 웹훅 이벤트의 `Stripe-Signature` 헤더에 서명을 포함함 | "Stripe signs the webhook events it sends to your endpoints by including a signature in each event's Stripe-Signature header." | `official-vendor-doc` | 웹훅 발신자 신원 검증 | 일반 클라이언트-서버 REST API 인증 |
| STRIPE-WEBHOOK-C2 | 서명 헤더는 타임스탬프(`t=`)와 서명 리스트(`v1=`)로 구성되며 쉼표(`,`)로 구분됨 | "The Stripe-Signature header contains a timestamp and one or more signatures. The timestamp is prefixed by t=, and each signature is prefixed by a scheme. Schemes start with v. Currently, the only supported live signature scheme is v1." | `official-vendor-doc` | 서명 포맷 파싱 및 결합 구조 | JSON 페일로드 이외의 이진 데이터 지원 여부 |
| STRIPE-WEBHOOK-C3 | 서명 생성에는 해시 기반 메시지 인증 코드인 HMAC-SHA256 알고리즘을 사용함 | "Stripe generates the signature using a hash-based message authentication code (HMAC) with SHA-256." | `official-vendor-doc` | 암호학적 서명 생성 알고리즘 선택 | asymmetric RSA/ECDSA 서명 방식 지원 |
| STRIPE-WEBHOOK-C4 | 서명 대상 페이로드는 `타임스탬프 문자열 + '.' + raw JSON 본문` 형태로 조립됨 | "Concatenate: The timestamp (as a string), The character ., The actual JSON payload (that is, the request body)" | `official-vendor-doc` | 서명 검증 원본 데이터 조립식 | 페이로드 내 화이트스페이스/개행 문자 무관성 |
| STRIPE-WEBHOOK-C5 | 서명 생성 시 각 웹훅 엔드포인트별 고유 secret key가 키 값으로 사용됨 | "Compute an HMAC with the SHA256 hash function. Use the endpoint's signing secret as the key, and the signed_payload string as the message." | `official-vendor-doc` | 비밀 키 범위설정 및 매핑 | 다중 엔드포인트 간의 단일 마스터 키 사용 방식 |
| STRIPE-WEBHOOK-C6 | timing attack을 차단하기 위해 서명 문자열 비교 시 constant-time 비교 방식을 적용해야 함 | "Compare the signature (or signatures) in the header to the expected signature. To protect against timing attacks, use a constant-time string comparison to compare the expected signature to each of the received signatures." | `official-vendor-doc` | 서명 비교 시 하드웨어 수준 부채널 공격 방어 | 일반 문자열 `equals` 비교의 안전성 |
| STRIPE-WEBHOOK-C7 | replay attack 방지를 위해 수신단은 타임스탬프와 현재 시각의 오차를 5분 윈도우 내로 제한해야 함 | "To prevent such attacks, Stripe includes a timestamp in the Stripe-Signature header. When verifying signatures, your integration should check that the timestamp is within a tolerance window (defaulting to 5 minutes) of the current time." | `official-vendor-doc` | 리플레이 공격 방어 윈도우 수립 | NTP 비동기화 상태에서의 강제 복구 |
| STRIPE-WEBHOOK-C8 | 재시도(retry) 발생 시 Stripe는 매번 새로운 타임스탬프와 그에 대응하는 새 서명을 생성하여 발송함 | "Stripe generates a new signature and timestamp for each retry attempt." | `official-vendor-doc` | 재시도 요청 수신 시 타임스탬프 갱신 정책 | 수신 측의 재시도 유일성 판별 방법 |
| STRIPE-WEBHOOK-C9 | 시크릿 로테이션 또는 복수 시크릿 존재 시 헤더에 `v1` 접두사를 가진 서명이 다중으로 포함됨 | "If you need to rotate secrets, or if you have multiple active secrets, Stripe includes multiple signatures in the header." | `official-vendor-doc` | 시크릿 로테이션 중단 최소화 설계 | 특정 서명 매칭 시 다른 서명의 무효화 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `STRIPE-WEBHOOK-C2`, `C4`: 헤더 문자열 포맷(`t=...,v1=...`) 및 payload 조립 방식 (`t.body`).
- `STRIPE-WEBHOOK-C3`, `C5`: HMAC-SHA256 알고리즘 사용과 엔드포인트별 단일 secret mapping.
- `STRIPE-WEBHOOK-C6`: constant-time comparison (`MessageDigest.isEqual`) 필수 적용.
- `STRIPE-WEBHOOK-C7`: replay window 기본값 5분 (300초).
- `STRIPE-WEBHOOK-C9`: 로테이션 단계에서 다중 signature 전송 메커니즘 지원.
- **이 자료가 증명하지 않는 것**:
- **수신단 시스템 시각 보정 (NTP)** — 수신 서버의 NTP 동기화가 실패하여 발생하는 타임스탬프 불일치 예외 처리 흐름은 명시하지 않음.
- **DB 기반 Key-Rotation 스키마** — 복수 Active Secret을 보관하기 위한 데이터베이스 테이블 구조 및 캐싱 메커니즘은 증명하지 않음.
- **서명 해시 인코딩 포맷** — 본문에는 명시되지 않았으나 관례적으로 HMAC 결과값을 Hexadecimal(16진수) 문자열로 인코딩하여 매칭한다는 점.
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- Spring Core `RestClient`를 활용해 외부 요청을 보낼 때, 직렬화된 JSON payload의 바이트 배열이 변경(예: Jackson Indent 출력, UTF-8 외 인코딩)되면 서명이 깨지므로, **반드시 JSON 직렬화 직후의 raw byte array를 그대로 서명 계산에 투입**해야 함.
- 다중 Secret을 지원하기 위해 `application.yml` 및 Database 구조가 List 형태의 Secret Key를 매핑할 수 있도록 설계되어야 함.
## 메모 / Notes
- **Constant-time comparison**: Java에서는 `java.security.MessageDigest.isEqual(byte[], byte[])`가 constant-time 비교를 제공하므로 이를 서명 검증 유틸에 필수로 사용해야 함.
- **Header Parsing**: 쉼표로 파싱할 때 `t=1672531199``v1=sig1`을 각각 맵핑하고, 서명 목록(`List<String>`)과 단일 타임스탬프(`String`)로 분리해 내는 견고한 파서 필요.
- **Rotation Window**: `overlap-24h` 또는 `manual` 로테이션 시 120초~300초 간 복수 서명이 발송될 수 있으므로, 수신 측은 목록 중 하나라도 일치하면 성공으로 판정해야 함.
## Related / 관련
- 관련 raw 자료: [[raw/official-docs/github-webhook-signature.md]], [[raw/official-docs/svix-webhook-best-practices.md]]
- 이 자료를 인용한 wiki 요약: (미작성)
- 이 자료를 인용하는 branch: [[raw/branch-notes/feature-webhook-outbound-contract.md]]
- 인용하는 project: [[raw/project-notes/ca-skeleton-operational-contract.md]]