API 연동을 처음 구현하면 HTTP 요청에 200 OK가 돌아오는 것을 성공으로 생각하기 쉽습니다. 하지만 운영 환경에서는 응답이 성공했는데 동일한 이벤트가 다시 도착하거나, 요청은 실패한 것처럼 보였는데 서버 쪽 작업은 이미 실행된 상황이 생깁니다. 이때 웹훅 중복 처리를 막지 않으면 같은 주문이 두 번 등록되거나 알림이 여러 번 발송될 수 있습니다.
기존의 API란 무엇인가?에서는 엔드포인트, HTTP 메서드, 헤더, 본문 같은 기본 구조를 설명했습니다. 이번에는 그 다음 단계인 Webhook Reliability(웹훅 신뢰성)를 살펴보고자 합니다.
200 OK가 보장하는 것은 어디까지인가?
웹훅 전송자가 200 응답을 받았다는 것은 일반적으로 수신 서버가 해당 요청을 성공으로 응답했다는 사실을 의미합니다. 하지만 이 값만으로 다음을 보장할 수는 없습니다.
- 데이터베이스에 영구 저장되었는가?
- 외부 시스템에 중복으로 반영되지 않았는가?
- 비동기 후속 작업까지 끝났는가?
- 같은 이벤트가 재전송되었을 때도 결과가 한 번만 적용되는가?
반대로 500이나 네트워크 타임아웃이 발생했다고 해서 아무 작업도 수행되지 않았다고 단정할 수도 없습니다. 응답 전달 단계에서 연결이 끊겼을 가능성이 있기 때문입니다.
웹훅은 왜 중복되어 들어오는가?
간단한 시나리오를 생각해 봅시다.
- 결제 서비스가 이벤트
evt_001을 보낸다. - 수신 서버가 이벤트를 저장한다.
- 수신 서버의 응답이 전송자에게 도착하기 전에 연결이 끊긴다.
- 전송자는 실패로 판단하고 동일 이벤트를 다시 보낸다.
- 수신 서버에 중복 방어가 없다면 같은 작업을 또 실행한다.
이 문제를 줄이려면 이벤트의 고유 식별자와 Idempotency(멱등성) 정책을 설계해야 합니다. 멱등성은 동일한 작업을 여러 차례 요청해도 허용되는 부작용이 한 번만 발생하도록 하는 속성입니다.
Python + SQLite로 중복 이벤트를 재현해 보자
아래는 외부 웹훅을 받지 않고 동일한 이벤트를 두 차례 처리하는 메모리 기반 교육용 시뮬레이션입니다. SQLite의 PRIMARY KEY 중복 제한을 활용합니다.
import sqlite3
def accept_event(conn, event_id, payload):
try:
with conn:
conn.execute(
'INSERT INTO processed(event_id, payload) VALUES (?, ?)',
(event_id, payload),
)
except sqlite3.IntegrityError:
return 200, 'duplicate ignored'
return 200, 'accepted'
conn = sqlite3.connect(':memory:')
conn.execute('CREATE TABLE processed (event_id TEXT PRIMARY KEY, payload TEXT NOT NULL)')
print(accept_event(conn, 'evt_001', '{"kind":"updated"}'))
print(accept_event(conn, 'evt_001', '{"kind":"updated"}'))
print(conn.execute('SELECT COUNT(*) FROM processed').fetchone()[0])
실행 결과는 다음과 같습니다.
(200, 'accepted')
(200, 'duplicate ignored')
1
두 번 호출했지만 저장된 레코드는 하나입니다.
다만 이 예제에는 서명 검증, 실제 HTTP 처리, 영속적인 운영 데이터베이스, 외부 시스템 부작용의 원자적 처리가 포함되어 있지 않습니다. :memory: 데이터베이스는 프로세스 종료 후 사라지며, 실제 시스템에서는 재시작 뒤에도 유지되는 고유 키 저장소와 트랜잭션 또는 Outbox 패턴 같은 추가 설계가 필요합니다.
중복 방어만으로 웹훅이 안전해지지는 않는다
| 실패 조건 | 확인할 사항 |
|---|---|
| 동일 이벤트 두 번 수신 | 고유 이벤트 ID와 중복 처리 정책 |
| 전송자 타임아웃 | 서버가 이미 처리했는지 재현 가능한 로그 |
| 일시적 5xx 오류 | 제한된 횟수·간격의 재시도와 백오프 |
| 영구적인 요청 오류 | 무제한 재시도 대신 오류 분류와 조사 경로 |
| 서명 불일치 | 처리 전에 인증 검증 및 거부 |
| 처리 순서 변경 | 이벤트 버전·상태 전이 및 순서 의존성 검토 |
운영 중인 이벤트 시스템은 전송 플랫폼의 재시도 규칙부터 확인해야 합니다.
어떤 서비스는 같은 이벤트를 여러 번 전달하며, 이벤트 순서도 보장하지 않을 수 있습니다.
예를 들어 AWS Lambda의 비동기 호출 문서 역시 중복 이벤트에 안전하게 대응하도록 권고합니다.
특히 외부 결제나 재고 변경이 이미 일어난 뒤 이벤트 ID만 기록하면 두 단계 사이의 장애에서 중복 부작용이 여전히 발생할 수 있으며, 단일 DB 트랜잭션, Outbox, downstream 고유 키 등을 고려해야 합니다.
실제 장애를 분석할 때 필요한 자료
웹훅 장애를 의뢰하거나 자체적으로 검증할 때는 다음 자료를 먼저 확보하는 것이 좋습니다.
- 이벤트 식별자와 발생 시각(시간대 포함)
- 전송자 측 재시도 기록과 수신 서버 응답 코드
- 중복 실행으로 의심되는 부작용(레코드, 이메일, 주문 등)
- 서명 검증 방식 및 비밀키 관리 경계(비밀값 자체는 공유하지 않음)
- 정상·중복·일시 실패·영구 실패에 대한 테스트 기대값
DeepLinK는 합성 이벤트로 여러 실패 경로를 체험할 수 있는 Webhook Reliability Lab을 운영하고 있습니다. 이 도구는 브라우저 시뮬레이션이지 실제 외부 웹훅을 수신하고 장애를 복구하는 고객 사례가 아닙니다.
실제 시스템의 원인을 진단할 필요가 있다면 API Integration & Webhook Reliability Audit에서 작업 범위와 검수 조건을 확인할 수 있으며, 진단과 운영 코드 수정·배포는 별도 합의가 필요합니다.
기술 범위 문의: DEEPLINK Technical scope request
