웹훅 타임아웃 함정: n8n 웹훅이 시간 초과되는 이유 (먼저 응답하고, 나중에 작업하기)
요약
n8n 웹훅을 이용한 LLM 워크플로우가 외부 서비스의 짧은 타임아웃(10~30초) 때문에 재시도되어 중복 처리가 발생하는 문제를 다룹니다. 해결책으로, 웹훅의 역할은 수신 확인에만 국한하고 실제 무거운 작업은 비동기적으로 처리하는 '먼저 응답하고, 나중에 작업하기' 패턴을 적용해야 합니다.
핵심 포인트
- 웹훅이 오래 걸리면 외부 서비스가 타임아웃 후 재시도하여 중복 처리가 발생합니다.
- n8n 웹훅의 기본 설정은 전체 워크플로우 실행 시간만큼 연결을 유지하려 합니다.
- 해결책은 'Respond to Webhook' 노드를 사용하여 즉시 200 OK 응답을 보내는 것입니다.
- 실제 무거운 작업(LLM 호출, DB 쓰기)은 웹훅 응답 이후 백그라운드에서 비동기적으로 처리해야 합니다.
웹훅 타임아웃 함정
이 글은 프로덕션 환경에서 n8n으로 LLM 워크플로우를 실행하는 과정에 대한 '빌드-인-퍼블릭(build-in-public)' 시리즈의 일부입니다. 여기에 있는 모든 내용은 제가 판매하고 지원하는 실제 템플릿에서 가져온 것입니다. 실수는 저의 것이고, 수정 사항은 테스트를 거쳤습니다.
아무도 주문하지 않은 중복 주문
3주 전, 저는 보고 싶지 않았던 알림을 받았습니다. "안녕하세요, 동일한 주문 알림을 세 번 받았습니다."
그것은 제가 직접 만든 주문 처리 웹훅이었습니다. 한 스토어에서 주문 이벤트를 보내면 → 저의 n8n 웹훅이 이를 수신하고 → 워크플로우가 주문을 검증하며, LLM(거대 언어 모델)을 호출하여 처리 요약본을 생성하고, 데이터베이스에 행을 기록하며, 확인 이메일을 보냅니다. 전체 과정은 약 60~90초의 실제 작업 시간이 소요됩니다.
문제는 발신자 측에서 타임아웃이 30초로 설정되어 있었다는 것입니다. 제 워크플로우가 응답하는 데는 90초가 걸렸습니다. 그래서 발신자는 발신자들이 하는 대로 정확히 재시도했습니다. 두 번이나요. "성공적으로 처리됨"이라는 실행 기록 세 건, 확인 이메일 세 통, 동일한 주문에 대한 데이터베이스 행 세 개가 생성되었습니다.
제가 만든 '스위퍼 패턴(sweeper pattern)' 덕분에 모든 단계는 아이뎀포턴트(idempotent)하게 설계되어 누군가에게 이중 청구가 되는 일은 없었습니다. 하지만 발신자 측에서는 계속 실패를 목격했습니다. 그들의 대시보드는 빨간색이었습니다. 제 워크플로우의 실행 기록은 녹색이었습니다. 성공적인 실행, 화난 파트너. 이것이 바로 함정입니다.
함정이 존재하는 이유
n8n의 기본 설정에서 웹훅(Webhook) 노드는 전체 워크플로우의 출력을 수집하여 HTTP 응답으로 반환합니다. 이는 곧 HTTP 연결이 전체 워크플로우 동안 열려 있다는 의미입니다. 즉, 모든 LLM 호출, 모든 API 대기 시간, 모든 데이터베이스 쓰기 작업까지 포함됩니다.
n8n 자체 호스팅(self-hosted) 환경은 유휴 웹훅 연결을 약 120초 후에 종료합니다. 클라우드 버전도 비슷한 제한이 있습니다. 하지만 실제 세상의 발신자들은 훨씬 인내심이 없습니다:
- Stripe는 자체 타임아웃 기간 이후 재시도합니다.
- Meta/TikTok은 웹훅 이벤트에 대해 검증 및 재시도를 합니다.
- 많은 SaaS 앱들은 10~30초 만에 포기하고 중복 이벤트를 발생시킵니다.
당신은 발신자의 타임아웃을 통제할 수 없습니다. 오직 당신이 얼마나 빨리 응답하는지만 통제할 수 있습니다. 저는 제 워크플로우를 빠르게 완료하도록 설계했습니다. 하지만 실제로 제가 필요했던 것은 _빠르게 응답하는 것_이었습니다.
해결책: 먼저 응답하고, 나중에 작업하기
이 변경은 약 10분이 걸렸습니다. 핵심 아이디어는 한 문장으로 요약됩니다. 웹훅의 유일한 임무는 수신을 확인하는 것이며, 실제 작업은 응답 이후에 일어난다는 것입니다.
n8n에서는 이 작업을 두 가지 방법으로 수행할 수 있습니다:
옵션 A — 웹훅 노드를 '수신 시(When Received)'로 설정하기 (또는 즉시). 이렇게 하면 응답을 보낸 후에도 워크플로우가 계속 실행됩니다. 최신 n8n 버전에서는 노드 자체에서 응답 모드를 선택할 수 있습니다.
옵션 B — 트리거 바로 다음에 '웹훅에 응답(Respond to Webhook)' 노드를 배치하기. 저는 이 방법을 사용하는데, 그 이유는 명시적이기 때문입니다. 제 흐름은 이제 다음과 같습니다:
Webhook Trigger
→ Respond to Webhook (200, {"received": true, "event_id": "{{ $json.id }}"})
→ Dedupe check (이 event_id를 본 적이 있나요? 그렇다면 건너뛰기)
...
응답은 1초도 안 되어 나갑니다. 발신자는 만족합니다. 워크플로우는 백그라운드에서 비동기적으로 계속 진행됩니다.
제 웹훅 응답 p95 지연 시간은 약 70초에서 약 400밀리초로 줄었습니다. 발신자 재시도율은 0%가 되었습니다. 왜냐하면 발신자의 관점에서는 실패하여 재시도할 필요가 없었기 때문입니다.
절대 건너뛰어서는 안 될 두 가지 사항
빠르게 응답하는 것이 해결책의 절반일 뿐입니다. 이는 두 가지 의무를 만듭니다:
1. Idempotency(멱등성)가 이제 선택이 아닌 필수입니다. 작업을 수행하기 전에 응답하면,
모든 새로운 웹훅에 적용하는 체크리스트
2. 백그라운드 작업 실패를 인지해야 합니다. 응답 우선 패턴(respond-first pattern)을 사용하면, 발신자는 문제가 발생했다는 사실을 결코 알려주지 않습니다. 왜냐하면 당신은 그들에게 모든 것이 괜찮다고 말했기 때문입니다. 따라서 워크플로우의 후반부에는 자체적인 오류 보고 기능이 필요합니다. 즉, event_id와 실패한 노드를 포함하여 저에게 알림(이메일 + Slack 메시지)을 보내는 Error Trigger 워크플로우가 필요한 것입니다. 이것은 지난주에 제가 작성했던 것과 같은 "조용한 실패(quiet fail)" 모니터링입니다. 녹색 HTTP 응답이 곧 녹색 결과(green outcomes)를 의미하지 않습니다.
두 가지 요소가 결합되어야 합니다: 빠른 확인 응답 + event_id 기반 중복 제거(dedupe) + 독립적인 오류 보고 기능. 이 중 어느 하나라도 놓치면, 실패 지점을 눈에 띄지 않는 다른 곳으로 옮긴 것과 같습니다.
현재 모든 새로운 웹훅에 적용하는 체크리스트
n8n에서 웹훅 수신기를 배포하기 전에 제가 확인하는 사항들:
- 응답이 1초 이내로 나갈 것 (트리거 직후 응답(Respond to Webhook) 처리).
- 사이드 이펙트가 발생하기 전에 발신자의 event_id로 중복 제거(dedupe)할 것.
- 실제 워크플로우에 연결된 Error Trigger 워크플로우를 통해 저에게 직접 보고되도록 할 것.
- "수신되었으나 완료되지 않은" 이벤트를 목록화하는 조정(reconciliation) 쿼리 (내 스위퍼가 이를 포착함).
- 응답 본문에 event_id를 포함하여 발신자와 제가 상관관계를 파악할 수 있도록 할 것.
지루하지만, 효과적입니다. 발신자들은 재시도를 멈췄고, 제 실행 기록은 더 이상 파트너들에게 거짓말을 하지 않습니다.
저는 AI Automation Lab — 프로덕션 환경에서 사용할 수 있는 n8n 워크플로우 템플릿을 구축하고 있습니다. 무료 AI Hook Generator는 제가 실제 워크플로우를 어떻게 구성하는지 보여주며, 제 오픈 소스 템플릿은 github.com/zhp910318/n8n-ai-automation-lab에서 확인할 수 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기