---
term: '웹훅 (이벤트 기반 웹훅)'
seoTitle: '웹훅이란? HMAC 보안, 재시도, 멱등성 설명'
headline: '웹훅이란?'
slug: webhooks
category: backend-compute
shortDefinition: '웹훅은 이벤트가 발생하면 한 시스템이 다른 시스템이 등록해 둔 URL로 페이로드를 POST하는 자동화된 HTTP 콜백입니다.'
relatedTerms:
  - event-driven-architecture
  - websockets-real-time-sync
  - cloud-code-serverless-functions
  - pub-sub-pattern
contrastsWith:
  - websockets-real-time-sync
aboutTerms:
  - 'Webhook Endpoint'
  - 'HMAC Signature Verification'
  - 'At-Least-Once Delivery'
faq:
  - question: '웹훅을 쉽게 설명하면 무엇입니까?'
    answer: '무언가 일어나는 순간 한 시스템이 다른 시스템에 보내는 자동화된 HTTP 메시지입니다. 문을 반복해서 확인하는 대신 초인종을 다는 것과 같습니다. 제공업체에 URL을 알려 주면, 이벤트가 발생할 때 제공업체가 이벤트 데이터를 그 URL로 POST합니다. 이 용어는 2007년의 "사용자 정의 HTTP 콜백"에서 유래했습니다.'
  - question: '웹훅과 API의 차이는 무엇입니까?'
    answer: '방향과 주도권입니다. API는 요청 기반으로, 클라이언트가 묻고 서버가 답합니다. 웹훅은 이벤트 기반으로, 무언가 일어나면 서버가 묻지 않아도 밀어냅니다. 웹훅은 사실 API 위에 구축된 패턴이며, 실제 연동 대부분은 둘을 함께 씁니다. 변경 사항을 듣는 데는 웹훅을, 그에 대응하는 데는 API 호출을 사용합니다.'
  - question: '웹훅과 폴링의 차이는 무엇입니까?'
    answer: '폴링은 타이머에 맞춰 묻고 대부분 "아직 없음"이라는 답을 듣습니다. 대형 자동화 플랫폼에서 측정한 유명한 결과에 따르면 폴링의 약 98%는 새 데이터 없이 돌아옵니다. 웹훅은 이를 뒤집습니다. 평상시에는 요청이 0건이고, 변경이 생기면 즉시 전달됩니다. 폴링은 웹훅의 경쟁자가 아니라 그 아래의 대조용 안전망으로 살아남습니다.'
  - question: '웹훅은 어떻게 수신합니까?'
    answer: 'POST를 받는 HTTPS 엔드포인트를 노출하고, 그 URL을 제공업체에 등록한 뒤, 원하는 이벤트를 선택합니다. 핸들러에서는 서명을 검증하고, 수 초 안에 2xx를 반환하며, 실제 처리는 비동기로 수행합니다. 프로덕션 로직을 연결하기 전에 캡처 도구로 테스트하는 것이 좋습니다.'
  - question: '웹훅은 안전합니까?'
    answer: '기본적으로는 아닙니다. 엔드포인트는 누구나 POST할 수 있는 공개 URL입니다. 표준 방어는 HMAC 서명입니다. 제공업체가 공유 시크릿으로 각 페이로드에 서명하면, 수신자는 원본 본문에 대해 다시 계산하여 상수 시간으로 비교하고, 오래된 타임스탬프는 거부하여 리플레이를 차단합니다. HTTPS는 항상, IP 허용 목록은 보조 수단입니다.'
  - question: '엔드포인트가 다운되면 어떻게 됩니까?'
    answer: '좋은 제공업체는 지수 백오프로 재시도하며, 수 시간에서 수 일까지 이어지기도 합니다. 그래서 전달은 최소 한 번(at-least-once)이며 중복은 정상입니다. 재시도 기간이 지나면 이벤트가 유실될 수 있으므로, 중요한 연동은 웹훅만 믿지 않고 주기적인 API 폴링으로 대조합니다.'
  - question: '중복된 웹훅 전달은 어떻게 처리합니까?'
    answer: '이벤트의 고유 ID로 중복을 제거합니다. 처리한 ID를 기록하고 반복되는 것은 건너뛰되, 그 기록을 제공업체의 재시도 기간 이상 보관합니다. 최소 한 번 전달에 멱등한 핸들러를 더하면 사실상 정확히 한 번 처리가 됩니다. 이것이 신뢰성 계약에서 수신 측이 맡는 절반입니다.'
  - question: '웹훅과 웹소켓의 차이는 무엇입니까?'
    answer: '웹훅은 무상태의 단방향 서버 간 HTTP 알림이고, 웹소켓은 채팅이나 라이브 대시보드 같은 클라이언트 대상 실시간 기능을 위해 만들어진 지속적인 양방향 연결입니다. 서버가 서버에게 알리면 웹훅, 서버가 사용자 인터페이스로 스트리밍하면 웹소켓입니다.'
codeLanguages: [javascript, dart, swift, kotlin]
externalAuthorities:
  - name: 'webhooks.fyi — webhook best practices'
    url: 'https://webhooks.fyi/'
  - name: 'W3C WebSub Recommendation'
    url: 'https://www.w3.org/TR/websub/'
  - name: 'REST Hooks — resthooks.org'
    url: 'https://github.com/zapier/resthooks'
  - name: 'OWASP SSRF Prevention Cheat Sheet'
    url: 'https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html'
cta:
  title: '양방향 웹훅'
  text: 'Back4app에서는 afterSave 트리거가 나가는 웹훅이고 Cloud Function이 준비된 수신자입니다. 서명을 검증하고, 데이터베이스에 쓰고, Live Query가 결과를 모든 화면에 푸시하게 하면 됩니다.'
  linkText: '무료로 시작하기'
  linkUrl: 'https://www.back4app.com/signup'
author: 'Back4app Engineering'
publishedDate: '2026-10-28'
translationKey: webhooks
---

**웹훅은 이벤트가 발생하면 한 시스템이 다른 시스템이 등록해 둔 URL로 페이로드를 POST하는 자동화된 HTTP 콜백입니다.** 2007년에 "사용자 정의 HTTP 콜백"으로 만들어진 이 용어는 중요한 역전을 가리킵니다. 자사 시스템이 무언가 바뀌었는지 반복해서 묻는 대신, 상대 시스템이 바뀌는 순간 자사 시스템에 알려 줍니다. 웹의 가장 평범한 부품인 HTTPS URL, POST, JSON 본문, 2xx 확인 응답으로 만든 푸시입니다.

## 핵심 요약

| 질문 | 답변 |
| --- | --- |
| 메커니즘 | URL 등록 → 이벤트 발생 → 제공업체가 페이로드 POST → 2xx 반환 |
| vs. API | API는 물으면 답하고, 웹훅은 무언가 일어나면 말함 |
| 보안 기준 | 원본 본문에 대한 HMAC, 상수 시간 비교, 타임스탬프 창 |
| 전달의 실제 | 최소 한 번, 순서 없음, 재시도됨 – 중복 제거와 대조 필요 |
| 수신자의 원칙 | 검증 · 빠른 확인 응답 · 비동기 처리 · 이벤트 ID로 중복 제거 |

## 전달, 처음부터 끝까지

```text
설정      수신자가 https://api.example.com/hooks/payments를 노출하고
          제공업체에 등록하며, 이벤트와 비밀(secret)을 선택

이벤트    제공업체에서 결제 성공

전달      POST /hooks/payments
          webhook-id: evt_8fk2            ← 중복 제거 키
          webhook-timestamp: 1767024900   ← 리플레이 방지(서명에 포함)
          webhook-signature: v1,d2Vio…    ← HMAC-SHA256(secret, id.timestamp.body)
          { "type": "charge.succeeded", "orderId": "o-1187" }

ACK       수신자가 서명 검증 → 수 초 안에 200 → 작업은 비동기로 진행
재시도    2xx가 없으면? 수 시간/수 일 동안 지수 백오프 → 중복은 정상
```

패턴의 양방향을 코드로 보여 줍니다. 송신자인 데이터 트리거, 수신자인 Cloud Function, 그리고 결과를 지켜보기만 하는 클라이언트입니다:

**JavaScript:**

```javascript
// JavaScript — Cloud Code (cloud/main.js): both directions of a webhook
// OUTGOING: any data change can notify an external system
Parse.Cloud.afterSave('Order', async (req) => {
  if (req.object.get('status') !== 'paid') return;
  await Parse.Cloud.httpRequest({
    method: 'POST',
    url: 'https://hooks.example.com/orders', // the receiver's registered URL
    headers: { 'Content-Type': 'application/json' },
    body: { event: 'order.paid', id: req.object.id },
  });
});

// INCOMING: a Cloud Function is a ready-made webhook receiver
Parse.Cloud.define('paymentWebhook', async (req) => {
  verifySignature(req.params, process.env.WEBHOOK_SECRET); // HMAC first
  await markOrderPaid(req.params.orderId); // write fast, work async
  return { received: true }; // 2xx before heavy processing
});
```

**Flutter:**

```dart
// Flutter / Dart — Back4app Flutter SDK
// The client's side of a webhook: watch its effect in real time
// (payment platform → Cloud Function receiver → database → Live Query → UI)
final orderQuery = QueryBuilder<ParseObject>(ParseObject('Order'))
  ..whereEqualTo('objectId', orderId);
final sub = await LiveQuery().client.subscribe(orderQuery);
sub.on(LiveQueryEvent.update, (order) {
  if (order.get<String>('status') == 'paid') showReceipt();
});
// The webhook itself was handled server-side — clients just watch the data.
```

**Swift:**

```swift
// iOS / Swift — Back4app Swift SDK
// The client's side of a webhook: watch its effect in real time
// (payment platform → Cloud Function receiver → database → Live Query → UI)
let orderQuery = Order.query("objectId" == orderId)
let sub = try await orderQuery.subscribe()
sub.handleEvent { _, event in
    if case .updated(let order) = event, order.status == "paid" {
        showReceipt()
    }
}
// The webhook itself was handled server-side — clients just watch the data.
```

**Kotlin:**

```kotlin
// Android / Kotlin — Back4app Android SDK
// The client's side of a webhook: watch its effect in real time
// (payment platform → Cloud Function receiver → database → Live Query → UI)
val orderQuery = ParseQuery.getQuery<ParseObject>("Order")
orderQuery.whereEqualTo("objectId", orderId)
val sub = ParseLiveQueryClient.Factory.getClient().subscribe(orderQuery)
sub.handleEvent(SubscriptionHandling.Event.UPDATE) { _, order ->
    if (order.getString("status") == "paid") showReceipt()
}
// The webhook itself was handled server-side — clients just watch the data.
```

## 웹훅 vs. API vs. 폴링

| | 웹훅 | API 호출 | 폴링 |
| --- | --- | --- | --- |
| 주도권 | 제공업체가 푸시 | 소비자가 요청 | 소비자가 타이머에 맞춰 요청 |
| 시점 | 이벤트 발생 시 | 필요할 때 | 다음 주기에 |
| 낭비되는 트래픽 | 평상시 없음 | 없음 | 폴링의 약 98%가 빈손 |
| 방향 | 단방향 알림 | 양방향 요청/응답 | 양방향, 반복 |
| 가장 잘하는 일 | "일어나면 알려 달라" | "이것을 해 달라 / 저것을 달라" | 대조, 웹훅이 없는 제공업체 |

이 논쟁은 대체로 무의미합니다. 성숙한 연동은 셋을 모두 씁니다. 변경을 빠르게 듣는 데는 웹훅, 신뢰할 수 있는 최신 상태를 가져와 행동하는 데는 [API](/glossary/ko/api/) 호출, 그리고 공중그네 아래의 안전망으로 느린 대조 폴링입니다.

## 수신자의 체크리스트

모든 제공업체 문서에 흩어져 있지만 어떤 설명 글도 모아 놓지 않은 목록입니다:

1. **원본 본문에 대해 HMAC을 검증합니다** – 파싱 전에 해야 합니다. 다시 직렬화한 JSON은 서명을 깨뜨립니다.
2. **상수 시간으로 비교합니다** – 문자열 동등 비교는 타이밍을 누설합니다. 암호화 라이브러리의 비교 함수를 사용해야 합니다.
3. **타임스탬프 창을 강제합니다** – 약 5분보다 오래된 전달은 거부합니다. 타임스탬프가 서명된 내용 *안에* 있으므로, 공격자는 유효하게 서명된 오래된 요청을 새 시각으로 리플레이할 수 없습니다.
4. **2xx를 빠르게 반환합니다** – 무거운 작업 전에, 수 초 안에 반환합니다. 느린 핸들러는 타임아웃되고 재시도되어 중복 폭풍이 됩니다.
5. **비동기로 처리합니다** – 큐에 넣고, 확인 응답한 뒤, 작업합니다.
6. **이벤트 ID로 중복을 제거합니다** – 처리한 ID 기록을 제공업체의 재시도 기간 이상 보관합니다.
7. **중요한 동작에서는 페이로드를 신뢰하지 않습니다** – 웹훅을 초인종으로 취급합니다. 상품을 발송하거나 접근을 허가하기 전에 제공업체의 API에서 현재 상태를 가져옵니다.
8. **전달을 로깅하고 실패에 경보를 겁니다** – 침묵은 망가진 엔드포인트와 구별되지 않습니다.

정의 페이지들이 건너뛰는 로컬 개발 메모 하나입니다. `localhost`는 인터넷에서 접근할 수 없으므로, 개발은 자신의 머신에 공개 URL을 빌려주는 터널링 도구와 실제 페이로드를 리플레이하는 캡처 도구를 거쳐 진행됩니다.

## 전달 의미론, 솔직하게

웹훅 전달은 **최소 한 번**(at-least-once)입니다. 제공업체는 확인 응답을 받을 때까지 재시도하므로, 중복은 신뢰성의 버그가 아니라 신뢰성의 *기능*입니다. 신뢰할 수 없는 네트워크 위에서 정확히 한 번 전달은 형식적으로 불가능하며, 실용적인 등가물은 최소 한 번 전달에 멱등한 핸들러를 더한 것입니다. **순서는 보장되지 않습니다.** 재시도와 병렬 전송이 뒤섞이므로 `updated`가 `created`보다 먼저 도착할 수 있습니다. 이벤트를 ID와 버전으로 적용하거나 상태를 다시 가져와야 합니다. 그리고 **재시도 기간은 끝납니다.** 주말 동안 다운된 엔드포인트는 이벤트를 영구히 놓칠 수 있으며, 그래서 돈이 걸린 중요한 연동은 웹훅에 주기적 대조를 짝지어 둡니다. 브로커가 형식화하는 [같은 최소 한 번 규율](/glossary/ko/pub-sub-pattern/)이 평범한 HTTP로 도착하는 것입니다. 이 비교는 일반화됩니다. 웹훅은 알려진 URL로의 지점 간 푸시이고, [pub/sub](/glossary/ko/pub-sub-pattern/)는 브로커, 토픽, 팬아웃을 더하며, [웹소켓과 SSE](/glossary/ko/sse-vs-websockets-vs-polling/)는 서버가 아닌 *클라이언트*를 상대합니다. 웹훅은 인프라를 공유하지 않는 두 시스템이 서로의 이벤트를 들어야 할 때를 위한 답입니다.

```mermaid
flowchart LR
  accTitle: 재시도와 비동기 처리를 포함한 웹훅 전달
  accDescr: 제공업체에서 발생한 이벤트는 큐에 들어가 HMAC 서명과 함께 수신자가 등록한 URL로 POST됩니다. 수신자는 서명을 검증하고 2xx로 빠르게 확인 응답한 뒤 중복 제거와 함께 비동기로 처리합니다. 실패한 전달은 지수 백오프로 제공업체의 재시도 큐에 다시 들어가고, 반복 실패는 데드 레터 로그로 갑니다.
  E["이벤트 발생"] --> Q["제공업체 큐<br/>+ HMAC 서명"]
  Q -->|"페이로드 POST"| R["수신자 엔드포인트"]
  R -->|"검증 → 빠른 2xx"| A["비동기 워커<br/>이벤트 ID로 중복 제거"]
  R -.->|"2xx 없음"| RT["백오프로 재시도<br/>수 시간 → 수 일"] --> Q
  RT -.->|"소진"| DL["데드 레터 로그<br/>+ 경보"]
  A --> DB[("자사 데이터베이스")]
```

## 송신자 측 구축하기

웹훅을 신뢰성 있게 내보내는 것은 그 자체로 작은 시스템이며, 어떤 순위 페이지도 그 윤곽을 그려 주지 않습니다. 죽은 엔드포인트 하나가 나머지를 막지 않도록 **목적지별 큐**, **지수 백오프와 지터를 더한 재시도**, 시도 횟수가 소진된 뒤 재전달 도구를 갖춘 **데드 레터 저장소**, 엔드포인트별 시크릿과 시크릿 회전을 갖춘 **HMAC 서명**, 수신자가 원하는 것만 선택하는 **이벤트 유형 구독**, 그리고 고객이 읽을 수 있는 **전달 로그**입니다. "보내긴 했습니까?"가 첫 번째 지원 질문이기 때문입니다. 송신자에게만 해당하는 보안 항목이 하나 있습니다. 수신자는 임의의 URL을 등록하므로 내부 주소 범위와 대조해 검증해야 합니다. 공격자가 `http://10.0.0.5/admin`을 자신의 "웹훅 엔드포인트"로 등록하는 것은 연동 기능의 탈을 쓴 [서버 측 요청 위조](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html)입니다.

## 일반적인 사용 사례

- **결제 수명 주기** – 결제, 환불, 구독 변경이 정산되는 대로 백엔드에 통보됩니다.
- **CI/CD 트리거** – git push가 빌드를 시작하는 전형적인 연결.
- **앱 간 자동화** – 폼 도구, 채팅 플랫폼, CRM이 서로의 이벤트로 연쇄됩니다.
- **운영 알림** – 모니터링 경보와 배송 업데이트가 팀 채널에 도착합니다.
- **데이터 동기화** – 파트너 시스템의 API 전체를 폴링하지 않고도 로컬 미러를 최신으로 유지합니다.

## 웹훅을 사용해야 할까? 결정 매트릭스

| 상황 | 선택 |
| --- | --- |
| 다른 회사의 시스템이 자사 시스템에 알려야 함 | 웹훅 – 상호 운용의 기본값 |
| 자사 서비스, 자사 인프라 | [Pub/sub](/glossary/ko/pub-sub-pattern/) – 브로커, 버퍼, 팬아웃 |
| 브라우저/앱에 실시간 업데이트가 필요함 | [웹소켓 / 라이브 쿼리](/glossary/ko/websockets-real-time-sync/) |
| 제공업체가 웹훅을 제공하지 않음 | 폴링, 예의 바르게 |
| 이벤트에 돈이나 접근 권한이 걸려 있음 | 웹훅 + 검증 후 재조회 + 대조 |
| 자사가 이벤트를 내보내는 플랫폼임 | 위의 송신자 측을 구축하거나, 신뢰성을 약속하지 않아야 함 |

## 한계와 트레이드오프

- **재시도 기간 너머의 전달은 최선 노력입니다.** 웹훅은 알릴 뿐 보장하지 않습니다. 놓쳐서는 안 되는 것은 대조 폴링이 뒷받침합니다.
- **수신자는 가동 시간의 의무를 물려받습니다.** 자사 엔드포인트의 가용성이 이제 다른 누군가의 이벤트를 좌우합니다. 배포, 콜드 스타트, 타임아웃이 모두 연동 버그가 됩니다.
- **보안은 직접 켜야 적용됩니다.** 검증하지 않는 웹훅 엔드포인트는 인증 없는 쓰기 API입니다. HMAC 체크리스트가 연동과 주입의 차이입니다.
- **디버깅이 두 회사에 걸칩니다.** 양측의 전달 로그와 리플레이 도구가 "도착하지 않았다"를 대치 상태에서 diff로 바꿉니다.
- **페이로드는 변합니다.** 제공업체는 이벤트 스키마의 버전을 올리고, 정확한 모양에 고정된 소비자는 조용히 깨집니다. 방어적으로 파싱하고 알 수 없는 필드는 무시해야 합니다.

## Back4app에서의 웹훅

Back4app은 관리형 데이터베이스, 자동 생성되는 REST 및 GraphQL API, 인증, 파일 스토리지, Cloud Code 서버리스 함수를 결합한 오픈소스 Backend as a Service(BaaS) 플랫폼입니다. 코드 탭이 보여 주듯, 패턴의 두 절반 모두 [Cloud Code](/glossary/ko/cloud-code-serverless-functions/) 구성 요소 하나면 됩니다. **나가는 방향:** 데이터를 지켜보는 `afterSave` 트리거가 `Parse.Cloud.httpRequest`로 등록된 어떤 URL이든 호출합니다. 함수를 작성하는 것만으로 앱이 웹훅 제공업체가 되며, 송신자 측 규율(실패 시 재시도, 전달 로깅)도 같은 파일에 담깁니다. **들어오는 방향:** HTTPS로 노출된 Cloud Function이 준비된 수신자입니다. 서버 측 설정에 둔 시크릿으로 HMAC을 검증하고, 결과를 데이터베이스에 쓰고, 빠르게 반환하면, 업데이트는 [Live Query](/glossary/ko/real-time-live-queries/)를 통해 열려 있는 모든 화면으로 팬아웃됩니다. 결제 플랫폼의 이벤트에서 사용자의 영수증까지, 어느 단계에서도 직접 운영할 서버 없이 루프가 완성됩니다.
