n8n 멱등성 웹훅 — 같은 이벤트가 두 번 실행되지 않게 막는 법
이 글과 연결된 글 보기
드래그로 이동 · 휠/두 손가락으로 확대 · 방향키로 글 선택 · 0으로 맞춤. 실선: 본문 링크, 점선: 시리즈·주제의 대표 연결.표시된 글 7편 목록
n8n.io 공식 템플릿을 가져오면, 결제·이메일·DB 기록처럼 한 번만 일어나야 하는 side effect가 있는 웹훅 워크플로우에서 같은 이벤트가 24시간 안에 다시 들어와도 실제 액션은 딱 한 번만 실행되게 막는 멱등성 게이트 구조를 그대로 익힐 수 있습니다.
이 템플릿 하나로 보여줄 수 있는 것
면접에서 "웹훅으로 결제 연동해봤어요"라고 말하면 자주 따라오는 질문이 있습니다. "만약 같은 요청이 두 번 오면 어떻게 되나요?" 웹훅을 연결만 해본 사람은 이 질문에 당황하기 쉽지만, 이 템플릿을 뜯어본 사람은 구체적으로 답할 수 있습니다. "웹훅 제공자는 원래 같은 이벤트를 여러 번 보낼 수 있어서, 이벤트마다 고유한 키를 만들어 중복이면 막는 게이트를 앞에 둡니다."
여기서 배우는 역량은 세 가지입니다.
- at-least-once delivery 이해 — 웹훅이 "한 번만 온다"는 전제가 틀렸다는 것, 그리고 왜 그런 전제가 위험한지를 아는 경험
- 멱등성 키 설계 — 페이로드 안에서 이벤트를 유일하게 식별할 필드를 골라 중복 판정 기준을 만들어본 경험
- 응답과 판단의 분리 — 웹훅에 즉시 200 OK로 응답하는 일과, 그 요청을 실제로 처리할지 판단하는 일을 별개 단계로 나눠 설계한 경험
이 세 가지는 결제 웹훅뿐 아니라 주문 확인, 알림 발송, 외부 API 콜백처럼 "같은 신호가 두 번 오면 곤란한" 모든 자동화에 그대로 적용됩니다. 한 번 이해해두면 웹훅이 들어가는 워크플로우를 설계할 때마다 "이거 중복 오면 어떻게 하지"를 습관적으로 먼저 물을 수 있습니다.
이 템플릿은 무엇인가
이 워크플로우는 데이터팝콘이 만든 게 아닙니다. n8n 공식 템플릿 마켓플레이스(n8n.io/workflows)에 Prevent duplicate webhook executions with AARI idempotency gate라는 이름으로 공개된 템플릿이고, 제작자는 Nesho Neshev입니다. 카테고리는 Engineering으로 등록돼 있습니다.
공식 페이지는 이 템플릿의 대상을 "웹훅을 받아 결제, 이메일, DB 삽입, API 호출 같은 side effect를 실행하는 n8n 워크플로우를 운영하는 사람 누구나"라고 밝힙니다. 문제 설명은 이렇습니다. 대부분의 웹훅 제공자는 "at-least-once delivery"(최소 한 번 전달) 방식을 씁니다. 요청이 타임아웃되거나 실패한 것처럼 보이면 제공자는 같은 이벤트를 다시 보내는데, n8n 입장에서는 이게 그냥 새로운 트리거로 보입니다. 결과적으로 같은 워크플로우가 같은 이벤트에 대해 두 번 실행될 수 있습니다.
해결 방식은 side effect가 실행되기 전에 멱등성 체크를 끼워 넣는 것입니다. 공식 설명에 따르면 첫 이벤트가 도착하면 워크플로우는 그 이벤트의 idempotency key를 기록하고 정상적으로 진행합니다. 24시간 안에 같은 이벤트가 다시 들어오면 게이트가 BLOCK 판정을 내리고, 결제·이메일 발송·DB 기록 같은 side effect가 실행되기 전에 워크플로우를 멈춥니다. 여기에 한 가지 장치가 더 있습니다. 웹훅은 워크플로우 실행 시간과 무관하게 즉시 200 OK로 응답합니다. 그래야 제공자가 "응답이 늦다"는 이유만으로 또 재시도하는 걸 막을 수 있고, 그다음에야 멱등성 게이트가 진행할지 멈출지를 판단합니다. 공식 페이지는 이 구조로 막을 수 있는 사례를 중복 결제, 중복 확인 이메일, 중복 DB 행, 중복 외부 API 호출 네 가지로 정리해둡니다.
n8n 설치나 웹훅 노드의 기본 사용법은 n8n으로 반복업무 자동화 포트폴리오 만들기에 정리돼 있으니, 이 글은 멱등성 게이트라는 개념 자체에 집중합니다.
구조 — Trigger / Task / Target
공식 문서의 흐름 설명을 Trigger/Task/Target 세 칸으로 정리하면 이렇습니다. 이 템플릿은 "응답"과 "판단"이 갈라진다는 점이 특이합니다.
- Trigger — Webhook 노드가 결제·주문 이벤트를 수신합니다. 제공자가 at-least-once delivery를 쓰기 때문에 같은 이벤트가 재전송될 수 있다는 전제를 이 트리거 단계부터 깔고 갑니다.
- Task — 도착 즉시 200 OK로 응답해 제공자의 불필요한 재시도를 차단하는 동시에, HTTP Request 노드로 AARI에 이벤트의 idempotency key를 보내 체크를 요청합니다. 키를 기록해두는 일 자체는 이 체크 시점에 AARI 쪽에서 이뤄지고, If 노드는 그 응답을 PROCEED(처음 보는 이벤트)와 BLOCK(24시간 안에 이미 본 이벤트)으로 가릅니다. PROCEED로 판정된 이벤트만 Edit Fields(Set)를 거쳐 다음 단계로 넘어갑니다.
- Target — PROCEED로 판정된 이벤트만 실제 액션 노드(자리표시자를 Stripe, Gmail, Postgres 등으로 교체)에 도달해 side effect를 실행합니다. BLOCK으로 판정된 이벤트는 여기서 조용히 끝나고, 아무 side effect도 일어나지 않습니다.
공식 카드 미리보기에 노출된 노드는 HTTP Request, If, Edit Fields(Set), 그리고 "+2 more"입니다. 이름이 나오지 않은 나머지 두 노드가 정확히 무엇인지는 페이지 텍스트만으로는 확인되지 않지만, 앞서 설명한 흐름을 감안하면 Webhook 트리거 노드와 사용자가 교체하는 실제 액션 노드가 그 자리를 채우고 있을 가능성이 큽니다. 정확한 구성은 템플릿을 임포트한 뒤 캔버스에서 직접 확인해야 합니다.
만들기
공식 페이지가 "약 2분"이라고 밝힌 설정 단계를 실제 준비 순서로 풀면 아래와 같습니다.
1. AARI 무료 API 키 발급
api.getaari.com/n8n에서 AARI API 키를 무료로 발급받습니다. AARI(getaari.com)는 멱등성 체크를 API 형태로 제공하는 제3자 서비스이고, 이 시리즈에서는 처음 등장하는 이름입니다. 지금 확인되는 건 "무료 티어가 있는 멱등성 체크 API"라는 사실뿐이라, 딱 그만큼만 알고 넘어가면 됩니다.
2. n8n에 Header Auth 크리덴셜 등록
n8n의 Credentials 메뉴에서 New를 눌러 Header Auth 크리덴셜을 만듭니다. 헤더 이름은 X-API-Key로 설정하고, 값에는 1번에서 발급받은 API 키를 붙여 넣습니다.
3. AARI 멱등성 게이트 노드에 크리덴셜 연결
캔버스의 AARI idempotency gate 노드(위 구조도의 HTTP Request 노드)에서 방금 만든 크리덴셜을 선택합니다. 이 연결이 끝나야 노드가 실제로 AARI에 체크 요청을 보낼 수 있습니다.
4. idempotency_key를 웹훅 페이로드에 맞게 조정
멱등성 키는 이벤트를 유일하게 식별할 수 있어야 합니다. 공식 페이지가 제시하는 예시는 웹훅을 보내는 서비스마다 다릅니다.
// Stripe 웹훅 이벤트
{{ $json.id }}
// Shopify 주문 이벤트
{{ $json.order_id }}
// 특정 필드가 없는 범용 이벤트 — 안정적인 필드 두 개를 조합
{{ $json.type }}:{{ $json.created }}
Stripe나 Shopify처럼 이벤트마다 고유 ID를 이미 주는 서비스라면 그 ID를 그대로 쓰면 됩니다. 그런 필드가 없는 웹훅이라면 이벤트 타입과 생성 시각처럼 변하지 않는 필드 여러 개를 합쳐서 대체 키를 만들라고 안내합니다. 핵심은 같은 이벤트가 재전송됐을 때 항상 같은 키가 나와야 한다는 것입니다.
5. placeholder 액션 노드를 실제 로직으로 교체
템플릿에 들어 있는 자리표시자 액션 노드를 실제로 쓸 서비스(Stripe 결제 확정, Gmail 발송, Postgres 삽입 등)로 바꿉니다. 이 자리가 바로 멱등성 게이트가 지켜주는 대상입니다. 공식 페이지는 이 부분의 상세 설명이 워크플로우 안 스티키 노트에 있고 페이지 텍스트만으로는 확인되지 않는다고 밝히고 있어서, 정확한 배선은 템플릿을 열어 직접 봐야 합니다.
주의할 점 · 제약
공식 페이지에 명시된 조건과 이 템플릿의 구조적 특성을 종합하면 확인되는 주의점은 네 가지입니다.
- 버전과 크리덴셜 요구 사항 — n8n 1.0 이상이 필요하고, AARI API 키(무료 티어 있음)가 있어야 게이트 노드가 동작합니다. 무료 티어의 구체적인 요청 한도는 공식 페이지에 나와 있지 않아, 실습 전에 AARI 사이트에서 직접 확인해야 합니다.
- 24시간이라는 창 — 같은 이벤트가 24시간을 넘겨 다시 들어오면 게이트는 더 이상 그것을 중복으로 보지 않습니다. 공식 페이지는 idempotency_key를 상황에 맞게 조정하라고만 안내할 뿐, 이 24시간이라는 창 자체를 조정할 수 있는지는 페이지 텍스트에 나와 있지 않습니다.
- 멱등성 체크를 외부 서비스에 위임한다 — 이 템플릿은 중복 판정 로직을 n8n 안에 직접 구현하지 않고 HTTP Request로 AARI에 물어봅니다. 외부 API를 호출하는 구조이므로, 그 호출에 걸리는 시간만큼 웹훅 응답 이전 전체 처리 흐름에 지연 요소가 하나 더 생긴다는 점은 설계할 때 감안해야 합니다.
- 노드 구성 불확실성 — 공식 카드 미리보기에는 HTTP Request, If, Edit Fields(Set) 세 노드 이름과 "+2 more"만 나와 있고, 실제 액션 노드 배선의 상세 설명은 페이지 텍스트가 아니라 워크플로우 안 스티키 노트에 있다고 안내돼 있습니다. 정확한 전체 구성은 임포트 후 캔버스에서 확인해야 합니다.
포트폴리오·면접 포인트
포트폴리오에는 같은 Stripe 웹훅 페이로드를 두 번 보냈을 때 첫 번째는 PROCEED, 두 번째는 BLOCK으로 갈리는 실행 화면 캡처 한 장이면 충분합니다. "결제 웹훅을 연동했다"보다 "웹훅이 at-least-once delivery로 재전송될 수 있다는 걸 알고, 멱등성 키로 중복 실행을 막는 게이트를 앞단에 뒀다"고 쓰면 이력서 한 줄이 훨씬 구체적인 근거를 갖습니다.
응용해볼 만한 지점도 하나 있습니다. 멱등성 키를 꼭 외부 서비스에 저장할 필요는 없습니다. n8n에는 워크플로우 실행 사이에 데이터를 남겨둘 수 있는 Data Table 기능이 있어서, AARI 같은 외부 API 대신 그 안에 처리한 이벤트 키를 직접 기록하고 조회하는 방식으로 바꿔보는 것도 포트폴리오에 넣을 만한 변형입니다.
면접에서는 "웹훅이 중복으로 오면 어떻게 처리하나요?"라는 질문이 나올 수 있습니다. 이 템플릿을 이해했다면 "웹훅 제공자는 응답이 늦거나 실패한 것처럼 보이면 같은 이벤트를 다시 보낼 수 있다는 걸 전제로 설계했다. 이벤트마다 고유한 키를 만들어 기록해두고, 같은 키가 일정 시간 안에 다시 들어오면 실제 처리 단계 전에 막았다. 응답 자체는 처리 결과와 무관하게 즉시 보내서 재시도 자체를 줄였다"고 구체적으로 답할 수 있습니다. 이런 답은 웹훅 연동 경험이 있는 사람과, 웹훅의 전달 방식까지 이해하고 설계한 사람을 가릅니다.
다음 액션
다음 편에서는 Azure OpenAI와 n8n API를 엮어 실패한 워크플로우를 AI가 직접 진단하고 고치는 템플릿을 다룹니다. 오늘 템플릿이 "실행 전에 중복을 막는" 방어적 설계였다면, 다음 편은 반대로 이미 실패한 실행을 사후에 AI가 개입해 고치는 설계라 대조해서 보면 좋습니다.
