메인 콘텐츠로 건너뛰기

웹 훅 예제: 안전한 구현 가이드

Node.js, Python, Go를 위한 code 웹 훅 예제를 찾으십시오. 서명 인증, 재생 공격 방지, 엔드포인트 디버깅에 대해 배우십시오.

마틴 도나디유

마틴 도나디유

마케팅 담당자

실용적인 웹 훅 예시: 보안 구현 안내서

서비스가 특정 이벤트가 발생했을 때 반응해야 하는 경우가 있습니다. 결제가 완료되거나 고객 정보가 변경되거나 저장소에 푸시가 발생했습니다. 매 분마다 API를 폴링하고

새로운 항목이 있는지 200를 반복적으로 묻는 것은 비효율적입니다. 대신, 이벤트가 발생했을 때 원본 시스템이 호출할 수 있도록 해보세요.

대부분의 웹 훅 예시 기사에서는 여기서 멈칫합니다. 라우트를 보여주고 JSON 본문을 출력하고 반환하고 끝내는 것입니다.

이 버전은 누군가가 위조된 요청을 보내거나 유효한 요청을 재생산하거나 핸들러가 파싱되기 전에 서명 확인이 수행되지 않아 실패하는 경우까지 작동합니다.

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

Your billing provider marks an invoice as paid at 02:13. If your app learns about it at 02:14, the customer gets access right away. If your app learns about it on the next polling cycle, they wait, support gets a ticket, and your logs fill with avoidable noise. Webhooks solve that timing problem by sending an HTTP callback when the event happens.

실질적으로, 웹후크는 이벤트에 따라 다른 시스템으로 POST 요청을 보내는 것입니다. 제공자는 변경 사항, 예를 들어 invoice.paid, order.created, 또는 push,을 감지하고 이벤트 데이터를 URL을 제어하는 URL로 전송합니다. 이로 인해 polling이 생성하는

이 패턴은 실제 시스템에서 나타납니다. Stripe은 결제 결과를 게시하고 GitHub은 저장소 활동을 게시합니다. Shopify는 주문 업데이트 게시합니다. 형식은 간단하지만 실제 운영 환경에서는 그렇지 않습니다. 웹후크가 돈, 액세스 또는 재고를 업데이트하는 경우에는 API 공개 엔드포인트와 동일한 주의를 기울여야 합니다. 특히 재시도, 중복, 그리고 신뢰할 수 없는 트래픽이 들어오면 더욱 그렇습니다.

웹후크 흐름을 프레임하는 데 도움이 되는 정신 모델

웹후크 흐름을 프레임하는 데 도움이 되는 유용한 방법은 네 가지 부분이 함께 작동하는 것으로 생각하는 것입니다.

  • 원본 시스템. 이벤트를 감지하는 서비스.
  • 목적지 엔드포인트. HTTP 요청을 받는 URL입니다.
  • 이벤트. 이름이 변경된 변경 사항이 발생했을 때 invoice.paid or push.
  • Payload. code가 필요로 하는 세부 정보가 포함된 요청 본문입니다.

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

실용적인 규칙: 웹 훅을 사용하여 이벤트 기반 업데이트. polling을 사용하여 예약된 읽기, 백필, 제공자가 출발하는 이벤트를 제공하지 않는 제공자에 대해.

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

What works and what fails in production

생산 환경에서 성공하고 실패하는 것

설정이 잘 유지되는 경우는 보통 설계가 단순하기 때문이다. 필요한 이벤트만 구독하고. 제공자나 이벤트 패밀리별로 엔드포인트를 범위로 한다. 이벤트 ID를 저장하여 중복 전달이 발생하지 않도록 한다. 요청이 검증되고 큐에 넣인 후 빠른 2xx 응답을 반환하고, 그 후 비즈니스 로직을 비동기적으로 수행한다.

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

이 가이드의 나머지 부분은 이 트레이드 오프로 정의된다. 웹후크 수신자 '헬로 월드' 버전은 작다. 생산 준비 버전은 서명 확인, 재생 방어, 중복 처리 및 디버깅 훅을 처음부터 제공한다.

Before writing code, it helps to look at the request as raw HTTP instead of as a framework object. A typical webhook is just an HTTP POST to a public endpoint with headers and a JSON body.

__CAPGO_KEEP_0__을 작성하기 전에, HTTP 요청을 프레임워크 객체로 보다는 raw HTTP로 보는 것이 도움이 된다. 일반적인 웹후크는 public 엔드포인트에 HTTP POST를 보내는 것에 불과하다. 헤더와 JSON 본문이 포함된다.

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"
  }
}

간단한 raw 요청

  • 중요한 부분은 다음과 같다:Method
  • 실제로 웹후크 전달은 일반적으로 POST 요청이다.Content-Type
  • User-Agent. Helpful for debugging, but never enough for trust.
  • 인증 헤더. 제공자의 인증 확인을 위한 검증입니다.
  • 타임스탬프 헤더. 오래된 요청이나 재생 요청을 거부하기 위해 사용됩니다.

바디 형태의 중요성

일반적으로 code는 모든 field에 관심이 없습니다. 이벤트 타입, 이벤트 식별자, 비즈니스 객체만 관심이 있습니다. data. 그래서 좋은 핸들러는 필요한 field만 파싱하고 나머지는 로깅을 통해 디버깅을 합니다.

OpenAPI는 이 패턴을 직접 모델링합니다. OpenAPI 3.1.0은 첫 번째급 웹훅 지원을 제공하며, top-level object에서 각 웹훅을 Path Item과 같은 방식으로 설명합니다. canonical 예제는 웹훅에 webhooks 을 사용합니다. newPet webhook with a post __CAPGO_KEEP_0__ 200 OpenAPI webhook 예시 만들어간 자신의 수신자 또는 제공자 계약을 문서화할 때, 강력한 예시가 추상적인 스키마 문장보다 더 도움이 됩니다. .

SheetMergy의 __CAPGO_KEEP_0__ 문서 예시를 사용하는 것처럼, 요청 예시, field 설명, 그리고 기대되는 응답이 어떻게 함께 작동하는지 명확하게 보여줍니다. SheetMergy’s API doc examples 웹후크 서명 인증을 안전하게 하기 위한 방법

서명된 웹후크는 한 가지 질문에 답합니다: 공유 비밀을 알고 있는 사람으로부터 이 페이로드가 왔는지 여부.

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

웹후크 서명 인증을 확인하기 위한 6단계 프로세스를 minh họa하는 그래픽.

인증 흐름

웹후크 서명 인증을 확인하기 위한 6단계 프로세스를 minh họa하는 그래픽.

인증 흐름

The usual HMAC flow looks like this:

  1. 제공자의 헤더에서 서명 읽기.
  2. 받은 요청 본문의 raw request body 원본 그대로 읽기.
  3. 보안 구성에서 웹훅 비밀 키 로드.
  4. 같은 알고리즘을 사용하여 예상 HMAC 다시 계산.
  5. 받은 서명과 계산된 서명에 대한 타이밍-안전한 비교.
  6. 일치하지 않으면 요청 거부.

그것은 raw-body 단계에서 많은 좋은 구현을 실패하는 곳입니다. JSON을 먼저 파싱하는 프레임워크, 공백을 재format, 또는 해싱하기 전에 인코딩 세부 정보를 변경하는 경우, 계산된 서명은 제공자의 서명과 일치하지 않습니다.

실제 code에서 무엇을 주의해야 할까요.

나는 가장 자주 보는 오류입니다:

  • JSON을 해싱하는 parsed. 하지 마 JSON.stringify(req.body) expect it to match.
  • normal string equality를 사용하는 대신. 시간에 안전한 비교를 사용하십시오.
  • 비밀번호를 하드 코딩하는. 환경 변수 또는 비밀 관리 도구에서 유지하십시오.
  • 헤더만 믿는. 서명 헤더는만 검증을 통해 의미가 있습니다.

서비스 간 비밀 관리를 강화하는 팀에게는 Capgo의 API 앱 스토어 준수에 대한 키 보안 가이드 관련성이 있습니다. 웹 훅 수신자에게도 동일한 discipline이 적용됩니다. 비밀번호 회전, 범위 접근, 로그에서 누출을 피하는 모든 것이 중요합니다.

A generic verification example

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

This is intentionally generic. Real providers often prefix signatures, combine timestamps into the signed content, or encode the digest differently. The rule stays the same. Follow the provider’s exact signing format, and always verify against the raw payload.

Replay 공격을 방지하는 방법

A signed webhook can still be dangerous if it arrives hours later and your handler treats it as new. That happens more often than teams expect. Proxies log traffic, request payloads leak into the wrong place, or a provider retries after a network failure and your endpoint processes the same event twice.

A replay 공격을 방지하는 데 필요한 5 가지 보안 조치의 체크리스트입니다.

서명 확인은 보낸 사람의 비밀 키로 이 페이로드를 생성했는지 여부를 확인하는 질문에 답합니다. 재생 보호는 다른 질문에 답합니다: 이 요청을 지금 여전히 받아들여야 하나요? 실제 운영 환경의 수신자는 두 가지 모두 필요합니다.

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

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

이 흐름은 다음과 같이 보이길 바랍니다.

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

그 마지막 점은 중요합니다. 타임스탬프가 서명에 포함되지 않으면 공격자는 원본 본문을 재사용할 수 있습니다. 항상 제공자의 정확한 서명 형식을 확인하기 전에 타임스탬프 논리를 신뢰하지 않습니다.

tolerance window를 선택하는 방법

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

이것은 상호 작용의 균형입니다. 30초 창은 더 안전해 보이지만, 실제 시스템에서 재시도, 큐잉 또는 지역 지연이 포함된 경우 더 자주 발생합니다. 30분 창은 운영하기 더 쉬우나, 서명된 요청이 노출된 경우 공격자에게 더 많은 시간을 제공합니다. 몇 분으로 시작하고 NTP와 서버를 동기화한 다음, 제공자의 전달 패턴이 지원하는 경우에만 조정하세요.

재생 방어는 단순히 타임스탬프 확인이 아닙니다.

타임스탬프 검증은 오래된 요청을 차단하지만, 유효한 창 내에서 동일한 서명된 이벤트가 두 번 전달되는 경우에도 중복 처리를 막지 않습니다.

두 번째 층을 사용하세요:

  • 이벤트 ID 또는 전달 ID를 추적하세요. Redis와 같은 임시 저장소에서 추적하세요.
  • 처리 핸들러를 중복 처리를 방지하기 위해 idempotent로 처리하세요. 중복 주문, 이메일, 청구 작업이 생성되지 않도록 반복적인 전달이 중복 처리되지 않도록 하세요.
  • 거부된陈舊요청을 로그하세요. 이유 코드와 함께 로그하세요, 그러나 비밀번호나 전체敏感데이터를 로그하지 마세요.
  • 유효성 검사와 큐 작업이 끝난 후 빠른 응답을 반환하세요. 유효 기간과 취소가 이미 고려되는 팀은 패턴을 인식할 것입니다. __CAPGO_KEEP_0__의

Capgo 앱에서 토큰 취소 패턴에 대한 Capgo의 안내서 token revocation patterns in Capacitor apps 서명된陈舊은 여전히 안전하지 않습니다.

__CAPGO_KEEP_0__

Node.js에서 Webhook 수신기를 구축하는 방법

Node.js와 Express를 사용하면 serious한 수신기를 온라인에 올릴 수 있는 가장 빠른 방법입니다. 그러나 Express가 객체로 변환하기 전에 raw body에 접근할 수 있어야 하는 한 가지 함정만이 더 중요합니다.

나무 책상 위에 있는 노트북이 VS Code 에디터 환경에서 Node.js 수신기 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에서 비밀 관리하는 방법에 대한 guide를 참조하세요. 작업 흐름을 보는 데 도움이 되는 짧은_walkthrough가 필요하다면:

실제 시스템에서 변경할 사항

실제 제공자 통합을 위해, I’d 이벤트 ID 중복 제거를 영구 저장소에 추가하고, 요청 ID와 함께 구조화된 로그를 만들고, 확인 경로 뒤에 큐를 추가합니다. 또한 단일 일반 엔드포인트를 사용하지 않도록 하며, 여러 제공자가 다른 서명 형식 사용하는 경우 분리된 핸들러를 사용하세요. 이는 더 쉬운 추론과 더 어려운 깨짐을 의미합니다.

Python에서 Webhook 수신자 만들기

Flask는 명확한 웹후크 예제를 위해 좋은 선택입니다. 요청 처리는 명확하고, Python 표준 라이브러리는 HMAC에 필요한 것을 이미 제공합니다.

주요 점은 Node와 같습니다. raw 요청 바이트와 비교하여 검증하세요, parsed JSON dict가 아닌.

What I’d change for a live system

A 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.jsonImplementation에 대한 몇 가지 주의 사항:

plain equality 대신

  • 를 사용하세요. hmac.compare_digest 헤더가 누락된 경우 클라이언트 오류로 처리하고
  • 를 사용하세요. JSON 파싱을 위해
  • 를 사용하세요. Flask가 오류를 발생시키지 않고 오류 처리를 제어하려면. silent=True __CAPGO_KEEP_0__ __CAPGO_KEEP_0__
  • 경로를 얇게 유지하세요. 비용이 많이 드는 작업이 트리거되는 경우 페이로드를 큐에 넣으세요.

서명 불일치로 인한 디버깅을 보안 검사를 완화하지 말고, 정확히 어떤 바이트를 해시했는지와 제공자가 기대하는 서명 형식을 출력하여 디버깅하세요.

팀이 일반적으로 막히는 곳

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

그럴 때는 무작위로 암호 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가 여기서 견고한 이유는 무엇입니까

몇 가지 이점이 눈에 띄는 것은:

  • 핸들러는 명시적입니다. 숨겨진 미들웨어 마법이 없습니다.
  • 엣지에서 타이핑이 도움이 됩니다. 헤더 파싱, 타임스탬프 변환 및 JSON 디코딩 모두 명확하게 실패합니다.
  • 표준 암호화 패키지가 충분합니다. 기본 HMAC 검증에 필요한 별도의 종속성이 없습니다.

운영 노트

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

가장 강력한 Go 웹훅 핸들러 중에서 가장 단순한 핸들러를 보았습니다. 그들은 수송 검증과 비즈니스 논리를 혼합하지 않으며, 응답이 돌아오기 전에 데이터베이스 작업을 수행하지 않습니다.

필수 디버깅 기법

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

웹 훅 디버깅을 위한 소프트웨어 개발 환경에서 필수적인 다섯 가지 도구 및 기술 목록입니다.

실제 디버깅 도구

전선 형식부터 시작하세요.

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

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

  • 원본 요청 캡처. 로깅할 헤더, 콘텐츠 유형, 콘텐츠 길이, 그리고 수정되지 않은 본문을 조사 중입니다.
  • 요청 검사 엔드포인트. 서비스들처럼 webhook.site 원본 송신자가 전송한 것을 확인하는 데 도움이 됩니다.
  • 로컬 터널링. ngrok 및 같은 도구들은 로컬 수신자와 테스트할 수 있으면, 제공자도 포함시킬 수 있습니다.
  • 수동 재생. __CAPGO_KEEP_0__ 또는 제공자 페이로드가 문제인지 확인하기 위해 가장 빠른 방법은 __CAPGO_KEEP_0__과 동일한 본문과 헤더를 사용하여 Postman 또는 같은 클라이언트로 요청을 다시 빌드하는 것입니다. curl or Postman using the same body and headers. That is the quickest way to confirm whether your code or the provider payload is the issue.
  • . 제공자 대시보드에는 요청 코드, 다시 시도 기록 및 요청 식별자가 포함되어 있으며 로그와 일치시킬 수 있습니다.패턴은 중요합니다. 외부에서 내부로 작업하세요. 먼저 제공자가 예상한 것과 같은 것을 보냈는지 확인하세요. 그런 다음 서버가 같은 바이트를 받았는지 확인하세요. 마지막으로 __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.

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

질문

유용한 로그 필드요청이 도착했는가?
경로, 메서드, received_at__CAPGO_KEEP_0__
Why was it rejected?missing_header, stale_timestamp, signature_failed
나중에 연관시킬 수 있나요?event_id, provider_request_id

실제 시스템에서 네 번째 필드는 도움이 됩니다. 로컬에 추가하여 request_id 받는 쪽에서 생성하여 요청을 따라가기 위해 앱, 큐, 워커 로그를 따라갈 수 있도록 하세요.

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

원래 입력으로 실패를 재현하세요.

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

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

  • 원본 본문 바이트
  • 모든 서명 관련 헤더
  • 요청 시간
  • 내용 형식
  • 제공자 요청 ID

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

더 광범위한 릴리스 및 모바일 전송 문제 해결을 위한, Capgo’s 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시의 서명 불일치로 인해 갑자기 깨집니다. 운영 환경의 바치는 높습니다. 수신자는 위조된 요청을 거부하고 정당한 재시도를 수락해야 하며, 실패를 디버그하는 데 필요한 신호를 제공해야 하며 sensitive 데이터를 노출하지 않도록 해야 합니다.

보안 및 정확성 검사

모든 요청 서명을 검증하십시오

  • __CAPGO_KEEP_0__. URL이 노출됩니다. 테스트 URL이 채팅에서 공유됩니다. 서명 검증은 공유 비밀을 알고 있는 송신자가 알려주는 제어입니다.
  • Reject old requests. 유효한 서명이 오래된 페이로드에도 적용될 수 있습니다. 제공자의 재시도 모델과 일치하는 타임스탬프 허용 범위를 설정하여 오래된 요청을 거부합니다.
  • Hash the raw body, not the parsed JSON. 미들웨어는 키를 재배치하거나 공백을 정규화하거나 인코딩을 변경할 수 있습니다. 검증은 도착한 정확한 바이트에 대해 실행해야 합니다.
  • Keep signing secrets out of code. 환경 변수는 기본입니다. 자격 증명을 정기적으로 회전하거나 여러 환경에서 실행하는 경우 비밀 관리기를 사용하는 것이 더 적합합니다.
  • Fail closed on auth errors. 서명 헤더가 누락되거나 오류가 발생하거나 예상치 못한 스키마를 사용하는 경우 요청을 거부하고 이유를 로깅합니다.

Reliability checks

  • Acknowledge fast. 제공자는 일반적으로 2xx를 성공으로 처리하므로 요청을 검증하고 필요한 것을 저장한 후 느린 작업을 큐 또는 워커로 이동하세요.
  • 이벤트 핸들러는 idempotent 해야 합니다.. 동일한 이벤트가 여러 번 도착할 수 있습니다. 이벤트 ID, 전달 ID, 또는 다른 안정적인 제공자 식별자에 의한 키 사이드 이펙트를 제거합니다.
  • 예측 가능한 오류 코드를 반환하십시오.. 잘못된 입력에 대해 400 를 사용하고, 401 로 실패한 검증에 대해 403 를 사용하고, 시스템이 문제인 경우에만 사용하십시오. 제공자가 다시 시도하는 동작을 쉽게 이해할 수 있도록 합니다. 5xx 파싱하기 전에 제한을 설정하십시오.
  • . 웹후크 엔드포인트가 일반적인 인가 구멍으로 변하지 않도록 요청 크기, 콘텐츠 유형, 헤더 카운트를 조기에 설정하십시오.계약을 좁게 유지하십시오.
  • . 제공자가 지원하는 필드와 이벤트 유형만 수락하십시오. 느슨한 파싱은 처음에는 편리하게 느껴지지만 제공자가 변경될 때 비용이 많이 들 수 있습니다.API

관찰성 검사

웹훅 작동이 좋은 것은 보잘것없는 일이다. 팀은 빠르게 세 가지 질문에 대답할 수 있다: 우리는 그것을 받았나요? 우리는 그것을 확인했나요? 다운스트림 처리가 성공했나요?

그 표준을 사용하세요:

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

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

팀이 위의 검사 항목을 모두 처리하면 웹훅 수신자는 일반적으로 프로덕션에 잘 준비되어 있다. 만약 하나의 항목이 누락된다면, 그 결함은 데모 중에 나타나지 않고 사고 중에 나타난다.

웹훅에 대한 자주 묻는 질문

어떤 상태 코드 code를 반환해야 하나요?

웹훅을 2xx 웹훅을 수락했을 때 반환하세요. 유효성 검사 실패 시 클라이언트 오류나 인증 오류를 반환하세요. 예를 들어, 400 입력 형식이 잘못되었을 때 401 인증 데이터가 유효하지 않아 오류가 발생했을 때. 그 로직을 일관되게 유지하여 제공자 대시보드의 해석이 더 쉬워집니다.

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

보통 그렇지 않습니다. 유효성을 검사하고 확인을 받은 후 실제 작업을 큐나 백그라운드 워커로 밀어넣세요. 그러면 전달 경로가 빠르고 느린 하위 스트림 처리로 인한 중복된 재시도 횟수가 줄어듭니다.

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

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

이벤트가 순서가 아닌 순서로 도착하는 경우

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

__CAPGO_KEEP_0__

웹후크 버전 변경에 대처하는 방법은?


If your team ships Capacitor or Electron apps, Capgo 또는 Electron 앱을 배포하는 팀이라면 __CAPGO_KEEP_0__

Capacitor 앱을 위한 실시간 업데이트

앱 스토어 승인까지 며칠 기다리지 않고, 웹-layer 버그가 생겼을 때 Capgo를 통해 패치를 배포하세요. 사용자는 배경에서 업데이트를 받으며, 네이티브 변경은 일반적인 검토 경로를 유지합니다.

시작하기

블로그에서 최신 소식

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