티스토리 뷰
웹훅(Webhook)은 어떻게 안전하게 받아야 할까? 서명 검증, 멱등성, 재시도까지 한 번에 정리 — FastAPI · Spring Boot · Node.js
octo54 2026. 6. 2. 09:56웹훅(Webhook)은 어떻게 안전하게 받아야 할까? 서명 검증, 멱등성, 재시도까지 한 번에 정리 — FastAPI · Spring Boot · Node.js
한 줄 요약
웹훅은 “외부 서비스가 우리 서버로 보내는 이벤트”인데, 진짜 실무에서는 그냥 POST 받기가 아니라 원본 바디(raw body)로 서명 검증하고, 중복 이벤트를 막고, 빠르게 2xx 응답한 뒤 비동기 후처리하는 구조로 가야 덜 망가집니다. Stripe는 Stripe-Signature 헤더와 원본 요청 바디로 서명을 검증하라고 안내하고, GitHub도 X-Hub-Signature-256으로 HMAC 검증을 하라고 공식 문서에서 설명합니다. (Stripe Docs)
이 글에서 바로 정리할 것
- 웹훅이 정확히 뭔지
- 왜 웹훅 엔드포인트는 일반 API처럼 만들면 위험한지
- 서명 검증을 왜 raw body 기준으로 해야 하는지
- 멱등성(idempotency)과 중복 이벤트 처리는 어떻게 생각해야 하는지
- FastAPI, Spring Boot, Node.js에서 바로 시작 가능한 최소 구현
- 검색에 잘 걸리도록 질문형 제목, 첫 문단 정답, 정의 문장, FAQ, 핵심 요약 구조를 반영한 글 구성 기준
웹훅이란?
웹훅은 외부 서비스가 어떤 이벤트가 발생했을 때, 우리 서버로 HTTP 요청을 보내서 알려주는 push 방식입니다. Stripe는 웹훅을 “이벤트가 발생했을 때 애플리케이션에 JSON payload를 보내는 실시간 push 알림”으로 설명하고, GitHub도 저장소/앱/조직에서 일어난 이벤트를 웹훅 payload로 전송합니다. (Stripe Docs)
예를 들면 이런 것들이죠.
- 결제 성공
- 구독 취소
- GitHub push 이벤트
- 외부 시스템 상태 변경
- 배송 상태 변경
즉 우리가 외부 API를 호출하는 게 아니라,
이번엔 외부 서비스가 우리 서버를 호출하는 구조입니다.
여기서 감각이 완전히 바뀝니다.
왜냐하면 이제는 “내가 신뢰하는 요청”이 아니라, 누가 보낸지 확인해야 하는 요청이 되거든요.
웹훅 엔드포인트는 왜 일반 POST API처럼 만들면 안 될까
이건 진짜 많이 하는 실수예요.
처음엔 이렇게 짭니다.
- /webhook/stripe
- JSON 받기
- event.type 보고 처리하기
- 끝
근데 이 상태에선 바로 위험해집니다.
- 아무나 POST 쏴도 처리될 수 있음
- 같은 이벤트가 여러 번 와도 중복 처리될 수 있음
- 느린 후처리 때문에 제공자 쪽 타임아웃/재전송이 생길 수 있음
- 로깅과 추적이 안 되면 디버깅이 지옥이 됨
Stripe는 웹훅 보안에서 서명 검증(signature verification) 을 강하게 권장하고, GitHub도 webhook secret 기반 HMAC 검증을 공식 문서로 따로 설명합니다. 또 GitHub는 secret을 반드시 고엔트로피 랜덤 문자열로 안전하게 저장하라고 best practice에서 안내합니다. (Stripe Docs)
즉 웹훅 엔드포인트는 보통 이 네 가지를 기본으로 가져가야 합니다.
- 서명 검증
- 중복 처리 방지
- 빠른 2xx 응답
- 후처리는 비동기/큐
왜 raw body가 그렇게 중요할까
이건 웹훅 구현에서 제일 많이 틀리는 포인트입니다.
Stripe 문서는 서명 검증 시 requestBody에 Stripe가 보낸 원본 바디 문자열 그대로를 넣어야 한다고 분명히 설명합니다. 파싱되거나 포맷이 바뀐 body를 쓰면 검증이 실패할 수 있습니다. (Stripe Docs)
GitHub도 X-Hub-Signature-256이 요청 본문과 secret을 사용한 HMAC SHA-256 결과라고 설명합니다. 즉 이것도 결국 원본 payload 기준으로 계산해야 맞습니다. (GitHub Docs)
이게 왜 중요하냐면요.
JSON을 파싱했다가 다시 직렬화하면 보기엔 같은 데이터라도 바이트 단위로 달라질 수 있거든요.
예를 들어:
- 공백
- key 순서
- 줄바꿈
- 인코딩
이런 것만 달라도 서명 검증은 틀어질 수 있어요.
그래서 웹훅에서는 늘 이렇게 생각해야 합니다.
먼저 raw body로 서명 검증
그다음에 JSON 파싱
이 순서를 뒤집으면, 나중에 정말 이유 모를 서명 오류로 고생합니다. Stripe도 바로 이 점 때문에 “원본 body”를 쓰라고 강조합니다. (Stripe Docs)
서명 검증만 하면 끝일까
아쉽지만 아니에요.
서명 검증은 “이 요청이 진짜 그 서비스에서 온 것 같냐”를 보는 1차 방어입니다.
그다음엔 중복 이벤트를 봐야 합니다.
왜냐하면 웹훅은 원래 재전송될 수 있기 때문이에요.
제공자 입장에선 우리 서버가 응답을 못 주거나 timeout이 나면, 같은 이벤트를 다시 보낼 수 있거든요.
Stripe는 웹훅을 처리할 때 빠르게 2xx를 응답하고, 필요한 후처리는 나중에 하라고 권장합니다. GitHub도 delivery 단위 식별 헤더를 보내고, 이벤트마다 식별 가능한 헤더들을 제공합니다. (Stripe Docs)
그래서 보통 이걸 같이 둡니다.
- Stripe: event.id
- GitHub: X-GitHub-Delivery
즉 이벤트마다 고유 식별자를 저장하고,
이미 처리한 적 있으면 다시 처리하지 않는 구조가 필요해요.
이게 결국 웹훅 멱등성입니다.
웹훅 멱등성(idempotency)은 어떻게 이해하면 쉬울까
저는 이걸 이렇게 설명합니다.
“같은 웹훅 이벤트가 두 번 와도 결과가 한 번 처리한 것과 같아야 한다.”
예를 들어 결제 완료 웹훅이 두 번 왔다고 해볼게요.
- 포인트 적립 2번
- 주문 상태 변경 2번
- 메일 발송 2번
이러면 바로 사고죠.
그래서 웹훅 처리 쪽에선 보통
이벤트 ID 테이블 또는 processed_webhooks 같은 저장소를 둡니다.
흐름은 대체로 이렇습니다.
- 이벤트 식별자 추출
- 이미 처리했는지 확인
- 안 했으면 처리 시작
- 처리 완료 후 processed 기록
- 이미 있으면 그냥 200 반환
이게 정말 중요합니다.
외부 시스템은 “한 번만” 보내준다고 믿으면 안 돼요.
빠르게 2xx 응답하라는 건 무슨 뜻일까
이것도 실무에서 꽤 중요합니다.
웹훅 엔드포인트 안에서 이런 걸 다 해버리면 안 좋아요.
- 무거운 DB 처리
- 외부 API 연쇄 호출
- 이미지 변환
- AI 요약
- 복잡한 비즈니스 후처리
왜냐하면 제공자 입장에선 일정 시간 안에 성공 응답이 안 오면
“실패했나?” 하고 다시 보낼 수 있기 때문입니다.
그래서 웹훅은 보통 이렇게 갑니다.
- 서명 검증
- 기본 유효성 확인
- 중복 여부 확인
- 큐에 넣기 또는 상태만 기록
- 빠르게 200/204 응답
- 실제 처리는 워커가 비동기로
이건 우리가 앞에서 배운 큐와 비동기 작업이 여기서 딱 이어지는 지점이에요.
진짜로요. 여기서 서로 연결됩니다.
이번 글에서 맞출 공통 구조
이번에는 Stripe 스타일 웹훅을 기준으로 통일하겠습니다.
GitHub도 같이 언급하되, 코드 감각은 공통 HMAC 검증 패턴으로 보여드릴게요.
흐름은 이렇게 갑니다.
- raw body 수신
- 서명 헤더 읽기
- secret으로 서명 검증
- 이벤트 ID 추출
- 중복 여부 확인
- 큐 등록 또는 상태 저장
- 200 응답
즉 오늘 글의 핵심은 이겁니다.
웹훅 엔드포인트의 본질은 이벤트를 “처리”하는 게 아니라, 이벤트를 “신뢰 가능하게 접수”하는 것이다.
1) FastAPI에서 웹훅 안전하게 받기
FastAPI는 Request 객체를 직접 받아 raw body를 읽을 수 있습니다. 공식 문서도 Request를 직접 주입받아 원시 요청 정보에 접근할 수 있다고 설명합니다. (FastAPI)
추천 구조
fastapi-backend/
├── app/
│ ├── api/
│ │ └── webhook.py
│ ├── repositories/
│ │ └── webhook_event_repository.py
│ ├── services/
│ │ └── webhook_service.py
│ └── main.py
app/repositories/webhook_event_repository.py
class WebhookEventRepository:
def __init__(self) -> None:
self._processed_event_ids: set[str] = set()
def exists(self, event_id: str) -> bool:
return event_id in self._processed_event_ids
def save(self, event_id: str) -> None:
self._processed_event_ids.add(event_id)
app/services/webhook_service.py
import hmac
import hashlib
import json
from typing import Any
from app.repositories.webhook_event_repository import WebhookEventRepository
class WebhookService:
def __init__(self, repository: WebhookEventRepository, secret: str) -> None:
self.repository = repository
self.secret = secret.encode("utf-8")
def verify_signature(self, raw_body: bytes, signature: str) -> bool:
expected = hmac.new(
self.secret,
raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature)
def handle_event(self, raw_body: bytes) -> dict[str, Any]:
payload = json.loads(raw_body.decode("utf-8"))
event_id = payload["id"]
event_type = payload["type"]
if self.repository.exists(event_id):
return {
"message": "already processed",
"event_id": event_id,
"event_type": event_type,
}
# 실제 서비스라면 여기서 큐에 넣거나 상태 기록
self.repository.save(event_id)
return {
"message": "accepted",
"event_id": event_id,
"event_type": event_type,
}
app/api/webhook.py
from fastapi import APIRouter, HTTPException, Request, status
from app.repositories.webhook_event_repository import WebhookEventRepository
from app.services.webhook_service import WebhookService
router = APIRouter(prefix="/api/webhooks", tags=["webhooks"])
repository = WebhookEventRepository()
webhook_service = WebhookService(repository, secret="demo-webhook-secret")
@router.post("/payment")
async def payment_webhook(request: Request):
raw_body = await request.body()
signature = request.headers.get("x-webhook-signature")
if not signature:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="signature header is missing",
)
if not webhook_service.verify_signature(raw_body, signature):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="invalid signature",
)
result = webhook_service.handle_event(raw_body)
return result
app/main.py
from fastapi import FastAPI
from app.api.webhook import router as webhook_router
app = FastAPI()
app.include_router(webhook_router)
FastAPI에서 핵심 감각
여기서 진짜 중요한 건 이 부분입니다.
raw_body = await request.body()
이 raw body를 먼저 읽고 검증한다는 감각이 웹훅에서는 핵심이에요.
Stripe가 공식 문서에서 바로 이 점을 강조합니다. constructEvent()에 넣는 body는 원본 문자열이어야 한다고요. (Stripe Docs)
즉 FastAPI에서 웹훅은
Pydantic 모델 파싱부터 하지 말고, Request raw body부터 보라
이게 진짜 포인트입니다.
2) Spring Boot에서 웹훅 엔드포인트 만들기
Spring에서는 raw body를 @RequestBody byte[] 또는 String으로 받는 패턴이 꽤 자연스럽습니다. 그리고 웹훅 같은 외부 이벤트 수신은 일반 DTO 바인딩보다 원본 바디 우선 접근이 더 중요합니다.
추천 구조
springboot-backend/
├── src/main/java/com/example/backend/
│ ├── controller/
│ │ └── WebhookController.java
│ ├── repository/
│ │ └── WebhookEventRepository.java
│ └── service/
│ └── WebhookService.java
repository/WebhookEventRepository.java
package com.example.backend.repository;
import org.springframework.stereotype.Repository;
import java.util.HashSet;
import java.util.Set;
@Repository
public class WebhookEventRepository {
private final Set<String> processedEventIds = new HashSet<>();
public boolean exists(String eventId) {
return processedEventIds.contains(eventId);
}
public void save(String eventId) {
processedEventIds.add(eventId);
}
}
service/WebhookService.java
package com.example.backend.service;
import com.example.backend.repository.WebhookEventRepository;
import org.springframework.stereotype.Service;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
@Service
public class WebhookService {
private final WebhookEventRepository repository;
private final ObjectMapper objectMapper = new ObjectMapper();
private final String secret = "demo-webhook-secret";
public WebhookService(WebhookEventRepository repository) {
this.repository = repository;
}
public boolean verifySignature(byte[] rawBody, String signature) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] digest = mac.doFinal(rawBody);
String expected = HexFormat.of().formatHex(digest);
return expected.equals(signature);
}
public Map<String, Object> handleEvent(byte[] rawBody) throws Exception {
Map<String, Object> payload = objectMapper.readValue(rawBody, Map.class);
String eventId = (String) payload.get("id");
String eventType = (String) payload.get("type");
if (repository.exists(eventId)) {
return Map.of(
"message", "already processed",
"eventId", eventId,
"eventType", eventType
);
}
repository.save(eventId);
return Map.of(
"message", "accepted",
"eventId", eventId,
"eventType", eventType
);
}
}
controller/WebhookController.java
package com.example.backend.controller;
import com.example.backend.service.WebhookService;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/webhooks")
public class WebhookController {
private final WebhookService webhookService;
public WebhookController(WebhookService webhookService) {
this.webhookService = webhookService;
}
@PostMapping("/payment")
@ResponseStatus(HttpStatus.OK)
public Object paymentWebhook(
@RequestHeader(value = "X-Webhook-Signature", required = false) String signature,
@RequestBody byte[] rawBody
) throws Exception {
if (signature == null || signature.isBlank()) {
throw new IllegalArgumentException("signature header is missing");
}
if (!webhookService.verifySignature(rawBody, signature)) {
throw new IllegalArgumentException("invalid signature");
}
return webhookService.handleEvent(rawBody);
}
}
Spring Boot에서 핵심 감각
Spring에서 웹훅은 @RequestBody를 DTO로 바로 받고 싶은 유혹이 큰데,
웹훅에선 raw body부터 받는 편이 더 맞습니다.
즉 이 구조를 기억하면 됩니다.
- controller는 raw body를 받는다
- service가 서명 검증을 한다
- 그 뒤에 JSON 파싱을 한다
이 순서가 안 뒤집히는 게 중요해요.
3) Node.js에서 웹훅 엔드포인트 만들기
Node.js는 여기서 특히 조심해야 할 게 있습니다.
일반적으로 express.json()을 먼저 걸어버리면 raw body를 잃기 쉬워요.
그래서 웹훅 라우트는 종종 별도 raw parser를 쓰거나, 라우트 순서를 조심해서 다룹니다.
GitHub는 secret이 있으면 X-Hub-Signature-256 헤더가 오고, SHA-256 HMAC으로 검증하라고 설명합니다. Stripe도 raw body 기반 검증을 요구합니다. (GitHub Docs)
추천 구조
node-backend/
├── src/
│ ├── repositories/
│ │ └── webhook-event.repository.js
│ ├── routes/
│ │ └── webhook.route.js
│ ├── services/
│ │ └── webhook.service.js
│ └── server.js
src/repositories/webhook-event.repository.js
export class WebhookEventRepository {
constructor() {
this.processedEventIds = new Set();
}
exists(eventId) {
return this.processedEventIds.has(eventId);
}
save(eventId) {
this.processedEventIds.add(eventId);
}
}
src/services/webhook.service.js
import crypto from "node:crypto";
export class WebhookService {
constructor(repository, secret) {
this.repository = repository;
this.secret = secret;
}
verifySignature(rawBodyBuffer, signature) {
const expected = crypto
.createHmac("sha256", this.secret)
.update(rawBodyBuffer)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
handleEvent(rawBodyBuffer) {
const payload = JSON.parse(rawBodyBuffer.toString("utf8"));
const eventId = payload.id;
const eventType = payload.type;
if (this.repository.exists(eventId)) {
return {
message: "already processed",
eventId,
eventType,
};
}
this.repository.save(eventId);
return {
message: "accepted",
eventId,
eventType,
};
}
}
src/routes/webhook.route.js
import express from "express";
import { WebhookEventRepository } from "../repositories/webhook-event.repository.js";
import { WebhookService } from "../services/webhook.service.js";
const router = express.Router();
const repository = new WebhookEventRepository();
const webhookService = new WebhookService(repository, "demo-webhook-secret");
router.post(
"/payment",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.header("X-Webhook-Signature");
if (!signature) {
return res.status(400).json({ message: "signature header is missing" });
}
if (!webhookService.verifySignature(req.body, signature)) {
return res.status(400).json({ message: "invalid signature" });
}
const result = webhookService.handleEvent(req.body);
return res.json(result);
}
);
export default router;
src/server.js
import express from "express";
import webhookRouter from "./routes/webhook.route.js";
const app = express();
const PORT = 3000;
// 일반 JSON 파서는 다른 라우트에만
app.use("/api/webhooks", webhookRouter);
app.use(express.json());
app.listen(PORT, () => {
console.log(`server running on http://localhost:${PORT}`);
});
Node.js에서 핵심 감각
Node.js에선 이게 제일 중요합니다.
express.raw({ type: "application/json" })
이걸 웹훅 라우트에 별도로 걸어 raw body를 살려두는 감각이요.
일반 express.json()을 전역으로 먼저 태워버리면 서명 검증이 꼬이기 쉬워요.
이건 실무에서 정말 자주 터집니다.
GitHub 웹훅과 Stripe 웹훅은 어떻게 다를까
구조는 꽤 비슷합니다.
Stripe
- Stripe-Signature 헤더
- 원본 body로 검증
- 공식 SDK constructEvent() 사용 권장
GitHub
- X-Hub-Signature-256
- secret 기반 HMAC SHA-256
- X-GitHub-Delivery로 delivery 식별 가능
GitHub는 X-Hub-Signature-256을 더 안전한 검증 헤더로 권장하고, X-GitHub-Delivery 같은 식별 헤더도 제공합니다. Stripe는 공식 라이브러리 기반 서명 검증을 권장합니다. (GitHub Docs)
즉 벤더마다 헤더 이름은 달라도,
감각은 거의 같습니다.
- 원본 body
- secret
- HMAC/서명 검증
- 이벤트 ID 저장
- 중복 방지
웹훅 엔드포인트에 꼭 같이 붙이면 좋은 것
1. 이벤트 로그
최소한 이 정도는 남기는 편이 좋습니다.
- received_at
- provider
- event_id
- event_type
- signature_valid
- handled_status
2. 처리 상태 기록
웹훅도 사실 비동기 작업처럼 볼 수 있습니다.
- RECEIVED
- VERIFIED
- DUPLICATE
- QUEUED
- PROCESSED
- FAILED
이렇게 두면 운영할 때 훨씬 편해요.
3. 빠른 응답 + 큐 연결
서명 검증과 중복 확인까지만 동기 처리하고,
비즈니스 후처리는 큐로 넘기는 구조가 가장 안정적입니다.
4. secret 관리
GitHub도 webhook secret은 고엔트로피 랜덤 문자열로 안전하게 저장하라고 설명합니다. 환경변수나 secret manager로 관리하는 편이 맞습니다. (GitHub Docs)
자주 발생하는 오류
오류 1. “서명 검증이 계속 실패해요”
원인:
- raw body가 아니라 파싱된 JSON 사용
- secret이 다름
- 헤더 이름을 잘못 읽음
Stripe는 원본 body를 써야 한다고 아주 명확하게 설명합니다. GitHub도 payload 기반 HMAC이므로 원본 body 감각이 같습니다. (Stripe Docs)
오류 2. “같은 이벤트가 두 번 처리돼요”
원인:
- 이벤트 ID 저장 안 함
- timeout 때문에 재전송
- 멱등성 체크 누락
해결:
- event ID 저장
- 이미 처리한 이벤트면 그냥 200 반환
오류 3. “웹훅 제공자가 자꾸 재시도해요”
원인:
- 응답이 너무 느림
- 2xx를 못 돌려줌
- 후처리를 요청 안에서 다 해버림
해결:
- 서명 검증 + 중복 체크까지만 빠르게
- 나머지는 큐/워커
실무에서 주의할 점
1. 웹훅은 “신뢰되지 않은 입력”입니다
외부 서비스가 보냈다 해도, 서명 검증 전에는 신뢰하면 안 됩니다. Stripe와 GitHub 모두 검증을 전제로 문서를 씁니다. (Stripe Docs)
2. IP allowlist는 보조 수단입니다
Stripe는 IP allowlisting도 언급하지만, 핵심은 서명 검증입니다. IP만 믿는 구조는 보통 부족합니다. (Stripe Docs)
3. secret 회전/재설정도 생각하세요
GitHub와 Stripe 모두 secret 개념이 있고, secret이 바뀌면 검증 로직도 맞춰야 합니다. GitHub는 webhook 업데이트 시 secret이 덮어써질 수 있다고 문서에 나옵니다. (GitHub Docs)
4. 운영 로그와 사용자 로그를 분리하세요
웹훅 실패 원인은 내부 운영용으로 자세히 남기고, 외부 응답은 짧고 안정적으로 가는 편이 좋습니다.
5. 웹훅도 결국 “외부 API 수신”입니다
우리가 앞에서 본 외부 API 호출, 큐, Job Status, 멱등성 개념이 여기서 다 연결됩니다.
FAQ
Q. 웹훅은 왜 raw body가 꼭 필요한가요?
Stripe는 서명 검증 시 원본 request body 문자열을 그대로 써야 한다고 공식 문서에서 설명합니다. GitHub의 HMAC 서명도 요청 본문 기준이라 감각이 같습니다. 파싱 후 재직렬화된 body는 서명이 달라질 수 있습니다. (Stripe Docs)
Q. 웹훅은 무조건 큐로 넘겨야 하나요?
작업이 아주 가볍다면 꼭 그런 건 아닙니다. 하지만 느린 비즈니스 로직, 외부 API 연쇄 호출, 재시도 가능성이 있으면 큐로 넘기는 편이 보통 더 안전합니다.
Q. 웹훅 중복 이벤트는 왜 오는 건가요?
제공자 입장에서 응답이 늦거나 실패로 보이면 재전송할 수 있기 때문입니다. 그래서 이벤트 ID 기반 멱등성 체크가 사실상 기본입니다.
Q. GitHub 웹훅과 Stripe 웹훅 구현 패턴은 많이 다른가요?
헤더 이름과 공식 SDK는 다르지만, 핵심 구조는 꽤 비슷합니다.
원본 body, secret, 서명 검증, 이벤트 ID 저장, 빠른 2xx 응답, 비동기 후처리. 이 흐름은 거의 같습니다. (Stripe Docs)
Q. FastAPI나 Express에서 body parser 때문에 서명 검증이 깨질 수 있나요?
네, 충분히 그럴 수 있습니다. FastAPI는 Request.body()로 raw body를 먼저 읽는 방식이 중요하고, Express는 웹훅 라우트에서 express.raw()를 쓰는 패턴이 흔합니다. (FastAPI)
핵심 요약
- 웹훅은 외부 서비스가 우리 서버로 보내는 이벤트입니다. (Stripe Docs)
- 웹훅 엔드포인트는 일반 POST API처럼 만들면 위험합니다.
- 서명 검증은 raw body로 먼저, JSON 파싱은 그다음입니다. Stripe와 GitHub 모두 이 감각이 핵심입니다. (Stripe Docs)
- 이벤트 ID를 저장해서 중복 처리 방지를 해야 합니다.
- 빠르게 2xx를 응답하고, 후처리는 큐로 넘기는 구조가 가장 안정적입니다.
- 검색에 잘 걸리는 개발 글은 질문형 제목, 초반 정답, 정의 문장, FAQ, 핵심 요약 구조가 강합니다.
출처
- Stripe 공식 문서 — Receive Stripe events in your webhook endpoint. (Stripe Docs)
- Stripe 공식 문서 — Resolve webhook signature verification errors. (Stripe Docs)
- Stripe 공식 문서 — Handle payment events with webhooks. (Stripe Docs)
- Stripe 공식 문서 — Set up and deploy a webhook. (Stripe Docs)
- GitHub 공식 문서 — Validating webhook deliveries. (GitHub Docs)
- GitHub 공식 문서 — Webhook events and payloads. (GitHub Docs)
- GitHub 공식 문서 — Best practices for using webhooks. (GitHub Docs)
- FastAPI 공식 문서 — Using the Request Directly. (FastAPI)
- FastAPI 공식 문서 — Request Body. (FastAPI)
- 글 구성 참고: 업로드하신 “AI 검색에 잘 걸리는 글” 가이드.
백엔드개발, FastAPI, SpringBoot, Nodejs, Express, 웹훅, Webhook, 서명검증, 멱등성, 백엔드시리즈
'study > 백엔드' 카테고리의 다른 글
- Total
- Today
- Yesterday
- PostgreSQL
- LangChain
- 생성형AI
- 웹개발
- 개발블로그
- JAX
- 쿠버네티스
- nodejs
- 주니어개발자
- 딥러닝
- Express
- kotlin
- Python
- flax
- Next.js
- fastapi
- rag
- REACT
- seo 최적화 10개
- SpringBoot
- CI/CD
- llm
- NestJS
- DevOps
- Prisma
- JWT
- nextJS
- SEO최적화
- node.js
- 백엔드개발
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |

