웹훅 시그니처 검증: 안전한 연동 구축 방법

Ashley Innocent

Ashley Innocent

22 December 2025

웹훅 시그니처 검증: 안전한 연동 구축 방법

웹훅은 타사 서비스로부터 실시간 업데이트를 받는 가장 강력한 방법 중 하나입니다. Stripe, GitHub, Shopify 또는 Twilio에서 전송되는 단일 HTTP POST는 고객에게 요금을 청구하거나, 저장소를 업데이트하거나, 주문을 배송하거나, 확인 SMS를 보내는 등 애플리케이션의 중요한 비즈니스 로직을 트리거할 수 있습니다.

하지만 모든 웹훅 요청은 공용 인터넷을 통해 전달됩니다. 이는 웹훅 URL을 추측하거나 발견한 사람은 누구든지 완전히 합법적으로 보이는 악성 페이로드를 보낼 수 있다는 것을 의미합니다. 적절한 인증 없이는 애플리케이션이 실제 이벤트와 위조된 이벤트를 구분할 방법이 없습니다.

바로 이 지점에서 웹훅 서명 검증이 필요합니다. 이는 모든 수신 웹훅 요청이 예상하는 서비스에서 실제로 온 것이며 전송 중에 변경되지 않았음을 보장하는 간단하고 표준화된 메커니즘입니다.

이 포괄적인 가이드에서는 웹훅 서명 검증이 정확히 어떻게 작동하는지, 그리고 인기 있는 언어에서 이를 올바르게 구현하는 방법을 배울 것입니다. 또한 피해야 할 일반적인 실수와 모든 것을 빠르고 안정적으로 엔드투엔드 테스트하는 방법도 알게 될 것입니다.

💡
기술적인 세부 사항에 들어가기 전에, 몇 분 안에 웹훅을 검증하고 디버깅할 수 있는 올바른 도구를 가지고 있는지 확인하고 싶습니다. Apidog는 완전히 무료로 다운로드할 수 있으며, 내장된 웹훅 서명 검증 테스트, 목 서버, 실시간 페이로드 검사를 제공합니다. 신용카드 정보도 필요 없습니다. 이는 검증 로직이 실제로 작동하는지 확인하는 가장 빠른 방법입니다.
버튼

웹훅 서명 검증이란 무엇인가요?

웹훅 서명 검증은 수신되는 웹훅 요청이 예상하는 서비스에서 실제로 왔으며 변조되지 않았음을 확인하는 과정입니다.

대부분의 제공업체는 SHA-256 또는 SHA-512와 함께 HMAC(Hash-based Message Authentication Code)를 사용합니다. 서비스는 다음과 같이 계산합니다.

signature = HMAC-SHA256(secret_key, payload)

그런 다음 헤더(일반적으로 X-Signature, Signature 또는 X-Hub-Signature-256)에 서명을 보냅니다.

귀하의 서버는:

  1. 페이로드를 원시 바이트로 수신합니다 (중요!)
  2. 저장된 비밀을 사용하여 HMAC를 다시 계산합니다
  3. 계산된 서명을 수신된 서명과 비교합니다

정확히 일치하면 웹훅을 처리합니다. 그렇지 않으면 HTTP 401 또는 403을 반환합니다.

HMAC-SHA256이 업계 표준인 이유

제공업체들이 HMAC-SHA256을 선택하는 데는 다음과 같은 좋은 이유가 있습니다:

GitHub, Stripe, Shopify, Slack 및 수십 개의 다른 서비스들이 모두 HMAC-SHA256을 사용합니다.

Node.js에서 웹훅 서명 검증을 구현하는 방법

Node.js의 실제 예시부터 시작하겠습니다.

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  const computedSignature = hmac.update(payload).digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(computedSignature),
    Buffer.from(signature)
  );
}

주요 유의 사항:

Express 미들웨어 예시:

app.post('/webhooks/stripe', (req, res, next) => {
  const signature = req.headers['stripe-signature'];
  const secret = process.env.STRIPE_WEBHOOK_SECRET;

  // Get raw body (Express needs middleware to preserve raw body)
  const rawBody = req.rawBody || req.body; // use body-parser with verify option

  if (!verifyWebhookSignature(rawBody, signature, secret)) {
    return res.status(401).send('Invalid signature');
  }

  // Signature is valid → process the event
  next();
});

Python 구현 (FastAPI + Pydantic)

from fastapi import FastAPI, Request, HTTPException
import hmac
import hashlib

app = FastAPI()

def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
    computed = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(computed, signature)

@app.post("/webhooks/github")
async def github_webhook(request: Request):
    signature = request.headers.get("X-Hub-Signature-256")
    if not signature:
        raise HTTPException(status_code=401, detail="Missing signature")

    payload = await request.body()

    if not verify_signature(payload, signature.split('=')[1], SECRET):
        raise HTTPException(status_code=401, detail="Invalid signature")

    # Process webhook
    return {"status": "ok"}

개발자들이 흔히 저지르는 실수 (및 피하는 방법)

1. JSON.stringify() 또는 파싱된 본문 사용

많은 프레임워크는 JSON을 자동으로 파싱합니다. 이는 공백, 키 순서 및 서식이 다르기 때문에 검증을 깨뜨립니다.

해결책: 항상 파싱하기 전에 원시 본문을 캡처하세요.

Express에서: { verify: true }와 함께 body-parser를 사용하세요.

FastAPI에서: await request.body()를 사용하세요.

2. ===로 문자열 비교

타이밍 공격은 정보를 유출할 수 있습니다. crypto.timingSafeEqual 또는 hmac.compare_digest를 사용하세요.

3. 코드에 비밀 저장

환경 변수 또는 비밀 관리자(AWS Secrets Manager, HashiCorp Vault 등)를 사용하세요.

4. 재실행 공격 처리 누락

대부분의 제공업체는 타임스탬프를 포함합니다. 이벤트가 최근 것인지 확인하세요(예: 5분 이내).

const timestamp = req.headers['X-Signature-Timestamp'];
if (Date.now() - timestamp > 5 * 60 * 1000) {
  return res.status(401).send('Timestamp too old');
}

5. SHA-1 사용 (여전히 발생!)

GitHub는 2022년에 SHA-1을 더 이상 사용하지 않도록 했습니다. 항상 SHA-256을 사용하세요.

Apidog로 웹훅 서명 검증 테스트

웹훅을 수동으로 테스트하는 것은 고통스러운 일입니다. 요청을 보내고, 로그를 확인하고, 수정하고, 반복해야 합니다.

Apidog는 이것을 사소한 일로 만듭니다:

Apidog 프로젝트에서 왼쪽 사이드바의 + 아이콘을 클릭하고 "새 기타 프로토콜 API" > "웹훅"을 선택하세요.

CleanShot 2025-11-05 at 17.18.02@2x.png

웹훅을 생성한 후 편집기에서 다음 필드를 작성하세요:요청 메서드: 일반적으로POST.웹훅 이름: 이는 API 문서 및 OpenAPI 내보내기에 표시됩니다 (예: order).디버그 URL (선택 사항): 테스트 요청을 보내는 데 사용되는 실제 URL. 참고: 이것은 테스트 목적으로만 사용되며 문서에 포함되지 않습니다.기타 정보: 요청 본문과 같은 정보.

image.png

모든 필수 필드를 작성한 후 저장을 클릭하세요.
웹훅 URL을 디버그 URL 필드에 입력한 다음, 보내기를 클릭하여 웹훅 호출을 시뮬레이션하세요.

image.png

Apidog의 웹훅 시뮬레이터 덕분에 디버깅 시간을 몇 시간이나 절약했습니다. Stripe의 정확한 stripe-signature 형식과 GitHub의 sha256=... 접두사까지 지원합니다.

실제 예시: Stripe 웹훅 검증

Stripe는 특별한 헤더 형식을 사용합니다:

stripe-signature: t=1681234567,v1=abc123...,v0=def456...

다음과 같이 해야 합니다:

Stripe는 이러한 복잡성을 처리하기 위한 공식 라이브러리를 제공합니다:

const stripe = require('stripe')('sk_...');
stripe.webhooks.constructEvent(payload, sigHeader, endpointSecret);

하지만 직접 구현해야 할 때는 기본 HMAC를 이해하는 것이 중요합니다.

고급 주제: 여러 서명 허용

일부 제공업체(예: Stripe)는 하위 호환성을 위해 여러 서명을 보냅니다. 귀하의 코드는 다음을 수행해야 합니다:

2025년 보안 모범 사례

결론: 작은 검증 단계, 큰 보안 이득

웹훅 서명 검증은 사소한 세부 사항처럼 들릴 수 있습니다. 하지만 이는 안전한 애플리케이션과 공격자가 쉽게 악용할 수 있는 애플리케이션의 차이를 만듭니다.

이를 올바르게 구현하고, Apidog와 같은 도구로 철저히 테스트하며, 통합이 보호된다는 사실을 알고 더 편안하게 잠드세요.

오늘 Apidog를 무료로 다운로드하고 5분 안에 첫 웹훅을 검증하세요. 이는 귀하의 코드가 실제로 작동함을 증명하는 가장 빠른 방법입니다.

버튼

Apidog에서 API 설계-첫 번째 연습

API를 더 쉽게 구축하고 사용하는 방법을 발견하세요