이 페이지에서 다루는 것
Webhook Idempotency
한 번에 끝까지 읽으며 맥락을 쌓을 수 있도록 구성했습니다.
중복 요청과 재시도에도 결제·알림·자동화 효과가 한 번만 적용되게 만드는 VIBE 코딩 실전 설계
학습 유형
주제 심층 학습
핵심 주제
Webhook Idempotency
키워드
웹훅 멱등성 · 중복 요청 방지 · AI 코딩 검증 · 상태 전이 · 재시도 설계 · 운영 로그
이 페이지에서 다루는 것
Webhook Idempotency
한 번에 끝까지 읽으며 맥락을 쌓을 수 있도록 구성했습니다.
예상 학습 시간
14분
본문과 보조 자료(이미지·영상)를 포함한 대략적인 소요입니다.
학습 팁
섹션 순서대로 읽고, 필요한 부분만 다시 찾아보기
표·이미지·영상은 본문 흐름을 돕는 보조 설명입니다.
VIBE 코딩으로 결제 알림, 가입 완료 알림, 배포 이벤트, 폼 제출, 자동화 웹훅을 만들 때 가장 위험한 착각은 “요청이 한 번만 온다”는 믿음입니다. 실제 서비스에서는 네트워크 재시도, 외부 서비스의 중복 전송, 사용자의 새로고침, 작업 큐 재처리, 에이전트의 반복 실행 때문에 같은 의미의 요청이 여러 번 들어옵니다. 이때 중복을 막는 설계가 없으면 한 사람에게 쿠폰이 두 번 지급되고, 같은 알림이 세 번 발송되고, 결제 성공 이벤트 하나가 여러 개의 주문 처리로 번질 수 있습니다.
이 글의 목표는 웹훅을 빠르게 붙이는 법이 아니라, AI가 만든 웹훅을 실제 서비스에 넣어도 안전한 운영 루프로 만드는 법입니다. 초보자는 “같은 요청이 또 와도 결과가 한 번만 적용되게 만든다” 정도로 이해하면 됩니다. 전문가 관점에서는 요청 식별자, 원자적 저장, 상태 전이, 재시도 정책, 관측 로그, 롤백 기준을 하나의 작업 계약으로 묶는 것이 핵심입니다.
웹훅 안전성은 라우트 하나를 만드는 문제가 아니라 “같은 사건을 여러 번 받아도 최종 상태가 한 번만 바뀌는가”를 증명하는 문제입니다. AI에게 웹훅 구현을 맡길 때는 엔드포인트, 검증, 저장, 처리, 재시도, 로그를 한 번에 맡기기보다 작게 나눠야 합니다.
가장 안전한 기본형은 다음 순서입니다.
| 구간 | 목표 | 실패하면 생기는 문제 |
|---|---|---|
| 수신 | 외부 이벤트를 받되 본문과 헤더를 보존 | 나중에 원인 분석을 못 함 |
| 식별 | eventId 또는 의미 있는 멱등성 식별자를 만든다 | 같은 요청을 구분하지 못 함 |
| 기록 | 먼저 받은 사실을 원자적으로 저장한다 | 동시 요청이 둘 다 성공 처리됨 |
| 처리 | 상태 전이를 한 번만 수행한다 | 결제, 포인트, 알림이 중복됨 |
| 응답 | 재시도 가능한 실패와 확정 실패를 구분한다 | 외부 서비스가 계속 재전송함 |
| 관측 | 중복 수, 처리 시간, 실패 원인을 남긴다 | 문제가 커진 뒤에야 발견함 |
초보자에게는 복잡해 보이지만 원리는 단순합니다. “받고 바로 처리”하지 말고, “받은 사실을 먼저 기록하고, 이미 처리한 사건인지 확인한 뒤, 한 번만 상태를 바꾼다”가 기본입니다.
AI가 작성한 웹훅 코드는 보통 데모에서는 잘 동작합니다. 로컬에서 한 번 요청을 보내면 200이 나오고, DB에 행이 생기고, 화면도 바뀝니다. 하지만 운영 환경은 데모와 다릅니다. 외부 서비스는 응답이 늦으면 같은 이벤트를 다시 보낼 수 있고, 서버리스 런타임은 순간적으로 같은 함수 인스턴스를 여러 개 띄울 수 있으며, 브라우저나 자동화 도구는 사용자가 의도하지 않은 재전송을 만들 수 있습니다.
예를 들어 AI가 “결제 성공 웹훅을 받으면 주문 상태를 paid로 바꾸고 환영 쿠폰을 지급한다”는 코드를 만들었다고 합시다. 첫 요청이 처리되는 동안 네트워크가 끊겨 외부 서비스가 두 번째 요청을 보냅니다. 두 요청이 동시에 들어오면 둘 다 “아직 쿠폰 지급 안 됨”이라고 판단할 수 있습니다. 그러면 쿠폰이 두 번 들어갑니다. 로그에는 둘 다 정상 200으로 남기 때문에 사용자가 신고하기 전까지 발견하기 어렵습니다.
VIBE 코딩에서는 이런 오류를 “AI가 실수했다”로 끝내면 안 됩니다. AI가 실수하기 쉬운 경계 조건을 작업 루프에 넣어야 합니다. 중복 요청, 순서 뒤바뀜, 부분 실패, 재처리, 관측 누락을 먼저 테스트로 고정하고, 그다음 구현을 맡기는 방식이 안전합니다.
웹훅 설계의 첫 질문은 “이 요청은 무엇을 한 번만 일으켜야 하는가”입니다. 결제 승인, 구독 해지, 파일 변환 완료, 배포 성공, 문의 등록, 알림 발송처럼 결과가 있는 사건을 하나 고릅니다. 그런 다음 같은 사건을 식별할 기준을 정합니다.
가능하면 외부 서비스가 제공하는 eventId를 씁니다. 없다면 provider, resourceId, eventType, occurredAt 같은 값을 조합해 안정적인 멱등성 식별자를 만듭니다. 단, 시간만으로 식별하면 위험합니다. 같은 초에 여러 사건이 생길 수 있고, 재시도 때 시간이 바뀔 수 있기 때문입니다. 사용자의 이름이나 이메일처럼 바뀔 수 있는 값도 단독 식별자로 쓰면 안 됩니다.
AI에게는 이렇게 지시합니다.
이 웹훅은 같은 eventId를 가진 요청이 여러 번 들어와도 비즈니스 효과가 한 번만 적용되어야 한다. eventId가 없을 때는 provider, resourceId, eventType을 조합한 idempotencyKey를 만들고, 이 값의 중복을 저장 계층에서 막아라. 단순 메모리 캐시는 서버 재시작과 다중 인스턴스에서 깨지므로 사용하지 마라.
웹훅을 받자마자 비즈니스 로직을 실행하지 마세요. 먼저 “이 사건을 받았다”는 기록을 남깁니다. 이 기록에는 idempotencyKey, provider, eventType, rawPayloadHash, status, receivedAt, processedAt, lastErrorKind 정도가 필요합니다. 원문 전체를 저장해야 하는지는 서비스 정책에 따라 다르지만, 민감한 값이 섞일 수 있으므로 최소한 해시와 안전한 요약을 남기는 편이 좋습니다.
중요한 점은 중복 방지를 애플리케이션 if 문에만 맡기지 않는 것입니다. 두 요청이 동시에 들어오면 둘 다 if 문을 통과할 수 있습니다. 저장 계층의 unique 제약이나 원자적 upsert를 사용해야 합니다. 이미 같은 키가 있으면 새 처리를 시작하지 말고 기존 상태를 읽어 응답해야 합니다.
웹훅 상태는 최소한 received, processing, processed, ignored, failedRetryable, failedFinal 정도로 나눕니다. 이름은 달라도 괜찮지만 의미는 분명해야 합니다. received는 수신됨, processing은 처리 중, processed는 처리 완료, ignored는 이미 처리했거나 처리할 필요 없음, failedRetryable은 다시 시도할 수 있음, failedFinal은 사람이 봐야 하는 확정 실패입니다.
상태 전이 표가 없으면 AI는 실패를 모두 같은 방식으로 처리하기 쉽습니다. 예를 들어 외부 API 일시 장애와 잘못된 서명은 완전히 다릅니다. 일시 장애는 재시도 대상이지만, 서명 오류는 재시도해도 해결되지 않습니다. 이 구분이 없으면 큐가 쌓이거나 공격성 요청을 계속 처리하게 됩니다.
주문 상태 변경, 포인트 지급, 알림 예약, 감사 로그 기록을 여러 곳에 흩뿌리면 중복 방지가 어려워집니다. “이 사건의 효과를 적용하는 함수”를 하나로 만들고, 그 함수 앞뒤에 멱등성 잠금과 상태 갱신을 둡니다. 이렇게 하면 테스트도 쉬워집니다.
실무에서는 처리 함수가 완전히 한 트랜잭션에 들어가지 못할 때가 많습니다. 예를 들어 DB 상태 변경과 외부 알림 발송은 원자적으로 묶기 어렵습니다. 이때는 먼저 내부 상태를 확정하고, 알림은 별도 outbox나 작업 큐에 넣는 방식이 안전합니다. 알림 발송 자체도 messageId 기준으로 중복을 막아야 합니다.
웹훅 응답은 외부 서비스의 재시도 정책과 연결됩니다. 처리에 성공했거나 이미 처리한 중복 이벤트라면 보통 성공 응답을 줍니다. 그래야 같은 이벤트가 계속 들어오지 않습니다. 반대로 일시 장애로 처리하지 못했다면 재시도 가능한 응답을 줘도 됩니다. 하지만 검증 실패, 형식 오류, 허용되지 않은 이벤트 타입은 재시도해도 해결되지 않으므로 확정 실패로 기록하고 안전하게 종료해야 합니다.
여기서 초보자가 자주 하는 실수는 모든 오류를 500으로 던지는 것입니다. 그러면 외부 서비스가 계속 재전송하고, 중복 방지 테이블과 로그가 불필요하게 커집니다. 반대로 모든 오류에 성공 응답을 주면 실제 장애를 놓칩니다. 오류 종류를 분류해야 합니다.
결제 완료 이벤트는 중복 비용이 가장 큽니다. 같은 결제에 대해 주문 상태 변경, 영수증 발송, 포인트 지급, 구독 권한 부여가 연쇄로 일어납니다. 따라서 paymentEventId와 orderId를 모두 기록하고, 주문 상태가 이미 paid라면 포인트와 권한 지급이 이미 끝났는지도 별도 마커로 확인해야 합니다.
좋은 승인 기준은 “같은 paymentEventId를 10번 보내도 주문 행은 paid 한 번, 포인트 지급 기록은 한 번, 알림 예약은 한 번만 생긴다”입니다. AI에게 단순히 결제 웹훅을 만들어 달라고 하지 말고, 이 반복 시나리오를 테스트로 먼저 만들게 해야 합니다.
배포 성공 이벤트를 받아 라이브 스모크 테스트를 돌리는 자동화도 중복을 고려해야 합니다. 같은 commitSha에 대해 여러 배포 이벤트가 들어오면 스모크 테스트가 동시에 여러 번 돌 수 있습니다. 테스트가 읽기 전용이면 큰 문제는 없어 보이지만, 알림 발송이나 이슈 생성이 붙으면 중복 소음이 됩니다.
이 경우 idempotencyKey는 provider, projectName, environment, commitSha, deploymentState 조합이 될 수 있습니다. 이미 같은 배포에 대해 스모크가 진행 중이면 새 작업을 만들지 말고 기존 작업 링크를 반환하는 편이 안전합니다.
사용자가 폼 제출 후 네트워크가 느려서 버튼을 여러 번 누르는 경우가 있습니다. 같은 문의가 여러 개 저장되면 운영자가 중복 답변을 하거나 자동 답변이 반복됩니다. requestId를 클라이언트에서 생성하거나, 서버에서 normalizedEmail, normalizedTitle, shortTimeBucket, contentHash를 조합해 중복 후보를 감지할 수 있습니다.
다만 문의는 결제와 달리 “완전 동일하지 않아도 의미상 중복”인 경우가 있습니다. 따라서 자동으로 삭제하기보다 중복 후보로 묶고, 공개 화면에는 한 건만 노출하는 방식이 더 안전할 수 있습니다.
첫 번째 실패 모드는 메모리 기반 중복 방지입니다. 로컬 Map이나 전역 변수는 개발 서버에서는 작동하는 것처럼 보입니다. 하지만 서버가 재시작되거나 인스턴스가 여러 개가 되면 바로 깨집니다. 중복 방지는 반드시 공유 저장소나 데이터베이스 제약에 있어야 합니다.
두 번째 실패 모드는 “이미 처리됨”을 너무 빨리 기록하는 것입니다. 처리 시작 전에 processed로 표시했다가 중간에 실패하면 실제 효과는 적용되지 않았는데 이후 재시도는 모두 중복으로 무시됩니다. 처리 전에는 received 또는 processing, 처리 후에만 processed로 바꿔야 합니다.
세 번째 실패 모드는 외부 부작용을 트랜잭션 안에 있다고 착각하는 것입니다. DB 업데이트는 되돌릴 수 있어도 이미 발송된 이메일, 메시지, 외부 결제 요청은 쉽게 되돌릴 수 없습니다. 외부 부작용은 outbox, messageId, 발송 로그로 별도 멱등성을 가져야 합니다.
네 번째 실패 모드는 검증 실패와 일시 장애를 섞는 것입니다. 서명 불일치, 허용되지 않은 이벤트, 필수 필드 누락은 재시도 대상이 아닙니다. 반면 저장소 연결 장애나 외부 서비스 시간 초과는 재시도 대상입니다. AI에게 오류 분류표를 주지 않으면 모든 오류가 같은 catch 블록으로 모입니다.
다섯 번째 실패 모드는 로그에 민감한 값을 남기는 것입니다. 웹훅 본문에는 결제자 정보, 이메일, 내부 인증값, 주문 상세가 섞일 수 있습니다. 운영 로그에는 식별 가능한 최소값, 해시, 상태, 오류 종류, 처리 시간만 남기고 원문 전체 출력은 피해야 합니다.
아래 문장을 작업 지시서에 그대로 넣으면 좋습니다.
웹훅 구현 범위는 수신, 검증, 멱등성 기록, 상태 전이, 비즈니스 효과 적용, 로그, 테스트까지다. 같은 사건이 여러 번 도착해도 결과는 한 번만 적용되어야 한다. 저장소의 unique 제약 또는 원자적 upsert를 사용하고, 메모리 캐시만으로 중복을 막지 마라. 테스트는 동일 eventId 반복, 동시 요청, 처리 중 실패, 이미 처리된 사건 재수신, 검증 실패, 일시 장애를 포함해야 한다. 공개 로그에는 민감한 원문을 출력하지 말고 안전한 식별자와 상태만 남겨라.
더 강하게 운영하려면 승인 조건도 함께 줍니다.
완료로 보고하려면 중복 요청 10회 테스트, 동시 요청 테스트, 재시도 가능 실패 테스트, 확정 실패 테스트, 부작용 중복 방지 테스트, live smoke 결과를 제시하라. 하나라도 실패하면 구현을 공개하지 말고 보류 사유와 다음 수정 범위를 보고하라.
이 지시는 AI에게 코드를 많이 쓰게 하는 지시가 아니라, 위험한 경계 조건을 먼저 고정하게 하는 지시입니다.
승인 기준은 숫자로 둬야 합니다. 예를 들어 같은 사건을 10회 재전송했을 때 처리 완료 행은 1개, 부작용 기록은 1개, 중복 수신 로그는 9개여야 합니다. 동시 요청 20개를 보냈을 때도 결과가 같아야 합니다. 처리 시간이 평소보다 3배 이상 늘거나, 재시도 가능 실패가 5분 동안 20건 이상 쌓이면 자동 공개를 멈추고 사람이 봐야 합니다.
중단 기준도 명확해야 합니다. 식별자를 만들 수 없는 이벤트, 서명 검증이 실패한 이벤트, 필수 필드가 없는 이벤트, 같은 사건인데 서로 다른 금액이나 사용자로 들어온 이벤트는 자동 처리하지 않습니다. 이런 요청은 무시하거나 보류 큐로 보내고, 사용자에게 영향을 주는 상태 변경은 하지 않습니다.
롤백은 “이전 코드로 되돌린다”만으로 충분하지 않습니다. 이미 중복 지급된 포인트, 중복 생성된 알림, 잘못 바뀐 주문 상태를 어떻게 정리할지도 필요합니다. 따라서 배포 전부터 보상 작업 목록을 만들어 둬야 합니다. 중복 부작용을 찾는 쿼리, 사용자 알림 문구, 수동 보정 승인자, 재처리 금지 조건까지 실무 절차로 관리해야 합니다.
첫 번째 실습은 결제처럼 위험한 영역이 아니라 내부 알림이나 배포 스모크 같은 낮은 위험의 웹훅으로 시작하세요. 같은 이벤트를 반복 전송하는 테스트를 만들고, 결과가 한 번만 적용되는지 확인합니다. 그다음 권한 부여, 포인트 지급, 구독 상태 변경처럼 비용이 큰 영역으로 확장합니다.
VIBE 코딩에서 웹훅 멱등성 루프는 “고급 백엔드 기법”이 아니라 AI 자동화를 운영 서비스에 연결하기 위한 기본 안전장치입니다. AI는 빠르게 엔드포인트를 만들 수 있지만, 같은 사건이 반복될 때의 책임은 설계자가 져야 합니다. 좋은 작업 지시서는 기능 설명보다 실패 조건을 더 많이 담고, 좋은 완료 보고는 성공 데모보다 중복과 재시도 테스트 결과를 먼저 보여줍니다.
같은 의미의 웹훅 요청이 여러 번 들어와도 주문 변경, 포인트 지급, 알림 발송 같은 실제 효과가 한 번만 적용되게 만드는 설계입니다.
단일 성공 데모가 아니라 동일 eventId 반복, 동시 요청, 처리 중 실패, 이미 처리된 사건 재수신 테스트를 먼저 만들게 해야 합니다.
운영 서비스에서는 위험합니다. 서버 재시작과 다중 인스턴스에서 깨질 수 있으므로 저장소의 unique 제약이나 원자적 upsert 같은 공유 기준이 필요합니다.
이미 처리된 같은 사건이라면 보통 안전한 성공 응답으로 종료해 재전송 폭주를 막습니다. 다만 식별자 충돌이나 서로 다른 금액처럼 위험 신호가 있으면 보류해야 합니다.
같은 사건을 10회 이상 재전송하고 동시 요청을 보내도 비즈니스 효과와 외부 부작용이 각각 한 번만 남는지 숫자로 확인하는 기준이 좋습니다.
다음 학습
AI가 API를 빠르게 만들면 처음에는 버튼이 눌리고 응답이 돌아오는지만 보게 됩니다. 하지만 실제 서비스에서 더 위험한 순간은 버튼이 한 번 눌렸는데 네트워크가 끊기고, 사용자가 새로고침하고, 브라우저가 같은 요청을 다시 보내고, 백그라운드 워커가 실패한 웹훅을 재처리하는 순간입니다. 이때 멱등성 없이 만든 API는 중복 주문, 중복 결제, 중복 알림, 재고 차감 오류를 만들 수 있습니다.
멱등성은 “같은 의도의 요청이 여러 번 들어와도 결과가 한 번 처리된 것처럼 보장하는 성질”입니다. 초보자에게는 어려운 단어처럼 보이지만, 실무에서는 매우 구체적입니다. 클라이언트가 요청마다 고유한 idempotency key를 보내고, 서버는 그 키와 사용자, 작업 종류, 요청 본문 해시, 처리 상태를 저장합니다. 같은 키가 다시 오면 새 작업을 만들지…
웹훅은 외부 시스템이 웹앱에 사건을 알려주는 입구입니다. Web Push는 브라우저가 닫혀 있거나 화면을 보고 있지 않을 때도 운영체제 알림으로 사용자를 깨우는 출구입니다. 두 기술을 연결하면 자동화 엔진, 배포 파이프라인, 모니터링, Hermes 같은 작업자가 중요한 사건을 사람의 휴대폰과 PC 브라우저로 바로 전달할 수 있습니다.
이 글은 한 가지 실전 문제를 다룹니다. '외부 서버에서 작업 완료 웹훅을 보냈을 때, 안드로이드 Chrome과 PC 브라우저에 안정적으로 푸시 알림을 띄우려면 어떤 구조로 설계해야 하는가'입니다. 단순히 알림 코드 몇 줄을 붙이는 이야기가 아닙니다. PWA, Service Worker, Web Push, VAPID, 구독 저장, 웹훅 인증, 만료 구독 정리, Telegram 같은 보조 채널, 클릭 후 상세 페이…