AI 음성 에이전트는 단 두 개의 웹훅(Webhook)일 뿐입니다: Retell을 n8n에 연결하여 프로덕션 환경 구축하기
요약
Retell AI 음성 에이전트를 n8n 웹훅과 연결하여 실무 수준의 프로덕션 환경을 구축하는 방법을 설명합니다. 단순한 프롬프트 설정을 넘어 캘린더 조회, CRM 연동 등 복잡한 로직을 웹훅 기반의 워크플로우로 처리하는 가이드를 제공합니다.
핵심 포인트
- Retell과 n8n을 웹훅으로 연결하여 실질적인 비즈니스 로직 구현 가능
- 통화 중 사용자 정의 기능(Custom Function)을 통한 실시간 데이터 조회 및 응답
- 지연 시간(Latency) 최소화를 위해 워크플로우를 가볍게 유지하는 설계 중요
- 이벤트 타입(call_started, call_ended 등)에 따른 분기 처리 권장
전화를 받는 음성 에이전트를 만드는 것은 쉬운 20%에 불과합니다. 흥미로운 나머지 80%는 통화가 트리거해야 하는 모든 것입니다: 캘린더 확인, CRM 연락처 작성, 확인 문자 전송, 잠재 고객(hot lead) 태깅 등 말이죠. 첫 음성 에이전트를 구축하는 새로운 개발자들은 거의 항상 그 로직을 음성 프롬프트(voice prompt)에 억지로 집어넣으려 시도하며, 이는 데모에서는 작동하지만 프로덕션(production) 환경에서는 무너집니다.
저는 미국 클라이언트들을 위해 AI 음성 에이전트를 구축하고 있는데, 다른 개발자들로부터 가장 많이 받는 질문은
- Webhook (웹훅) 노드를 추가합니다. 메서드는 POST로 설정하고, 생성된 프로덕션(production) URL을 복사합니다. 만약 셀프 호스팅(self-host)을 사용 중이라면, Retell의 서버가 해당 URL로 직접 접속해야 하므로 URL은 반드시 공개적으로 접근 가능해야 합니다. 즉,
localhost는 작동하지 않습니다. 로컬 개발 시에는 ngrok으로 터널링을 사용하며, 실제 서비스에 배포하기 전에 URL을 실제 URL로 교체합니다. - Retell이 깔끔한
200응답을 받을 수 있도록 Respond to Webhook (웹훅에 응답) 노드를 추가합니다. 통화 중 기능(mid-call functions)의 경우, 이 응답이 곧 에이전트가 다시 말하게 될 답변이 되므로 생각보다 훨씬 중요합니다.
Retell을 해당 URL로 지정합니다. 에이전트 설정에서 웹훅 URL을 n8n 프로덕션 URL로 설정합니다. 이제 Retell은 각 이벤트 발생 시 JSON 바디를 POST로 전송합니다. 페이로드(payload)에는 event 필드(call_started, call_ended, call_analyzed)와 전사(transcript), 메타데이터(metadata), 그리고 전달한 모든 동적 변수(dynamic variables)가 포함된 전체 call 객체가 담겨 있습니다.
유용한 습관 하나: 웹훅 바로 다음에 {{ $json.event }}를 기준으로 분기하는 Switch (스위치) 노드를 배치하는 것입니다. 각 이벤트는 서로 다른 처리가 필요합니다. call_ended는 보통 연락처와 전사 내용을 CRM에 기록하는 단계로 사용합니다. call_analyzed는 Retell의 통화 후 분석 결과(감정(sentiment), 예약 여부, 사용자 정의 추출 필드 등)를 읽고 그에 따라 경로를 지정(route)하는 단계로 사용합니다.
통화 중 사용자 정의 기능 (The mid-call custom function). 이 부분이 강력한 핵심입니다. 에이전트 내부에서 check_availability나 book_appointment와 같은 **사용자 정의 기능 (custom function)**을 정의합니다. 발신자가 화요일 시간대를 물어보면, 에이전트는 해당 기능을 호출하고, 이는 대화 도중에 n8n 웹훅을 호출하여 대기한 뒤 그 결과를 다시 말하게 됩니다:
발신자: "화요일 오후에 가능한 시간 있나요?"
-> Retell이 사용자 정의 기능 check_availability 실행 -> n8n 웹훅 호출
-> n8n이 Google Calendar / GHL 캘린더를 조회
...
여기서 당신을 괴롭힐 제약 사항은 바로 **지연 시간 (latency)**입니다. n8n이 실행되는 동안 발신자는 침묵 속에 기다리게 됩니다. 통화 중 워크플로우 (mid-call workflows)는 최대한 가볍게 유지하세요. 단 한 번의 조회, 빠른 응답, 그리고 4초를 잡아먹는 연쇄적인 API 호출 (chained API calls)을 피해야 합니다. 만약 특정 단계가 진정으로 느리다면, 에이전트가 "잠시만 기다려 주세요"라고 말하도록 설정하고 그에 맞춰 설계하세요. 확인 SMS를 보내거나 CRM 노트를 작성하는 것과 같이 비차단형 (non-blocking) 작업은 통화 중 경로에 포함되어서는 안 됩니다. 이를 call_ended 브랜치로 옮기세요.
방향 2: n8n에서 Retell로 (아웃바운드 콜)
반대 방향은 Retell의 REST API를 사용합니다. 가장 일반적인 용도는 아웃바운드 콜 (outbound call)을 거는 것이며, 이는 모든 부재중 전화 콜백 (missed-call callback) 시스템의 중추 역할을 합니다.
n8n에서 HTTP Request 노드를 사용하여 다음과 같이 수행합니다:
- Retell의
create-phone-call엔드포인트로 POST 요청을 보냅니다. - Retell API 키가 포함된 인증 헤더 (Authorization header)를 설정합니다. API 키는 n8n **자격 증명 (credentials)**에 저장해야 하며, 노드 내에 직접 입력해서는 안 됩니다. 이는 공유된 워크플로우에서 제가 가장 흔히 보는 실수로, 내보낸 JSON 파일 안에 API 키가 평문으로 남아 있는 경우입니다.
- 바디 (Body):
from_number(등록된 Twilio 번호),to_number(잠재 고객),agent_id, 그리고 에이전트가 전화를 걸기 전에 알아야 할 정보(발신자 이름, 문의 내용, 업체명 등)를 담은retell_llm_dynamic_variables객체를 포함합니다.
이러한 동적 변수 (dynamic variables)를 사용하면 에이전트를 수정하지 않고도 통화를 개인화할 수 있습니다. 프롬프트가 {{customer_name}}을 참조하면, n8n이 통화 시점에 이를 채워 넣습니다. 하나의 에이전트로 수많은 연락처에 무한히 재사용할 수 있는 것입니다. 코드에서 이메일 템플릿을 만들어 본 적이 있다면, 전화 통화에 적용하는 방식도 동일한 개념입니다.
전체 루프, 엔드 투 엔드 (end to end)
실제 부재중 전화 콜백에서 이 두 방향이 결합되는 방식은 다음과 같습니다:
GHL 부재중 전화 -> GHL 웹훅 (webhook) -> n8n
-> n8n HTTP Request -> Retell create-phone-call (잠재 고객 이름 + 컨텍스트)
-> Retell이 잠재 고객에게 전화를 걸고, 에이전트가 대화함
...
해당 다이어그램의 모든 화살표는 위에 설명한 두 가지 웹훅 방향 중 하나입니다. 특별한 것은 없습니다. 단지 각 로직이 어디에 위치해야 하는지에 대한 규율(discipline)의 문제입니다.
제가 실제로 겪었던 프로덕션 환경의 주의사항 (gotchas)
- A2P 10DLC 우선 적용. 만약 워크플로우가 Twilio를 통해 SMS를 전송한다면, 등록되지 않은 트래픽은 조용히 필터링됩니다. 테스트를 시작하기 전에 브랜드와 캠페인을 먼저 등록하세요. 그렇지 않으면 도착하지도 않을 문자를 디버깅하느라 하루를 허비하게 될 것입니다.
call_ended에 대한 멱등성 (Idempotency). 드문 경우지만 Retell이 이벤트를 한 번 이상 전달할 수 있습니다. 중복 이벤트가 중복 연락처를 생성하지 않도록call_id를 사용하여 CRM 쓰기 작업을 보호하세요. 모든 큐(queue)와 마찬가지로 웹훅을 '최소 한 번 전달 (at-least-once)' 방식으로 취급해야 합니다.- 웹훅 보안 강화. Retell의 서명(signature)을 검증하거나, 최소한 헤더에 공유 비밀키(shared secret)를 넣어 무작위 POST 요청이 워크플로우를 트리거하지 못하게 하세요. 외부 전화를 거는 개방된 n8n 웹훅은 가상의 위험이 아니라 실제적인 책임(liability) 소재가 됩니다.
- 테스트 및 프로덕션 웹훅 분리. n8n은 테스트 URL과 프로덕션 URL을 제공하며, 이 둘은 동일한 엔드포인트가 아닙니다. 저는 사람들이 테스트 URL을 라이브 에이전트에 연결해 두고, 에디터를 닫은 후 왜 아무것도 작동하지 않는지 의아해하는 모습을 본 적이 있습니다.
- Raw 페이로드 (raw payload) 로깅. 빌드 초기 단계에서 저는 모든 수신 Retell 페이로드를 시트(Sheet)나 데이터베이스 노드에 기록합니다. 구축 2주 차에 무언가 제대로 작동하지 않을 때, 원시 이벤트 기록(raw event history)은 에이전트가 실제로 무엇을 보냈는지, 그리고 제가 무엇을 보냈다고 가정했는지를 확인하는 가장 빠른 방법입니다.
앞으로의 방향
두 가지 방향이 견고해지면, 나머지는 단순히 노드를 추가하는 작업일 뿐입니다. CRM을 교체하거나, 통화자가 화가 난 것처럼 들릴 때 Slack 알림을 보내거나, 통화 후 분석(post-call analysis) 결과에 따라 잠재 고객(hot leads) 태그를 다는 식입니다. 음성 에이전트는 단순하고 멍청한 상태를 유지하고, 지능은 n8n에 머물게 됩니다. 그러면 프롬프트(prompt)를 전혀 건드리지 않고도 지능을 확인하고, 테스트하고, 변경할 수 있습니다. 이러한 분리가 핵심 기술이며, 고객이 다른 음성 플랫폼을 시도하고 싶어 한다고 해서 제가 고객의 시스템을 처음부터 다시 구축할 필요가 없었던 이유입니다.
이 스택의 나머지 부분, 플랫폼 선택, 테스트 파이프라인, 그리고 이러한 에이전트의 운영 비용에 대해서는 제 블로그에 정리해 두었습니다. 만약 여러분이 첫 번째 에이전트를 연결하고 있다면, 두 개의 화살표(두 개의 웹훅)부터 시작하세요. 그리고 프롬프트(Prompt) 내부에서 지나치게 영리하게 동작하도록 만들려는 유혹을 뿌리치십시오.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기