메인 콘텐츠로 건너뛰기

웹 훅 예제: 안전한 구현 지침

Node.js, Python 및 Go를 위한 완전한 웹 훅 예제를 찾으십시오. code을 사용하여 서명 확인, 재생 공격 방지 및 엔드포인트 디버깅을 학습하십시오.

마틴 도나디우

마틴 도나디우

콘텐츠 마케터

웹 훅 예제: 안전한 구현 지침

서비스가 특정 이벤트가 발생할 때 반응할 필요가 있을 때가 있습니다. 결제가 완료되거나 고객 기록이 변경되거나 리포지토리가 푸시되었습니다. API를 매 분마다 폴링하여 '새로운 항목이 있나요?'라는 질문을 반복적으로 묻는 것보다, 이벤트가 발생할 때 소스 시스템이 호출할 수 있도록 할 수 있습니다.

대부분의 웹 훅 예제 기사에서는 여기서 멈칫합니다. 그들은 경로를 보여주고 JSON 본문을 출력하고 반환합니다. 200, 그리고 그것을 완료합니다. 그 버전은 완벽하게 작동하지만, 누군가가 위조된 요청을 보내거나 유효한 요청을 재생하거나 프레임워크가 서명 확인 전에 요청 본문을 파싱하는 경우 핸들러가 깨질 수 있습니다.

이 안내서에서는 실제로 사용할 프로덕션 경로를 취합니다. 예시는 작지만, 중요한 부분을 포함합니다: raw body handling, HMAC verification, timestamp checks, fast acknowledgment, and practical debugging.

목차

웹 훅이란 무엇이며 왜 사용해야 하나?

계정 제공 업체는 02:13에 청구서를 지불한 것으로 표시합니다. 만약 앱이 02:14에 알게 되면 고객은 즉시 접근할 수 있습니다. 만약 앱이 다음 폴링 사이클에 알게 되면 고객은 기다리게 되고, 지원 팀은 티켓을 받고, 로그는 불필요한 잡음으로 채워집니다. 웹 훅은 이러한 시간 문제를 해결하기 위해 이벤트가 발생했을 때 HTTP 콜백을 보내는 것입니다.

실제로 웹 훅은 이벤트 기반의 POST 요청입니다. 제공 업체는 변경을 감지하고, 예를 들어 invoice.paid, order.created또는 push이벤트 데이터를 URL을 제어하는 URL로 보내는 것입니다. 이는 폴링이 생성하는 "새로운 것이 있는가?"라는 지속적인 루프를 제거하고 많은 불필요한 요청을 줄입니다.

실제 시스템에서 나타나는 패턴은 사업 이벤트에 매핑되기 때문에 CLEAN합니다. Stripe은 결제 결과를 게시합니다. GitHub은 저장소 활동을 게시합니다. Shopify는 주문 업데이트 게시합니다. 형식은 간단하지만 실제 운영 환경의 동작은 아닙니다. 결제, 접근, 또는 재고를 업데이트하는 웹 훅은 공공 API 엔드포인트와 동일한 주의를 기울여야 합니다. 특히 재시도, 중복, 그리고 신뢰할 수 없는 트래픽이 들어오면 더욱 그렇습니다.

정신적 모델이 도움이 됩니다.

웹 훅 흐름을 프레임하는 유용한 방법은 네 가지 부분이 함께 작동하는 것입니다:

  • 원천 시스템. 이벤트를 감지하는 서비스
  • 목적지 엔드포인트. 웹 훅을 받는 HTTP 경로
  • 이벤트. 발생한 이름이 지정된 변경 사항, 예를 들어 invoice.paid or push.
  • Payload. code이 필요로 하는 요청 본문에 포함된 세부 정보

이벤트가 발생한 후에 제공자에서 이미 발생한 사실에 대한 정보를 보내는 경우, 그 정보를 확인하고, 요청이 최신인지 확인하고, 변경 사항을 적용하는 일은 중요합니다. 마지막 부분은 많은 기본 튜토리얼에서 인정하지 않는 경우가 많습니다. 실제 운영 환경에서 중복 전달은 일반적인 동작이며, corner case가 아닙니다.

실용적인 규칙: 웹 훅을 사용하여 이벤트 기반 업데이트, 폴링을 사용하여 예약된 읽기, 백필, 제공자가 외부 이벤트를 제공하지 않는 경우에 사용하십시오.

보다 광범위한 워크플로 자동화 및 데이터 통합, 웹 훅은 불필요한 요청 트래픽 없이 시스템을 동기화하는 이벤트 층이 됩니다. 통합-heavy 서비스를 개발하는 경우 Capgo의 백엔드 개발 기사 는 유용한 배경 지식입니다. core 문제는 재시도, 큐, 관찰성, 실패 처리와 관련이 있습니다.

운영 환경에서 성공하고 실패하는 것

잘 유지되는 설정은 보통 설계가 단순합니다. 필요한 이벤트만 구독하십시오. 제공자나 이벤트 패밀리별로 엔드포인트를 스코프하십시오. 이벤트 ID를 저장하여 중복 전달이 반복되는 부작용을 피하십시오. 요청이 검증되고 큐에 들어간 후 빠른 2xx 응답을 반환하고, 느린 비즈니스 로직은 비동기적으로 처리하십시오.

약한 버전은 쉽게 식별할 수 있습니다. 하나의 일반적인 엔드포인트가 모든 것을 처리합니다. 서명 확인이 초기 테스트 중에 건너뛰어지고 다시 돌아오지 않습니다. 핸들러는 이벤트가 인증되거나陈舊한지 확인하기 전에 직접 중요 테이블에 쓰게 됩니다. 데모에서 작동하지만 재시도 폭풍, 제공자 장애 또는 공격자가 이전 요청을 재생하는 경우에 실패합니다.

이 안내서의 나머지 부분은 이 거래를 정의합니다. 웹후크 수신자 'hello world' 버전은 작습니다. 프로덕션 준비 버전은 서명 확인, 재생 방어, 중복 처리 및 디버깅 훅을 시작부터 제공합니다.

웹후크 HTTP 요청의 구조

code을 작성하기 전에, 요청을 프레임워크 객체 대신 raw HTTP로 보는 것이 도움이 됩니다. 일반적인 웹후크는 public endpoint에 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디버깅을 위해 도움이지만 충분히 신뢰할 수 있는 것은 아닙니다.
  • 서명 헤더제공자의 인증 확인을 담고 있습니다.
  • 타임스탬프 헤더이미지나 재생중인 요청을 거부하기 위해 사용됩니다.

몸체의 형태는 왜 중요한가요?

code는 일반적으로 모든 field에 대해 신경 쓰지 않습니다. 이벤트 타입, 이벤트 식별자, 그리고 내부의 비즈니스 객체에만 관심이 있습니다. data그것이 왜 좋은 핸들러는 필요한 것만 파싱하고 나머지 로그를 디버깅하기 위해 사용하는 이유입니다.

OpenAPI는 이 패턴을 직접 모델링합니다. OpenAPI 3.1.0은 첫 번째급 웹훅 지원을 제공하며, 각 웹훅은 제공자에 의해 트리거되는 Path Item과 같은 형태로 설명됩니다. canonical 예제는 웹훅, operation, JSON 요청 본문, 그리고 수신을 나타내는 response를 사용합니다. webhooks operation newPet response post response 200 response to indicate receipt, as shown in the 웹훅 예제.

자신의 수신자 또는 제공자 계약을 문서화할 때, 강력한 예제가 추상적인 스키마 문장보다 더 도움이 됩니다. 나는 참조를 사용하는 것을 좋아합니다. SheetMergy의 API 문서 예제 그것들은 요청 예제, 필드 설명, 그리고 기대되는 응답이 어떻게 함께 작동하는지 명확하게 보여줍니다.

웹훅은 전송层에서 단순합니다. 대부분의 실패는 헤더, 본문 인코딩, 또는 서명 규칙에 대한 일치하지 않는 가정 때문입니다.

웹훅 서명 인증을 안전하게 보장하는 방법

서명된 웹훅은 한 가지 질문에 답합니다: 이 페이로드는 공유 비밀을 알고 있는 사람으로부터 왔습니까?

그것은 요청이 최근인지 또는 이미 처리했는지 여부를 묻는 것과는 다릅니다. 서명 인증은 첫 번째 게이트가 아니라 마지막 게이트입니다.

서명 인증을 통해 요청의 진위성과 보안을 보장하기 위한 6단계 프로세스를 minh họa하는 인포그래픽입니다.

인증 흐름

일반적인 HMAC 흐름은 다음과 같습니다.

  1. 제공자의 헤더에서 서명을 읽습니다.
  2. 웹훅 예제를 읽으십시오. 원본 요청 본문 원본 형태로 받은 것과 같이.
  3. 보안 설정에서 웹훅 비밀 키를 로드하십시오.
  4. 같은 알고리즘을 사용하여 예상된 HMAC을 재계산하십시오.
  5. 받은 서명과 계산된 서명을 타이밍-안전한 비교로 비교하십시오.
  6. 받은 서명과 계산된 서명이 일치하지 않으면 요청을 거부하십시오.

그 원본 본문 단계에서 많은 좋은 구현이 실패하는 곳입니다. JSON을 먼저 파싱하는 프레임워크, 공백을 재배치하거나 인코딩 세부 사항을 변경하는 경우, 계산된 서명이 제공자의 서명과 일치하지 않습니다.

실제 code에서 주의해야 할 점은 무엇인가요?

이러한 오류를 가장 자주 본 오류는 다음과 같습니다.

  • JSON을 해싱하는 것. 하지 마십시오. JSON.stringify(req.body) 그것이 일치할 것으로 기대합니다.
  • 일반 문자열 비교를 사용합니다.시간 안정 비교를 사용하세요.
  • 비밀번호를 직접 입력하지 마세요.환경 변수나 비밀번호 관리자에 저장하세요.
  • 헤더만 믿지 마세요.서명 헤더는 검증하지 않으면 의미가 없습니다.

서비스 간 비밀번호 관리를 강화하는 팀에게는 Capgo의 앱 스토어 준수성을 위한 Capgo 키 보안 가이드가 관련이 있습니다. API key security for app store compliance 일반적인 검증 예시입니다.

실제 제공자들은 서명에 접두사를 붙인다거나, 서명된 콘텐츠에 타임스탬프를 결합하거나, 해시를 다른 방식으로 인코딩합니다. 규칙은 동일합니다.

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);
}

제공자의 정확한 서명 형식을 따르세요. 그리고 항상 원본 페이로드와 검증하세요.

공격을 막는 방법

서명된 웹후크는 여전히 위험할 수 있습니다. 그것이 몇 시간 후에 도착하고 처리기에서 새로운 것으로 처리하는 경우가 더 자주 발생합니다. 그게 팀이 기대하는 것보다 더 자주 발생합니다. 프록시가 트래픽을 로깅하고, 요청 페이로드가 잘못된 곳으로 유출되거나, 제공자가 네트워크 오류 후에 다시 시도하고 엔드포인트가 동일한 이벤트를 두 번 처리하는 경우가 있습니다.

웹 애플리케이션에서 재생 공격을 효과적으로 방지하는 데 필요한 5 가지 주요 보안 조치에 대한 체크리스트입니다.

서명 검증은 공유 비밀로 생성한 페이로드의 보낸 사람을 확인하는 데 답변합니다. 재생 보호는 요청이 현재 받아들여질지 여부를 결정하는 다른 질문에 답변합니다. 실제로 생산 수신기에 필요한 것은 두 가지입니다.

실제로 중요하다는 최소한의 확인

실제로 재생 방어를 시작하는 것은 서명된 타임스탬프입니다. 제공자가 헤더나 서명된 메시지에 타임스탬프를 포함하고, 수신기가 작은 허용 범위 내에 있는 요청을 거부하는 경우입니다.

그 흐름은 다음과 같이 보이려면 됩니다:

  • 제공자가 정의한 위치에서 타임스탬프를 읽어라헤더 이름을 추측하지 마라.
  • RFC 형식으로 날짜로 파싱하라제공자의 스펙에 따라.
  • 서버 클록과 비교하라.
  • 이 요청이 너무 오래된 것인지 또는 너무 먼 미래인지 거부합니다..
  • 서명 체계의 일부로 타임스탬프를 확인합니다. 제공자가 지원할 때만.

마지막 점은 중요합니다. 서명이 타임스탬프를 포함하지 않으면 공격자는 원본 본문을 다시 보내는 새로운 타임스탬프를 교체할 수 있습니다. 항상 제공자의 정확한 서명 형식을 확인하여 타임스탬프 논리를 믿기 전에.

tolerance window를 선택하는 방법

5분은 일반적인 기본값입니다. 공격 창을 줄이기 위해 충분히 짧지만, 작은 시계 드리프트와 일반적인 네트워크 지연을 견딜 수 있습니다.

이것은 상호 보완적입니다. 30초 창은 더 안전해 보이지만, 실제 시스템에서 다시 시도, 큐잉, 또는 지역 지연이 포함된 경우 더 자주 깨집니다. 30분 창은 운영하기 더 쉽지만, 서명된 요청이 노출된 경우 공격자에게 더 많은 시간을 제공합니다. 몇 분을 시작하여 서버를 NTP와 동기화하고, 제공자의 전달 패턴이 지원하는 경우에만 단축하세요.

재생 방어는 단순히 타임스탬프 검증만이 아닙니다.

타임스탬프 검증은 오래된 요청을 차단하지만, 유효한 창 내에서 동일한 서명된 이벤트가 두 번 전달되는 것을 막지 않습니다. 동일한 서명된 이벤트가 유효한 창 내에서 두 번 전달되는 경우, 애플리케이션은 여전히 이를 식별해야 합니다.

두 번째 층을 사용하세요:

  • 이벤트 ID 또는 전달 ID를 추적하세요. Redis와 같은 짧은 생명 주기의 저장소에서.
  • 처리 핸들러를 idempotent으로 처리하십시오. 이러한 처리 핸들러로 인해 반복적인 전달이 발생하지 않도록 하십시오. 이로 인해 중복된 주문, 이메일, 또는 계정 관리 작업이 생성되지 않도록 하십시오.
  • 만료된 요청을 로그하십시오. 이유 코드와 함께, 하지만 비밀 키나 전체敏感 데이터를 로그하지 마십시오.
  • 유효성 검사 및 큐 작업이 다른 곳에서 처리되도록 하십시오. 빠른 응답을 반환하십시오.

유효 기간과 취소가 이미 고려되고 있는 팀은 패턴을 인식할 것입니다. Capgo의 Capgo 앱에서 토큰 취소 패턴에 대한 Capgo의 가이드를 참조하십시오. token revocation patterns in Capacitor apps Node.js에서 Webhook 수신기를 구축하는 방법

Node.js와 Express를 사용하면 빠르게 serious 수신기를 온라인으로 구축할 수 있지만, 다른 어떤 것보다 더 중요한 한 가지 함정에 주의하십시오. Express가 객체로 변환하기 전에 raw body에 접근할 수 있어야 합니다.

__CAPGO_KEEP_0__’s

__CAPGO_KEEP_0__

노트북이 나무 책상 위에 Node.js 수신기 code를 VS Code 편집 환경에서 표시하고 있습니다.

실제 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 처리는 분리되어 있습니다.서버 로직이 실패해도 수신 경로의 크기는 작습니다.

웹 훅 구현에서 가장 약한 부분은 비밀입니다. 소스 코드에 넣지 마세요, 테스트 fixture에 붙여넣지 마세요, 로그에 반영하지 마세요. 비밀을 rotation하고 CI 처리에 대한 더 광범위한 프로세스가 필요하다면 Capgo의 CI/CD pipeline에서 비밀 관리하는 방법에 대한 지침을 참조하세요. 비밀 관리 비밀 관리

비밀 관리를 위한 단계적인 walkthrough가 필요하다면:

실제 시스템에서 변경할 점

실제 제공자 통합을 위해, 이벤트 ID 중복 제거를 영구 저장소에, 요청 ID가 포함된 구조화 로그를, 확인 경로 뒤에 큐를 추가하는 것이 좋습니다. 또한 여러 제공자가 다르게 서명 형식을 사용하는 경우, 단일 일반적인 엔드포인트를 피하고 여러 핸들러를 사용하는 것이 좋습니다. 핸들러를 분리하면 더 쉽게 이해할 수 있고 더 쉽게 깨지지 않습니다.

Python에서 웹 훅 수신기 만들기

Flask는 웹 훅 예제에 적합한 선택입니다. Flask는 요청 처리가 명확하고, HMAC를 위한 Python 표준 라이브러리가 이미 제공되기 때문입니다.

Node와 동일한 주의사항을 기억하세요. raw 요청 바이트와 비교하여 parsed JSON 딕셔너리와 비교하지 마세요.

Flask에서 서명과 타임스탬프 검증을 위한 예제

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에서 중요한 세부 사항

request.get_data() 이것이 여기서 호출의 키입니다. 이 것은 본체의 raw bytes를 제공합니다. 만약 바로 서명 불일치로 인한 혼란을 피하기 위해 바로 건너뛰면... request.json, 그럼 이미 서명 불일치로 인한 혼란을 피하기 위해 바로 건너뛰었다는 것을 의미합니다.

몇 가지 구현 관련 주의사항이 있습니다:

  • 을 사용하세요. hmac.compare_digest 이 대신 평범한 등가 비교를 사용하세요.
  • 헤더가 누락된 경우 클라이언트 오류로 처리하고 일찍 거부하세요. 을 사용하세요.
  • JSON 파싱을 위해 사용하세요. 만약 Flask가 오류 처리를 자동으로 처리하는 대신 오류 처리를 직접 제어하고 싶다면. silent=True 경로를 얇게 유지하세요. . 비용이 많이 드는 작업이 트리거되면 작업을 큐에 넣으세요.
  • __CAPGO_KEEP_0____CAPGO_KEEP_0__

보안 검사 조정을 통해 서명 불일치 문제를 디버그하지 말고, 정확히 어떤 바이트를 해시했는지와 제공자가 기대하는 서명 형식이 정확히 무엇인지 출력하여 디버그하십시오.

팀들이 일반적으로 막히는 곳

일반적인 실패 경로는 JSON 본체를 수동으로 빌드하여 테스트한 다음 실제 제공자로 전환하여 서명이 더 이상 일치하지 않게 된 경우입니다. 일반적으로 이는 다음 중 하나의 문제를 의미합니다: 제공자는 시간이 표시된 봉투를 서명합니다, 서명이 당신이 가정한 것과 다르게 인코딩되어 있습니다, 또는 미들웨어가 본체를 검증하기 전에 변경했습니다.

서명 불일치 문제를 디버그할 때는 무작위로 암호 code를 변경하지 말고, 원본 헤더와 원본 본체를 캡처하고, 작은 고립된 스크립트에서 해시를 재생산한 다음 Flask 경로에 다시 넣으십시오.

Go로 웹후크 수신기를 구축하는 방법

Go는 웹후크 수신기에 좋은 선택입니다. 표준 라이브러리가 충분하므로 프레임워크가 필요하지 않으며 code를 쉽게 진실되게 유지할 수 있습니다.

몇 가지 주의할 점이 있습니다. r.Body 이는 스트림입니다. 한 번 읽고, 얻은 바이트를 해시하고, 그리고 같은 바이트에서 언마샬링하십시오.

Go 표준 라이브러리 예제

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가 여기서 견고한 이유

몇 가지 이점이 눈에 띕니다.

  • 핸들러는 명확합니다. No hidden middleware magic.
  • Edge에서 도움이 됩니다.. Header parsing, timestamp conversion, 및 JSON decoding 모두 명확하게 실패합니다.
  • 표준 암호화 패키지만 사용해도 충분합니다.. 기본 HMAC 검증을 위한 추가 종속성이 필요하지 않습니다.

운영 노트

웹훅 볼륨이 증가하면 Go의 동시성 모델은 HTTP 진입점을 변경하지 않고 배경 작업을 확장할 수 있는 공간을 제공합니다. 그 때도, 수신자 너비를 유지하세요. 수락, 유효성 검사, 확인, 그리고 응답을 전달하세요.

강력한 Go 웹훅 핸들러는 대부분 단순합니다. transport 검증과 비즈니스 로직을 혼합하지 않으며, 응답이 돌아오기 전에 데이터베이스 작업을 수행하지 않습니다.

필수 디버깅 기법

웹훅 버그는 일반적으로 스택 트레이스 대신 지원 메시지로 나타납니다. 제공자는 이벤트를 전달했다고 말합니다. 엔드포인트는 애플리케이션에 도달한 것이 없거나, 요청이 처음으로 유효하게 보이지만 서명 검증이 실패했다고 말합니다. 그 때, 디버깅은 정확한 HTTP 교환을 재구성하고, 바이트 단위로 증명하는 것입니다.

웹훅 디버깅을 위한 5가지 필수 도구 및 기법의 목록입니다.

실용적인 디버깅 도구킷

Wire 형식으로 시작하세요.

서명 확인이 실패하면, 원본 요청 본문을 정확히 받은 것과 함께, 검증을 위해 사용된 헤더를 캡처하세요. 실제로, 버그는 종종 지루합니다. 프레임워크는 JSON을 해싱하기 전에 파싱했거나, 프록시는 인코딩을 변경했거나, 테스트 재생은 원래 타임스탬프 헤더를 놓쳤습니다. 파싱된 객체를 로깅하는 것만으로는 충분하지 않습니다. 원본 바이트와 검증 입력이 필요합니다.

이러한 도구는 문제를 빠르게 분리하는 데 도움이 됩니다:

  • 원본 요청 캡처수사 중에 로깅할 때, 헤더, 콘텐츠 타입, 콘텐츠 길이, 그리고 수정되지 않은 본문을 캡처하세요.
  • 요청 검사 엔드포인트서비스들처럼 webhook.site 보내는 쪽이 전송한 것을 확인하는 데 도움이 됩니다.
  • 로컬 터널링. ngrok 및 같은 도구들은 로컬 수신자와 테스트할 수 있게 해주며, 제공자도 포함시킬 수 있습니다.
  • 수동 재생요청을 다시 빌드하세요. curl 또는 Postman을 사용하여 동일한 본문과 헤더를 사용하여. 그게 가장 빠른 방법으로 확인하는 방법입니다. code 또는 제공자 페이로드가 문제인지 확인합니다.
  • 제공자 전송 로그패턴이 중요합니다. 외부에서 내부로 작업하세요. 먼저 제공자가 예상한 것과 같은 것을 보냈는지 확인하세요. 그런 다음 서버가 동일한 바이트를 받았는지 확인하세요. 그런 다음 __CAPGO_KEEP_0__이 동일한 바이트와 동일한 비밀번호와 타임스탬프 규칙으로 해시했는지 확인하세요.

The pattern matters. Work from the outside in. First verify the provider sent what you expected. Then verify your server received the same bytes. Then verify your code hashed the same bytes with the same secret and timestamp rules.

좋은 웹후크 로그는 한 번의 검색으로 세 가지 질문에 답해야 합니다.

질문

유용한 로그 필드 요청이 도착했는가?
route, method, received_at 왜 거부되었는가?
missing_header, stale_timestamp, signature_failed __CAPGO_KEEP_0__
나중에 연관시킬 수 있나요? event_id, provider_request_id

실제 시스템에서 도움이 되는 네 번째 field를 추가하세요. request_id 받는 쪽에서 생성된 것으로, 요청을 따라가기 위해 앱, 큐, 워커 로그까지 추적할 수 있도록 해 주세요.

저장할 때 선택적이세요. 비밀을 로깅하지 마세요. 고객 데이터, 접근 토큰, 청구 세부 정보가 포함된 전체 프로덕션 페이로드를 덤프하지 마세요. 고객 데이터, 접근 토큰, 청구 세부 정보가 포함된 전체 프로덕션 페이로드를 덤프하지 마세요. 대신 메타데이터와 짧은 본문 해시를 로깅하세요. 그럼에도 불구하고 재시도와 두 번의 전달이 동일한지 확인할 수 있습니다.

원래 입력과 함께 실패한 요청을 재현하세요.

기본 튜토리얼은 이 부분을 생략합니다. 실패한 요청을 정확히 재현할 수 없다면 추측하는 것입니다.

실패한 웹후크를 저장하세요:

  • 원본 본문 바이트
  • 모든 서명 관련 헤더
  • 요청 시간
  • 콘텐츠 유형
  • 제공자 요청 ID

그 다음은 스테이징 엔드포인트에 다시 재생합니다. 재생이 통과하면, 전송 중에 변경된 것을 비교합니다. 일반적인 범죄자로는 요청 본체를 정규화하는 미들웨어, 문자 인코딩 불일치, 헤더를 제거하거나 다시 쓰는 로드 밸런서가 있습니다. 또한, 팀이 실제 요청 본체 대신 예쁘게 출력된 대시보드 뷰에서 페이로드를 복사하는 경우도 실패를 유발합니다. 하시마크 검증을 깨뜨리는 것만으로도 whitespace 차이만으로도 충분했습니다.

더 광범위한 릴리스와 모바일 전송 문제 해결을 위한, Capgo의 OTA 업데이트를 위한 디버깅 도구의 Capgo . 다른 전송 방법, 동일한 교훈. 실제 요청 경로를 캡처하기 전에 애플리케이션 Capacitor를 변경하지 마세요.서명 검증이 실패하면, 원시 바이트, 사용된 정확한 헤더, 시간 스탬프 값을 검사하기 전에 암호화 code를 조작하지 마세요.

If signature verification fails, inspect the raw bytes, the exact headers used in verification, and the timestamp value before touching the cryptography code.

스테이징 환경에서 웹후크 핸들러가 보통 잘 보이지만, 첫 번째 재시도 폭풍, 잘못된 페이로드, 또는 2시의 서명 불일치로 인해 갑자기 문제가 발생합니다. 생산 환경의 바치는 더 높습니다. 수신자는 위조된 요청을 거부하고, 유효한 재시도를 수락하고, 실패를 디버깅하기 위해 운영자에게 충분한 신호를 제공해야 합니다.

보안 및 정확성 검사

모든 요청 서명을 검증하세요

  • . 엔드포인트 URL은 유출됩니다. 테스트 URL은 채팅에서 공유됩니다. 서명 검증은 보낸 사람에게 공유된 비밀 키를 알았는지 알려주는 제어가 됩니다.기존 요청을 거부하세요
  • __CAPGO_KEEP_0__. 올드 페이로드에 대한 유효한 서명은 여전히 재생할 수 있습니다. 제공자의 재시도 모델과 일치하는 타임스탬프 허용도 설정해야 합니다.
  • JSON을 파싱한 텍스트가 아닌 raw body를 해시하세요.. 미들웨어는 키를 재배치하거나 공백을 정규화하거나 인코딩을 변경할 수 있습니다. 검증은 도착한 정확한 바이트에 대해 실행해야 합니다.
  • 서명 비밀을 code에서 유지하세요.. 환경 변수는 기본입니다. 자주 암호를 회전하거나 여러 환경에서 실행하는 경우에는 비밀 관리기를 사용하는 것이 더 적합합니다.
  • 인증 오류 시 실패. 서명 헤더가 누락되거나 오류가 발생하거나 예상치 못한 스키마를 사용하는 경우 요청을 거부하고 이유를 로그에 기록하세요.

신뢰도 검사

  • 빠르게 확인. 제공자는 일반적으로 2xx를 성공으로 처리하므로 요청을 검증하고 필요한 것을 저장한 후 느린 작업을 큐 또는 워커로 이동하세요.
  • 핸들러를 무결성 있게 유지하세요.. 동일한 이벤트가 여러 번 도착할 수 있습니다. 이벤트 ID, 전달 ID 또는 다른 안정적인 제공자 식별자에 따라 이벤트의 부수 효과를 분리하세요.
  • 예측 가능한 오류 코드를 반환하십시오.. 잘못된 입력에 대해 사용하십시오. 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.

웹훅 계약을 좁게 유지하십시오.

웹훅 계약을 좁게 유지하십시오. 제공자 __CAPGO_KEEP_0__ 변경 시 느슨한 파싱이 비용이 많이 들 수 있기 때문입니다.

표준을 사용하세요:

  • 수신, 인증, 처리를 별도의 결과로 추적하세요.
  • 요청 ID, 이벤트 ID, 서명 상태 및 타임스탬프 왜곡을 로그하세요.
  • 큐 지연, 핸들러 지연 및 재시도 양을 측정하세요.
  • 스테이징 또는 재배포 워크플로우를 위한 안전한 재생 경로를 유지하세요.
  • 패턴 변경에 대한 경고를 보내세요서명 실패 또는 중복 배달과 같은 특정 패턴의 급증과 같은 경우

Capgo은 더 광범위한 운영점을 보여주는 유용한 예입니다. 업데이트워크플로우에서 배포 관련 도구 및 관찰성을 포함하고 있으며, 이들의 생태계의 일부도 웹후크 관련 흐름을 다룹니다. 이 교훈은 실제입니다. 배달 시스템은 수신부터 완료까지의 시각성을 필요로 합니다.

팀이 위의 점검을 모두 수행하면 웹후크 수신자는 일반적으로 프로덕션에 잘 준비되어 있습니다. 만약 하나의 항목이 누락된다면, 그 빈틈은 데모 중에 나타나지 않고 사고 중에 나타납니다.

웹후크에 대한 자주 묻는 질문

code의 상태를 반환해야 하는가요?

__CAPGO_KEEP_0__을 반환하세요 2xx 웹훅을 수락한 후에 발생하는 2xx 상태 코드입니다. 유효성 검사 실패 시, 클라이언트 오류 또는 인증 오류를 반환하여 실패 원인을 명확히 하세요. 예를 들어, 잘못된 입력 또는 인증 데이터가 유효하지 않은 경우 400 잘못된 입력 또는 401 유효하지 않은 인증 데이터입니다. 유효성 검사 로직을 일관되게 유지하여 제공자 대시보드의 해석이 쉬워집니다.

웹훅을 동기적으로 처리해야 하나요?

보통은 아닙니다. 유효성 검사를 수행한 후, 실제 작업을 큐 또는 백그라운드 작업으로 밀어넣습니다. 이는 전달 경로를 빠르게 유지하고 느린 하위 스트림 처리로 인한 중복 재시도 횟수를 줄입니다.

재시도는 어떻게 처리해야 하나요?

재시도가 발생할 것으로 가정하세요. 핸들러에 중복성을 구현하여 동일한 이벤트를 받았을 때 부수 효과가 중복되지 않도록 하세요. 이벤트 ID 또는 제공자 전달 ID가 일반적으로 중복성을 구현하는 데 사용됩니다.

이벤트가 순서가 아닌 순서로 도착하는 경우 어떻게 처리해야 하나요?

가능한 경우 핸들러를 순서에 내재된 것으로 설계하세요. 사업 프로세스가 순서가 필요한 경우,陈舊한 전환을 감지하기 위해 충분한 상태를 저장하여 전달 순서가 이벤트 순서를 반영하는 것으로 가정하지 마세요.

웹훅 버전 변경은 어떻게 처리해야 하나요?

버전을 의도적으로 핸들러 로직에 추가하세요. 제공자별 파싱을 분리하고, 코드베이스에 payload 가설을 퍼뜨리지 마세요. 새로운 형식에 대한 지원을 출시하기 전에 실제 캡처된 샘플과 함께 테스트를 추가하세요.


만약 팀이 Capacitor 또는 Electron 앱을 배포한다면 Capgo 이것은 관련된 이유로 알아야 할 가치가 있습니다. 팀에게 signed 웹 업데이트를 전달하는 제어된 방법, rollout 동작을 관찰하고, 앱 스토어 리뷰를 기다리지 않고 인시던트를 복구하는 방법을 제공합니다. 이것은 solid webhook 디자인의 동일한 엔지니어링 인стинктив에 부합합니다: 입력을 검증하고, 릴리스 경로를 관찰하고, 복구를 빠르게 하세요.

Capacitor 앱에 대한 즉각적인 업데이트

웹-layer 버그가 활성화된 경우, Capgo를 통해 픽스를 배포하는 대신 앱 스토어 승인까지 며칠 기다리지 말고, 사용자는 배경에서 업데이트를 받으면서 네이티브 변경은 일반적인 리뷰 경로를 유지합니다.

마틴의 인간 지원

시작하기

최신 뉴스

Capgo은 전문적인 모바일 앱을 만들기 위해 필요한 최고의洞察력을 제공합니다.