서비스가 특정 이벤트에 반응할 필요가 있을 때가 있습니다. 결제가 완료되거나 고객 정보가 변경되거나 리포지토리가 푸시되었습니다. API를 매 분마다 폴링하여 “새로운 항목이 있나요?”라는 질문을 반복적으로 하는 것보다, 이벤트가 발생할 때 소스 시스템이 호출하는 것을 허용할 수 있습니다.
웹 훅 예제 기사에서 대부분이 여기서 멈추고 있습니다. 그들은 경로를 보여주고 JSON 본문을 출력하고 반환합니다. 200, 그리고 그것을 완료하세요. 그 버전은 누군가가 위조된 요청을 보내거나 유효한 요청을 재생산하거나 프레임워크가 시그니처 검증 전에 요청 본문을 파싱한 경우에도 올바르게 작동합니다.
이 안내서에서는 실제 운영 환경에서 사용할 경로를 따라갑니다. 예시는 복사하기에 충분히 작지만 중요한 부분을 포함합니다: raw body 처리, HMAC 검증, 타임스탬프 검사, 빠른 확인, 그리고 실제 운영 환경에서 디버깅하기에 실용적인 방법입니다.
목차
- 웹훅이란 무엇이며 왜 사용해야 하나요?
- 웹훅 HTTP 요청의 구조
- 웹훅 시그니처를 안전하게 검증하는 방법
- 재생 공격 방지
- Node.js에서 Webhook 수신기 만들기
- Python에서 Webhook 수신기 만들기
- Go로 웹후크 수신기를 구축하는 방법
- 중요한 디버깅 기법
- 웹후크가 프로덕션 준비 상태인지 확인하는 체크리스트
- 웹훅에 대한 자주 묻는 질문
웹훅이란 무엇이며 사용하는 이유
billing 제공자가 02:13에 청구서를 지불 표시합니다. 만약 앱이 02:14에 알게되면 고객은 즉시 접근할 수 있습니다. 만약 앱이 다음 폴링 사이클에 알게되면 고객은 기다리게 되고, 지원팀은 티켓을 받고, 로그는 불필요한 잡음으로 채워집니다. 웹훅은 이러한 시간 문제를 해결하기 위해 이벤트가 발생했을 때 HTTP 콜백을 보내는 것입니다.
실제로 웹훅은 시스템 간의 이벤트 기반 POST입니다. 제공자는 변경을 감지하고, 예를 들어 invoice.paid, order.created, 또는 push, 이벤트 데이터를 URL을 제어하는 URL로 보내는 것입니다. 이로써 폴링이 생성하는
이 패턴은 실제 시스템에서 나타난다. 이는 사업 이벤트에 순수하게 매핑되기 때문이다. Stripe은 결제 결과를 게시한다. GitHub은 저장소 활동을 게시한다. Shopify은 주문 업데이트 게시한다. 형식은 간단하지만, 실제 운영 환경에서는 그렇지 않다. 돈, 접근, 또는 재고를 업데이트하는 웹후크는 일반적인 API 엔드포인트와 동일한 주의를 기울여야 한다. 특히 재시도, 중복, 그리고 신뢰할 수 없는 트래픽이 들어오면 더욱 그렇다.
이벤트를 감지하는 서비스가 필요합니다.
웹후크 흐름을 프레임하는 유용한 방법은 네 가지 부분이 함께 작동하는 것이라고 생각하는 것입니다.
- 원본 시스템. 이벤트를 감지하는 서비스.
- 목적지 엔드포인트. HTTP 경로가 이벤트를 받습니다.
- 이벤트. 발생한 이름이 지정된 변경 사항, 예를 들어
invoice.paidorpush. - Payload. code이 필요로 하는 세부 정보가 포함된 요청 본문.
The provider sends facts about something that already happened. Your job is to verify the sender, confirm the request is fresh, and apply the change once. That last part matters more than many basic tutorials admit. In production, duplicate delivery is normal behavior, not a corner case.
Practical rule: 웹 훅을 사용하여 이벤트 기반 업데이트. 폴링을 사용하여 예약된 읽기, 백필, 또는 제공자가 아웃바운드 이벤트를 제공하지 않는 경우.
팀이 더 광범위한 워크플로 자동화 및 데이터 통합웹 훅은 시스템을 동기화하는 데 필요한 불필요한 요청 트래픽 없이 이벤트 층이 됩니다. 통합-heavy 서비스를 개발하는 경우 Capgo의 백엔드 개발 기사 는 유용한 맥락을 제공합니다. core 문제는 다시 시도, 큐, 관찰성, 실패 처리와 관련이 있습니다.
제품에서 성공하고 실패하는 것은
설정은 보통 평범합니다. 구독할 이벤트를 선택하여. 엔드포인트를 제공자 또는 이벤트 패밀리별로 제한합니다. 이벤트 ID를 저장하여 중복 전달이 반복되는 효과를 피합니다. 요청이 검증되고 큐에 등록된 후 빠른 2xx 응답을 반환하고, 그 다음 비즈니스 로직을 비동기적으로 수행합니다.
약한 버전은 쉽게 식별할 수 있습니다. 하나의 일반적인 엔드포인트가 모든 것을 처리합니다. 서명 확인이 초기 테스트 중에 건너뛰어지고 다시 돌아오지 않습니다. 핸들러는 이벤트가 인증되거나陈舊한지 확인하기 전에 직접 중요 테이블에 쓰게 됩니다. 데모에서 작동하지만 재시도 폭풍, 제공자 장애 또는 공격자가 이전 요청을 재생하는 경우에 실패합니다.
이 거래 방식은 이 안내서의 나머지 부분을 정의합니다. 웹후크 수신기의 '헬로 월드' 버전은 작습니다. 프로덕션 준비 버전은 서명 확인, 재생 방어, 중복 처리 및 디버깅 훅을 시작부터 제공합니다.
웹후크 HTTP 요청의 구조
code을 작성하기 전에, 요청을 프레임워크 객체 대신 raw HTTP로 보는 것이 도움이 됩니다. 일반적인 웹후크는 public 엔드포인트에 HTTP POST를 보내는 것입니다. 헤더와 JSON 본문이 있습니다.
간단한 raw 요청
POST /webhooks/orders HTTP/1.1
Host: your-app.example
Content-Type: application/json
User-Agent: Provider-Webhooks/1.0
X-Webhook-Signature: sha256=abc123example
X-Webhook-Timestamp: 1712345678
{
"event": "order.created",
"id": "evt_123",
"data": {
"order_id": "ord_456",
"status": "created"
}
}
중요한 부분은 다음과 같습니다:
- Method. 실제로는 웹후크 전달은 일반적으로 POST 요청입니다.
- Content-Type. 대부분의 현대 제공자는 JSON을 보내는 것입니다.
- User-Agent. 디버깅을 위해 도움이지만 신뢰할 수 있는 것은 아닙니다.
- 서명 헤더. 제공자의 인증 확인을 수행합니다.
- 타임스탬프 헤더. 오래된 또는 재생된 요청을 거부합니다.
몸체의 형태가 왜 중요한가요
Your code는 일반적으로 모든 field에 대해 신경 쓰지 않습니다. 이벤트 유형, 이벤트 식별자, 그리고 내부의 비즈니스 객체에만 관심이 있습니다. data. 그 이유는 좋은 핸들러가 필요한 것만 파싱하고 나머지 로그를 디버깅을 위해 남기기 때문입니다.
OpenAPI는 이 패턴을 직접 모델링합니다. OpenAPI 3.1.0은 첫 번째급 웹훅 지원을 제공하며, 각 웹훅은 제공자에 의해 트리거되는 Path Item과 유사한 형태로 설명됩니다. webhooks canonical 예제는 웹훅에 operation, JSON 요청 본문, 그리고 수신 확인을 나타내는 response를 포함합니다. newPet operation post JSON 요청 본문 200 수신 확인을 나타내는 response OpenAPI webhook 예시.
자신의 수신자 또는 제공자 계약을 문서화할 때 강력한 예시가 추상적인 스키마 문장보다 더 도움이 됩니다. 나는 참조와 같은 것들을 사용하는 것을 좋아합니다. SheetMergy의 API 문서 예시 because they make it obvious how request examples, field descriptions, and expected responses fit together.
웹후크는 전송層에서 간단합니다. 대부분의 실패는 헤더, 바디 인코딩 또는 서명 규칙에 대한 일치하지 않는 가정으로부터 발생합니다.
웹후크 서명 인증을 안전하게 확인하는 방법
서명된 웹후크는 한 가지 질문에 답합니다: 이 페이로드는 공유 비밀을 알고 있는 사람에게서 왔습니까?
이것은 요청이 최근인지 여부를 묻는 것과는 다릅니다. 요청의 진위를 확인하는 첫 번째 문턱이 아닌 마지막 문턱입니다.

인증 흐름
일반적인 HMAC 흐름은 다음과 같습니다.
- 제공자의 헤더에서 서명을 읽습니다.
- Read the 원본 요청 바디를 받은 그대로 읽습니다.
- 보안 설정에서 웹훅 시크릿을 로드하세요.
- 같은 알고리즘을 사용하여 예상된 HMAC을 재계산하세요.
- 타이밍-안전한 비교를 사용하여 받은 서명과 계산된 서명을 비교하세요.
- 일치하지 않으면 요청을 거부하세요.
그것은 raw-body 단계에서 많은 좋은 구현이 실패하는 곳입니다. JSON을 먼저 파싱하는 경우, 공백을 재정렬하거나 인코딩 세부 정보를 변경하는 경우, 계산된 서명이 제공자의 서명과 일치하지 않습니다.
실제 code에서 무엇을 주의해야 하나요?
나는 가장 자주 보는 오류입니다:
- 해시된 JSON. 하지 마세요
JSON.stringify(req.body)그것을 일치시키기를 기대합니다. - 일반 문자열 비교를 사용합니다.타이밍-안전한 비교를 사용하세요.
- 비밀을 직접 입력하지 마세요.환경 변수나 비밀 관리 도구에 저장하세요.
- 헤더만 믿지 마세요.서명 헤더는만 검증하면 의미가 있습니다.
서비스 간 비밀 처리를 강화하는 팀에게는 Capgo의 API 앱 스토어 준수성을 위한 키 보안 가이드 관련성이 있습니다. 웹훅 수신자에게도 동일한 규칙이 적용됩니다. 비밀轮换, 범위별 접근, 로그에 누출을 피하는 것이 중요합니다.
일반적인 검증 예시
const crypto = require('crypto');
function verifySignature(rawBody, receivedSignature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const a = Buffer.from(receivedSignature, 'utf8');
const b = Buffer.from(expected, 'utf8');
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
}
이것은 의도적으로 일반적입니다. 실제 제공자는 서명에 시간戳을 결합하거나, 서명된 콘텐츠에 시간戳을 포함하거나, 해시를 다른 방식으로 인코딩할 수 있습니다. 규칙은 동일합니다. 제공자의 정확한 서명 형식을 따르세요. 그리고 항상 원본 페이로드와 검증하세요.
재생 공격 방지
서명된 웹후크가 여전히 위험할 수 있습니다. 그것이 몇 시간 후에 도착하고 처리기에서 새로운 것으로 처리하는 경우가 더 자주 발생합니다. 팀이 예상하는 것보다.

서명 확인은 공유 비밀로 생성한 이 페이로드의 보낸 사람을 확인하는 질문에 답합니다. 재생 보호는 다른 질문에 답합니다: 현재 이 요청을 여전히 받아들여야 합니까?
실제로 중요하다는 최소한의 확인
실용적인 재생 방어는 서명된 타임스탬프로 시작됩니다. 제공자는 헤더나 서명된 메시지에 타임스탬프를 포함하고, 수신자는 작은 허용 범위 내의 요청을 거부합니다.
그런 흐름은 다음과 같이 보아야 합니다:
- 제공자가 정의한 위치에서 타임스탬프를 읽어라.헤더 이름을 추측하지 마라.
- RFC 형식으로 날짜로 파싱하거나, 제공자의 스펙에 따라 정수로 파싱하라.서버 클록과 비교하라.
- 재생 공격을 방지하는 데 필요한 최소한의 확인.
- 이 요청이 너무 오래된거나 너무 먼 미래의 요청을 거부합니다..
- 서명 체계의 일부로 타임스탬프를 확인합니다. 제공자가 지원할 때만.
그 마지막 점은 중요합니다. 타임스탬프가 서명에 포함되지 않으면 공격자는 원래 본문을 교체하여 원래 본문을 재생할 수 있습니다. 항상 제공자의 정확한 서명 형식을 확인하여 타임스탬프 논리를 믿기 전에.
tolerance window를 선택하는 것
5분은 일반적인 기본값입니다. 공격 창을 줄이기 위해 충분히 짧지만, 작은 시계 드리프트와 일반적인 네트워크 지연을 견딜 수 있습니다.
이것은 상호 작용의 균형입니다. 30초 창은 더 안전해 보이지만, 실제 시스템에서 다시 시도, 큐잉, 또는 지역 지연이 포함된 경우 더 자주 발생합니다. 30분 창은 운영하기 더 쉬우나, 서명된 요청이 노출된 경우 공격자에게 더 많은 시간을 제공합니다. 몇 분으로 시작하고 서버를 NTP와 동기화한 다음 제공자의 전달 패턴이 지원하는 경우에만 조정하세요.
재생 방어는 단순히 타임스탬프 확인이 아닙니다.
타임스탬프 검증은 오래된 요청을 차단합니다. 유효한 창 내에서 동일한 서명된 이벤트가 두 번 전달되는 경우에도 중복 처리를 막지는 않습니다. 동일한 서명된 이벤트가 유효한 창 내에서 두 번 전달되는 경우, 애플리케이션은 여전히 이를 식별해야 합니다.
두 번째 층을 사용하세요:
- 이벤트 ID 또는 전달 ID를 추적하세요. Redis와 같은 짧은 생명 주기의 저장소에.
- Treat handlers as idempotent 이벤트가 중복으로 발생하지 않도록 하기 위해, 중복된 주문, 이메일, 또는 청구 작업이 생성되지 않도록 한다.
- Log rejected stale requests reason code와 함께, 하지만 비밀번호나 sensitive payload를 로그하지 않는다.
- Return a fast response validation과 queue heavy work은 다른 곳에서 처리한다.
Teams that already think about expiry windows and revocation will recognize the pattern. Capgo’s guide to Capacitor 앱에서 token revocation patterns을 다루는 Capacitor’s 가이드 credential이나 request가 한 번 유효했다면, 영원히 신뢰되지 않아야 한다.
Signed and stale is still unsafe.
Node.js에서 Webhook Receiver를 구축하는 방법
Node.js와 Express를 사용하면 빠르게 serious receiver를 온라인에 올릴 수 있지만, 가장 중요한 함정은 raw body에 접근할 수 있어야 한다는 것이다.

생산 환경에 맞는 Express 예시
const express = require('express');
const crypto = require('crypto');
const app = express();
const PORT = process.env.PORT || 3000;
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
// Capture raw body for signature verification
app.use(
express.json({
verify: (req, res, buf) => {
req.rawBody = buf;
},
})
);
function safeEqual(a, b) {
const aBuf = Buffer.from(a, 'utf8');
const bBuf = Buffer.from(b, 'utf8');
if (aBuf.length !== bBuf.length) return false;
return crypto.timingSafeEqual(aBuf, bBuf);
}
function verifySignature(rawBody, secret, receivedSignature) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return safeEqual(expected, receivedSignature);
}
function isFresh(timestampHeader, toleranceSeconds = 300) {
const timestamp = Number(timestampHeader);
if (!Number.isFinite(timestamp)) return false;
const now = Math.floor(Date.now() / 1000);
return Math.abs(now - timestamp) <= toleranceSeconds;
}
app.post('/webhooks/example', async (req, res) => {
const signature = req.get('x-webhook-signature');
const timestamp = req.get('x-webhook-timestamp');
if (!WEBHOOK_SECRET) {
return res.status(500).send('Webhook secret is not configured');
}
if (!signature || !timestamp) {
return res.status(400).send('Missing required security headers');
}
if (!isFresh(timestamp)) {
return res.status(401).send('Stale webhook');
}
const valid = verifySignature(req.rawBody, WEBHOOK_SECRET, signature);
if (!valid) {
return res.status(401).send('Invalid signature');
}
// Acknowledge quickly
res.status(200).send('OK');
// Process after acknowledgement
try {
const event = req.body;
console.log('Accepted event:', event.event, event.id);
// enqueueJob(event)
} catch (err) {
console.error('Post-ack processing failed:', err);
}
});
app.listen(PORT, () => {
console.log(`Webhook receiver listening on ${PORT}`);
});
이 구조가 지속되는 이유
여기서 일부 선택은 의도적으로 선택한 것입니다.
- 미들웨어에서 Raw body 캡처가 발생합니다.. 원래 바이트를 해싱하기 위해 보존됩니다.
- 타임스탬프는 비즈니스 로직 전에 확인됩니다.. 스테일 트래픽에 대한 작업을 할 필요가 없습니다.
- 경로가 반환됩니다.
200빠르게.장시간 작업은 큐 또는 백그라운드 작업에 속합니다. - Post-ack 처리는 격리됩니다.. downstream logic 오류가 발생해도 수신 경로의 크기는 작습니다.
웹훅 구현에서 많은 경우 secret는 weak point입니다. source에 저장하지 마세요, test fixture에 복사하지 마세요, 로그에 출력하지 마세요. rotation과 CI 처리를 위한 broader process가 필요하다면 Capgo의 CI/CD pipeline에서 secret 관리하는 방법에 대한 설명을 참조하세요. CI/CD pipeline에서 secret 관리하는 방법 CI/CD pipeline에서 secret 관리하는 방법에 대한 설명은 operational 측면에서 잘 다루고 있습니다.
작업을 시작하기 위해 짧은 walkthrough가 도움이 될 것입니다:
실제 시스템에서 변경할 점
실제 제공자 통합을 위해 live 시스템을 구축한다면, event ID deduplication을 persistent storage에서, request ID를 포함한 structured logs를, 그리고 확인 경로 뒤에 queue를 구축하는 것이 좋습니다. 또한 generic endpoint를 사용하지 말고, 여러 제공자가 다르게 signature format을 사용하는 경우를 대비하여 single endpoint를 사용하지 마세요. handler를 분리하면 더 쉽게 이해할 수 있고, 더 쉽게 break되지 않습니다.
Python에서 Webhook Receiver를 구축하는 방법
Flask는 clean web hook 예제를 구축하기에 적합한 선택입니다. Flask는 request handling이 explicit하고, Python의 standard library는 HMAC를 위한 필요한 모든 것을 제공합니다.
주요 점은 Node와 동일합니다. raw request bytes를 확인하여, parsed JSON dict를 확인하지 마세요.
Flask에서 signature와 timestamp를 확인하는 예제
import os
import time
import hmac
import hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get("WEBHOOK_SECRET", "")
def is_fresh(timestamp_header, tolerance_seconds=300):
try:
timestamp = int(timestamp_header)
except (TypeError, ValueError):
return False
now = int(time.time())
return abs(now - timestamp) <= tolerance_seconds
def verify_signature(raw_body, secret, received_signature):
expected = hmac.new(
secret.encode("utf-8"),
raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, received_signature)
@app.route("/webhooks/example", methods=["POST"])
def webhook():
if not WEBHOOK_SECRET:
return "Webhook secret is not configured", 500
signature = request.headers.get("X-Webhook-Signature")
timestamp = request.headers.get("X-Webhook-Timestamp")
if not signature or not timestamp:
return "Missing required security headers", 400
if not is_fresh(timestamp):
return "Stale webhook", 401
raw_body = request.get_data()
if not verify_signature(raw_body, WEBHOOK_SECRET, signature):
return "Invalid signature", 401
payload = request.get_json(silent=True) or {}
# Acknowledge receipt
response = jsonify({"status": "ok"})
# In production, queue payload here instead of heavy sync work
print("Accepted event:", payload.get("event"), payload.get("id"))
return response, 200
if __name__ == "__main__":
app.run(port=5000, debug=True)
Flask에서 중요한 detail은 Flask-specific detail입니다.
request.get_data() 이것이 여기서의 키 호출입니다. 이 키는 본문에 있는 raw bytes를 제공합니다. 본문에 직접 뛰어들면, 서명 불일치가 혼란스러워질 때가 이미 지나간 것입니다. request.json서명 불일치가 혼란스러워질 때까지는 이미 경계를 넘어간 것입니다.
implementation에 대한 몇 가지 주의사항입니다:
- plain한 등등 대신
hmac.compare_digest를 사용하세요. - missing한 헤더를 클라이언트 실패로 처리하고 를 사용하세요.
- JSON 파싱을 위해
silent=True를 사용하세요. Flask가 오류를 발생시키지 않고 오류 처리를 제어하고 싶다면. 경로를 얇게 유지하세요. - 를 사용하여 작업을 큐에 넣으세요. payload가 비용이 많이 드는 작업을 트리거하면.오류를 처리하는 대신 Flask가 오류를 발생시키지 않도록하고 싶다면 JSON 파싱을 위해
Don’t debug signature mismatches by relaxing security checks. Debug them by printing exactly what bytes you hashed and exactly what format the provider expects.
팀들이 일반적으로 막히는 부분
일반적인 실패 경로는 JSON 형식의 손수 만든 본체를 테스트 한 다음 실제 제공자로 전환하고 서명이 더 이상 일치하지 않음을 발견하는 것입니다. 일반적으로 이는 다음 중 하나의 이유로 발생합니다: 제공자는 시간이 지남에 따라 봉투를 서명하거나 서명이 당신이 가정한 것과 다르게 인코딩되거나 미들웨어가 본체를 검증하기 전에 변경했습니다.
그럴 때는 중간에 crypto code를 임의로 변경하지 마십시오. 원본 헤더와 원본 본체를 캡처하고, 작은 고립된 스크립트에서 해시를 재생산한 다음 Flask 경로에 다시 넣으십시오.
Go로 웹후크 수신기를 구축하는 방법
Go는 웹후크 수신기에 좋은 선택입니다. 표준 라이브러리가 충분하기 때문입니다. 작은, 신뢰할 수 있는 핸들러를 얻기 위해 프레임워크가 필요하지 않으며 code를 쉽게 신뢰할 수 있습니다.
주의할 점은 본체 처리입니다. r.Body 본체는 스트림입니다. 한 번 읽고, 얻은 바이트를 해시하고, 그리고 그 동일한 바이트에서 언마샬링하십시오.
표준 라이브러리 예제
package main
import (
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"encoding/json"
"io"
"log"
"net/http"
"os"
"strconv"
"time"
)
type WebhookPayload struct {
Event string `json:"event"`
ID string `json:"id"`
Data json.RawMessage `json:"data"`
}
func isFresh(timestampHeader string, toleranceSeconds int64) bool {
ts, err := strconv.ParseInt(timestampHeader, 10, 64)
if err != nil {
return false
}
now := time.Now().Unix()
diff := now - ts
if diff < 0 {
diff = -diff
}
return diff <= toleranceSeconds
}
func verifySignature(rawBody []byte, secret string, received string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(rawBody)
expected := hex.EncodeToString(mac.Sum(nil))
if len(expected) != len(received) {
return false
}
return subtle.ConstantTimeCompare([]byte(expected), []byte(received)) == 1
}
func webhookHandler(secret string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
signature := r.Header.Get("X-Webhook-Signature")
timestamp := r.Header.Get("X-Webhook-Timestamp")
if signature == "" || timestamp == "" {
http.Error(w, "missing required security headers", http.StatusBadRequest)
return
}
if !isFresh(timestamp, 300) {
http.Error(w, "stale webhook", http.StatusUnauthorized)
return
}
rawBody, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "failed to read body", http.StatusBadRequest)
return
}
if !verifySignature(rawBody, secret, signature) {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
var payload WebhookPayload
if err := json.Unmarshal(rawBody, &payload); err != nil {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
log.Printf("accepted event=%s id=%s", payload.Event, payload.ID)
}
}
func main() {
secret := os.Getenv("WEBHOOK_SECRET")
if secret == "" {
log.Fatal("WEBHOOK_SECRET is not set")
}
http.HandleFunc("/webhooks/example", webhookHandler(secret))
log.Println("listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Go가 여기서 견고한 이유
몇 가지 장점이 눈에 띕니다.
- 핸들러는 명확합니다. __CAPGO_KEEP_0__
- Edge에서 __CAPGO_KEEP_1__이 도움이 됩니다.. __CAPGO_KEEP_2__ parsing, timestamp conversion, and JSON decoding 모두 명확하게 실패합니다.
- 기본적인 HMAC 검증을 위한 추가 의존성이 필요하지 않습니다.. __CAPGO_KEEP_0__
운영 노트
웹훅 볼륨이 증가하면 Go의 동시성 모델은 HTTP 진입점을 변경하지 않고 배경 작업을 분산하는 데 충분한 공간을 제공합니다. 그 때도 수신자 너비를 유지하세요. 수락, 유효성 검사, 확인, 그리고 응답이 돌아오기 전에 작업을 넘겨주세요.
Go 웹훅 핸들러 중 가장 강력한 것들은 재미없습니다. transport 검증을 비즈니스 로직과 섞지 않고, 응답이 돌아오기 전에 데이터베이스 작업을 수행하지 않습니다.
중요한 디버깅 기법
웹훅 버그는 일반적으로 스택 트레이스 대신 지원 메시지로 나타납니다. 제공자는 이벤트를 전달했다고 말합니다. 엔드포인트는 앱에 도달한 것이 없거나, 요청이 처음으로 유효해보이는 것처럼 보이지만 서명 검증이 실패했다고 말합니다. 그 때 디버깅은 정확한 HTTP 교환을 재구성하고, 바이트 단위로 증명하는 것입니다. 그곳에서 디버깅이 실패합니다.

실용적인 디버깅 도구킷
__CAPGO_KEEP_0__.
__CAPGO_KEEP_0__ 시그니처 검증이 실패하면, 원본 요청 바디와 검증을 위해 사용한 헤더를 정확하게 캡처하세요. 실제로, 버그는 종종 지루합니다. 프레임워크는 JSON을 해싱하기 전에 파싱했거나, 프록시는 인코딩을 변경했거나, 테스트 재생은 원래 타임스탬프 헤더를 놓쳤습니다. 파싱된 객체만 로깅하는 것은 충분하지 않습니다. 원본 바이트와 검증 입력이 필요합니다.
__CAPGO_KEEP_0__ 이 도구들은 문제를 빠르게 분리하는 데 도움이 됩니다:
- 원본 요청 캡처로그 헤더, 콘텐츠 유형, 콘텐츠 길이, 그리고 수정되지 않은 바디를 조사 중입니다.
- 요청 검사 엔드포인트서비스들
webhook.site__CAPGO_KEEP_0__이 보내진 내용을 확인하는 데 도움이 됩니다. - 로컬 터널링.
ngrok및 같은 도구들은 로컬 수신자와 테스트할 수 있으면, 제공자도 포함시킬 수 있습니다. - 수동 재생__CAPGO_KEEP_0__ 요청을 원본으로 재구성합니다.
curlor Postman을 사용하여 동일한 본문과 헤더를 사용하여. 그게 code 또는 제공자 페이로드가 문제인지 가장 빠르게 확인하는 방법입니다. - 제공자 전송 로그. 보낸 사람 대시보드에는 종종 응답 코드, 재시도 기록 및 요청 식별자가 로그와 일치시킬 수 있는 항목이 포함되어 있습니다.
패턴이 중요합니다. 외부에서 내부로 작업하세요. 먼저 제공자가 예상한 것과 같은 것을 보냈는지 확인하세요. 그런 다음 서버가 동일한 바이트를 받았는지 확인하세요. 그런 다음 code가 동일한 바이트와 동일한 비밀번호와 타임스탬프 규칙으로 동일한 해시를 생성했는지 확인하세요.
실제로 도움이 되는 로깅
좋은 웹후크 로그는 한 번의 검색으로 세 가지 질문에 답해야 합니다.
| 질문 | 유용한 로그 필드 |
|---|---|
| 요청이 도착했는가? | route, method, received_at |
| 왜 거부되었는가? | missing_header, stale_timestamp, signature_failed |
| 나중에 연관관계를 설정할 수 있나요? | 이벤트 ID, 제공자 요청 ID |
실제 시스템에서 네 번째 필드는 도움이 됩니다. 로컬 저장소에 항목을 추가합니다. request_id 애플리케이션, 큐, 및 워커 로그를 통해 요청을 추적하기 위해 수신기에서 생성한 것으로, 앱, 큐, 및 워커 로그를 통해 요청을 추적할 수 있습니다.
production 데이터에 포함된 고객 정보, 접근 토큰, 또는 청구 세부 정보가 있는 경우 전체 프로덕션 페이로드를 덤프하지 말라. 보다 안전한 패턴은 로그 메타데이터와 짧은 바디 해시를 기록하는 것이다. 그래도 재시도 비교와 두 번의 전달이 동일한지 확인할 수 있다.
원래 입력값으로 실패를 재현하십시오.
이 부분은 기본 튜토리얼이 생략하는 부분입니다. 만약에 실패한 요청을 정확히 재생할 수 없다면, 그저 추측하는 것입니다.
웹후크 저장 실패를 위한 이름을 입력하세요:
- raw body bytes
- Capgo에서 관련된 모든 서명 헤더
- 요청 시간
- 컨텐츠 타입
- provider request ID
그런 다음 스테이징 엔드포인트에 다시 재생합니다. 재생이 통과하면 전송 중에 변경된 것을 비교합니다. 일반적인 원인에는 요청 본체를 정규화하는 미들웨어, 문자 인코딩 불일치, 헤더를 제거하거나 다시 쓰는 로드 밸런서가 있습니다. 또한 팀이 실제 요청 본체 대신 예쁘게 출력된 대시보드 뷰에서 페이로드를 복사하는 경우도 실패를 유발합니다. whitespace 차이만으로 HMAC 검증을 깨트릴 수 있었습니다.
더 광범위한 릴리스 및 모바일 전송 문제 해결을위한 Capgo의 가이드 Capacitor의 OTA 업데이트에 대한 디버깅 도구다른 전송 방법, 동일한 교훈. 실제 요청 경로를 변경하기 전에 애플리케이션 code를 캡처하십시오.
서명 검증이 실패하면, 원시 바이트, 정확한 헤더, 시간 스탬프 값을 검토하기 전에 암호화 code를 조작하지 마십시오.
제품 준비가 된 웹후크의 체크리스트
웹후크 핸들러는 스테이징에서 처음으로 재시도 폭풍, 잘못된 페이로드, 또는 2시의 서명 불일치로까지 괜찮아 보일 것입니다. 그러나 제품 버전은 더 높습니다. 수신자는 위조된 요청을 거부하고, 합법적인 재시도를 수락하고, 실패를 디버깅하기 위해 운영자에게 충분한 신호를 제공해야 합니다. 그러나 sensitive 데이터를 노출하지 않도록해야 합니다.
보안 및 정확성 검사
- 모든 요청 서명을 검증하십시오.엔드포인트 URL이 누설됩니다. 테스트 URL은 채팅에서 공유됩니다. 서명 검증은 보낸 사람에게 공유된 비밀 키를 알았는지 알려주는 제어가 됩니다.
- 기존 요청을 거부하십시오. 올드 페이로드에 대한 유효한 서명은 여전히 재생할 수 있습니다. 제공자의 재시도 모델과 일치하는 타임스탬프 허용 범위를 강제하십시오.
- JSON을 해시하지 말고 raw body를 해시하십시오. 미들웨어는 키를 재배치할 수 있습니다, 공백을 정규화할 수 있습니다, 또는 인코딩을 변경할 수 있습니다. 검증은 정확히 도착한 바이트에 대해 실행해야 합니다.
- code에 서명 비밀을 유지하지 마십시오.. 환경 변수는 기본입니다. 자주 암호를 회전하거나 여러 환경에서 실행하는 경우 비밀 관리자가 더 적합합니다.
- 인증 오류 시 실패. 서명 헤더가 누락되거나 오류가 발생하거나 scheme이 예상치 못한 경우 요청을 거부하고 이유를 로깅하십시오.
신뢰도 검사
- 빠른 응답을 인식하십시오. 제공자는 일반적으로 2xx를 성공으로 처리하므로 요청을 검증하고 필요한 것을 저장한 후 느린 작업을 큐 또는 워커로 이동하십시오.
- 핸들러를 무결성 있게 유지하십시오. 동일한 이벤트는 여러 번 도착할 수 있습니다. 이벤트 ID, 전달 ID, 또는 다른 안정적인 제공자 식별자로 이벤트의 부수 효과를 분리하십시오.
- Return predictable error codes. 사용하여
400잘못된 입력 또는401인증 실패 시403만 사용하고, 시스템이 문제가 될 때만 사용합니다. 이로 인해 제공자 재시도 동작을 더 쉽게 이해할 수 있습니다.5xx파싱하기 전에 제한을 설정하십시오 - . 웹후크 엔드포인트가 일반적인 인식 구멍으로 변하지 않도록 요청 크기, 콘텐츠 유형 및 헤더 수를 조기에 설정합니다.계약을 좁게 유지하십시오
- . 지원하는 field 및 이벤트 유형만 수락하십시오. 느슨한 파싱은 처음에는 편리하게 느껴지지만 제공자 __CAPGO_KEEP_0__ 변경 시 비용이 많이 들 수 있습니다.. Accept only the fields and event types you support. Loose parsing feels convenient at first and becomes expensive during provider API changes.
좋은 웹후크 작업은 흥미롭지 않습니다. 팀은 빠르게 세 가지 질문에 답할 수 있습니다: 받았습니까? 인증했습니까? 하류 처리가 성공했습니까?
Good webhook operations look boring. Teams can answer three questions quickly: Did we receive it? Did we verify it? Did downstream processing succeed?
이 표준을 사용하세요.
- 수신, 인증, 처리를 별도의 결과로 추적하세요..
- 요청 ID, 이벤트 ID, 서명 상태, 타임스탬프 왜곡을 로깅하세요..
- 대기열 지연, 핸들러 지연, 재시도 양을 측정하세요..
- 스테이징 또는 재배달 워크플로우의 안전한 재생 경로를 유지하세요..
- 패턴 변경에 대한 알림, 서명 실패의 급증 또는 중복 배달과 같은 예시입니다.
Capgo는 더 광범위한 운영점을 보여주는 유용한 예시입니다. 업데이트 워크플로우에서 배포 관련 도구 및 관찰 가능성을 포함하고, 웹후크 관련 흐름에 영향을 미치는 일부 생태계 부분도 있습니다. 이 교훈은 실제입니다. 배달 시스템은 수신부터 완료까지의 시각성을 필요로 합니다.
팀이 위의 점검을 모두 수행하면 웹후크 수신자는 일반적으로 프로덕션에서 좋은 상태입니다. 만약 하나라도 누락된다면, 그 빈틈은 인시던트 중에 나타나며 데모 중에는 나타나지 않습니다.
웹후크에 대한 자주 묻는 질문
code에서 반환해야 하는 상태는 무엇인가요?
반환하십시오. 2xx 웹훅을 수락한 경우에만 작동합니다. 유효성 검사 실패 시, 클라이언트 또는 인증 오류를 반환하여 실패를 일치시킵니다. 400 잘못된 입력 또는 401 잘못된 인증 데이터를 위한 오류입니다. 그 로직을 일관되게 유지하여 제공자 대시보드가 더 쉽게 해석되도록 합니다.
웹훅을 동기적으로 처리해야 하나요?
보통 그렇지 않습니다. 유효성 검사, 확인, 그리고 실제 작업을 큐 또는 백그라운드 워커로 밀어넣어 주면 됩니다. 전달 경로가 빠르고 느린 하위 스트림 처리로 인한 중복된 재시도 횟수를 줄일 수 있습니다.
재시도는 어떻게 처리해야 하나요?
재시도가 발생할 것이라고 가정하십시오. 핸들러에 중복성을 구축하여 동일한 이벤트를 받았을 때 중복된 부수 효과가 발생하지 않도록 하십시오. 이벤트 ID 또는 제공자 전달 ID가 일반적으로 그 역할을 합니다.
이벤트가 순서가 아닌 순서로 도착하는 경우 어떻게 해야 하나요?
가능한 경우 핸들러를 순서에 내성이 있게 설계하십시오. 사업 프로세스가 순서가 필요하다면陈舊한 전환을 감지하기 위해 충분한 상태를 저장하여 전달 순서가 이벤트 순서를 반영하는 것으로 가정하지 않도록 하십시오.
웹훅 버전 변경은 어떻게 처리해야 하나요?
핸들러 로직을 의도적으로 버전화하십시오. 제공자별 파싱을 분리하고, 코드베이스에 중복된 페이로드 가정치를 흩어지지 않도록 하십시오. 그리고 새로운 형식에 대한 지원을 출시하기 전에 실제 캡처된 샘플과 함께 테스트를 추가하십시오.
If your team ships Capacitor or Electron apps, Capgo __CAPGO_KEEP_0__는 팀이 앱 스토어 리뷰를 기다리지 않고 signed web 업데이트를 전달하고, rollout 동작을 관찰하고, 사고에서 복구할 수 있는 제어된 방법을 제공하기 때문에 관련된 이유로 알아야 할 것입니다. 이는 solid webhook 디자인의 동일한 엔지니어링 인стин트와 일치합니다: 입력을 검증하고, 릴리스 경로를 관찰하고, 복구를 빠르게 하세요.