API는 웹 대시보드와 같은 에이전트, 통화, 크레딧을 다룹니다. 대시보드에서 만든 것은 모두 내 시스템에서 제어할 수 있고, 그 반대도 마찬가지입니다.
| 내용 | 엔드포인트 |
|---|---|
| 에이전트 목록 | GET /api/v1/voice/agents |
| 에이전트 만들기 | POST /api/v1/voice/agents |
| 에이전트 조회 / 수정 | GET · PATCH /api/v1/voice/agents/{id} |
| 발신 통화 걸기 | POST /api/v1/voice/calls |
| 통화 목록 | GET /api/v1/voice/calls |
| 통화 조회(대화 기록) | GET /api/v1/voice/calls/{id} |
| 통화 삭제 | DELETE /api/v1/voice/calls/{id} |
| 발신자의 통화 삭제 | POST /api/v1/voice/calls/delete-by-caller |
| 캠페인 | GET · POST /api/v1/voice/campaigns |
| 캠페인 조회 | GET /api/v1/voice/campaigns/{id} |
| 시작 / 일시 중지 / 취소 | POST /api/v1/voice/campaigns/{id}/{action} |
| 복제한 음성 | GET · POST /api/v1/voice/voices |
| 복제한 음성 삭제 | DELETE /api/v1/voice/voices/{id} |
| 수신 거부 목록 | GET · POST /api/v1/voice/do-not-call |
| 목록에서 삭제 | DELETE /api/v1/voice/do-not-call/{phone} |
| 언어, 음성, 템플릿 | GET /api/v1/voice/catalog |
| 에이전트 테스트 | GET · POST /api/v1/voice/agents/{id}/tests |
| 테스트 실행 / 실행 결과 조회 | POST /api/v1/voice/agents/{id}/test-runs · GET /api/v1/voice/test-runs/{id} |
| 번호를 에이전트에 연결 | PATCH /api/v1/voice/phone-numbers/{id} |
| 단어 목록 | GET · POST /api/v1/voice/vocabularies |
대시보드의 연동 → API 키에서 키를 만들고(개요 페이지에서 한 번에 만들 수도 있습니다) Bearer 토큰으로 보내세요.
Authorization: Bearer sk-your-key
키는 소유자가 일하는 워크스페이스에서 그 사람의 역할 권한으로 동작합니다. 읽기 전용 멤버의 키는 읽을 수는 있지만 변경할 수는 없습니다.
템플릿으로 에이전트를 만들고, 같은 요청에서 필드를 설정할 수도 있습니다. template 없이 만들면 빈 상태로 시작합니다. GET /api/v1/voice/catalog 는 템플릿과 에이전트에 쓸 수 있는 언어와 음성을 알려 줍니다. 템플릿은 영어와 중국어로만 작성되어 있으므로 다른 언어라면 system_prompt 와 first_message 를 그 언어로 보내세요. 요청 전체가 저장되거나 에이전트가 아예 만들어지지 않으므로, 거부된 요청은 고친 뒤 그대로 다시 보내면 됩니다. 응답의 dashboard_url 는 대시보드의 에이전트 페이지로, 브라우저에서 초안과 바로 통화할 수 있습니다. 모든 에이전트는 EvaSay ASR, EvaSay LLM, EvaSay TTS로 동작하며, 통화마다 언어와 부하에 맞게 EvaSay가 선택하고 통화 요금에 포함됩니다. 다른 서비스의 계정을 설정할 필요가 없습니다.
curl https://evasay.com/api/v1/voice/agents \
-X POST \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{
"name": "Front desk",
"language": "en",
"first_message": "Hi, thanks for calling Sunrise Salon. How can I help?",
"system_prompt": "You are the front desk of Sunrise Salon..."
}'
다음 필드는 PATCH /api/v1/voice/agents/{id}로 수정할 수 있습니다.
| 필드 | 설명 |
|---|---|
name, first_message, system_prompt | 텍스트 |
status | published는 초안을 새 버전으로 게시하고, draft는 통화 수신을 멈춥니다 |
language | 27개 언어 중 하나(예: en, zh, es, de) 또는 발신자를 따르는 multi |
temperature | 0-100 |
max_duration_sec, silence_timeout_sec | 통화 제한 |
interruptible, end_call_enabled | 불리언 |
confirm_details | 불리언. 번호와 이름을 다시 읽어 주고 확인받습니다 |
ai_disclosure | 불리언, 기본값 true. 첫 메시지 전에 통화 언어로 상대가 AI와 이야기하고 있다고 알립니다(첫 메시지에 이미 있으면 생략) |
voicemail_detection | 불리언. 발신 통화는 먼저 상대의 소리를 듣고, 음성사서함이면 종료합니다 |
voicemail_message | 음성사서함에 남길 메시지. {{variables}}를 쓸 수 있으며, 없으면 끊습니다 |
transfer_number | 연결할 번호(국제 형식) |
transfer_destinations | 추가 번호 최대 9개 [{name, number, when}]. 에이전트가 when에 따라 고릅니다 |
transfer_briefing | 불리언. 전화를 받은 사람이 먼저 에이전트의 통화 요약을 듣습니다 |
pronunciations | 최대 100개의 [{word, say, case_sensitive}]. 음성이 word를 say대로 읽습니다 |
idle_messages | 말이 없는 발신자에게 차례로 하는 알림 최대 3개. 마지막 뒤에 통화가 끝납니다 |
noise_reduction | 불리언. 발신자의 배경 소음을 걸러 낸 뒤 알아듣습니다 |
recording_enabled | 불리언. 통화를 2채널(왼쪽 발신자, 오른쪽 에이전트)로 녹음합니다 |
recording_consent | announce, ask(발신자가 동의할 때만 보관) 또는 none |
recording_notice | 먼저 말하는 안내 또는 질문. 비우면 통화 언어의 기본 문구 |
handoffs | 최대 5개의 [{agent_id, when}]: 이 에이전트가 통화를 넘길 수 있는 다른 에이전트 |
knowledge_base_id | 내 지식 베이스 중 하나 |
vocabulary_id | 내 어휘 중 하나. 발신자가 그 용어를 말하면 더 정확하게 알아듣습니다 |
voice_id | EvaSay 기본 음성 ID 또는 복제한 음성(clone:…) |
기본 음성은 언어마다 다릅니다. 중국어는 ming-designed-female-clear, ming-designed-female-warm(여성), ming-designed-male-clear, ming-designed-male-friendly(남성), 다른 언어는 F1–F5(여성)와 M1–M5(남성)입니다. 에이전트의 언어를 말하지 못하는 기본 음성은 그 언어를 말하는 같은 성별의 음성으로 바뀝니다. 예전 필드 이름 tts_voice_id도 계속 받습니다. 그 밖의 voice_id 값이나 아직 준비되지 않은 복제 음성은 거부됩니다.
analysis_config 는 통화가 끝난 뒤 무엇을 분석할지 정합니다. summary (기본 true), summaryLanguage, successCriteria (성공한 통화가 이루는 것), fields (추출할 최대 20개의 {name, type, description}, type 은 string, number, boolean 중 하나), scorecard (최대 10개의 {name, description, weight}, 가중치 1–5)입니다. 결과는 통화의 analysis 과 call.analyzed 웹훅에 들어갑니다.
에이전트 필드를 바꾸면 초안이 바뀝니다. POST /api/v1/voice/agents/{id}/publish(선택적으로 note 포함)로 초안을 게시하거나 같은 PATCH에 status: "published"를 넣기 전까지 통화는 게시된 버전을 계속 씁니다. 게시할 때마다 번호가 붙은 버전이 남고, 에이전트의 published_version이 통화에 쓰이는 버전을 알려 줍니다. 통화는 시작할 때 게시되어 있던 버전을 유지하므로 통화 중에 게시해도 바뀌지 않습니다. 대시보드의 테스트 통화는 초안을 씁니다.
GET /agents/{id}/versions는 버전 목록을 보여 주고, POST /agents/{id}/versions/{version}/restore는 되돌립니다. 새 통화는 그 버전을 쓰고 초안도 그 버전이 됩니다. 에이전트의 이름, 배포, 위젯 모양, 알림은 버전 관리되지 않고 즉시 바뀌며, 지식 베이스의 문서도 마찬가지입니다.
테스트는 통화 한 건을 텍스트로 재현합니다. 가상의 발신자가 scenario 을 연기하고, 에이전트는 초안(지시, 첫 메시지, 지식)으로 답하며, 심사자가 success_criteria 에 따라 대화를 판정합니다. 중요한 통화마다 테스트를 작성하고, 바꿀 때마다 실행하고, 모두 통과하면 게시하세요.
curl https://evasay.com/api/v1/voice/agents/AGENT_ID/tests \
-X POST \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{
"name": "Move a booking",
"scenario": "A regular customer wants to move Friday 3 pm to Saturday morning.",
"success_criteria": "Offers Saturday times and confirms the new time and the customer'"'"'s name.",
"max_turns": 8
}'
POST /agents/{id}/test-runs 는 에이전트의 모든 테스트 또는 test_ids 의 테스트를 실행하고, 테스트마다 실행 한 건(status: "running")을 돌려줍니다. status 가 passed, failed, error 중 하나가 될 때까지 GET /test-runs/{id} 을 조회하면, 실행에 transcript 과 심사자의 reason (영어, reason_language: "zh" 이면 중국어)이 담깁니다. 실행마다 Credits 가 들며 금액은 대시보드의 테스트 화면에 표시됩니다. 잔액으로 낼 수 없는 첫 실행에서 멈추고 나머지는 not_started 에 나옵니다. 실행되지 못한 건은 환불됩니다. 테스트는 에이전트가 하는 말을 확인할 뿐 목소리나 타이밍은 보지 않으며, 함수도 호출하지 않습니다. 함수는 POST /tools/{id}/test 로 미리 확인하세요. 에이전트당 테스트는 최대 30개이며 PATCH 와 DELETE /agents/{id}/tests/{test_id} 로 수정하거나 삭제합니다.
전체 통화(type: "simulation", 기본값) 외에 답변 하나만 확인하는 테스트도 있습니다. type: "next_reply" 에는 그 답변 앞의 대화로 발신자의 말로 끝나는 chat_history ([{role, text}], role 은 caller 또는 agent)와 에이전트의 다음 답변을 판정할 success_criteria 를 지정합니다. type: "tool_call" 에는 chat_history 와 expected_tool {name, arguments, match} 를 지정합니다. 에이전트는 name (자신의 함수 또는 end_call, transfer_call, handoff_to_agent)을 arguments 와 함께 호출해야 하며, 값은 정확히(앞뒤 공백 제거, 대소문자 무시) 비교하고 match: "judge" 이면 의미로 비교합니다. 그 밖에 전달된 인수는 무시됩니다. 실행의 transcript 에는 에이전트가 호출한 함수가 tool_calls 에 담깁니다.
POST /agents/{id}/tests/generate 은 에이전트의 초안(지시, 지식, 함수)으로 테스트 초안을 만듭니다. count (1–10, 기본 5), types, focus (다룰 내용), language (이름과 기준의 언어, en 또는 zh)를 받으며, 실행 한 번과 같은 Credits 가 듭니다. 돌려받은 초안은 검토한 뒤 POST /agents/{id}/tests 로 보내고, save: true 를 주면 바로 저장됩니다.
hooks는 통화의 특정 시점에 동작합니다(최대 10개 규칙). on: "silence"(에이전트가 말을 마친 뒤 발신자가 seconds초(3–600) 동안 아무 말도 하지 않음, 다시 말할 때까지 한 번), on: "time_elapsed"(통화 시작 후 seconds초, 10–3600), on: "call_ending". 각 규칙은 {type: "say", text}, {type: "transfer", destination?}, {type: "end_call", text?}, {type: "tool", name, args?} 중 최대 3개를 do합니다. 통화 종료 시점의 규칙은 함수만 실행합니다. 규칙으로 끝난 통화의 ended_reason은 rule_ended입니다.
전화 통화에서는 발신자가 누른 키가 답변으로 에이전트에게 전달됩니다. 입력은 # 또는 2초 멈춤으로 끝납니다. 발신 통화에서는 에이전트가 키를 눌러 회사의 자동 응답 메뉴를 통과할 수도 있습니다.
transfer_number는 발신자를 사람에게 넘길 때 쓰는 에이전트의 기본 번호이자, 워크스페이스의 크레딧이 떨어졌을 때 울리는 번호입니다. transfer_destinations는 이름이 붙은 번호를 추가하고, 각각에 언제 쓸지 설명을 달면("결제: 청구서와 환불 질문") 에이전트가 고릅니다. transfer_briefing을 켜면 에이전트가 연결하면서 짧은 요약을 쓰고, 전화를 받은 사람은 발신자가 연결되기 전에 EvaSay 음성과 에이전트의 요약 언어로 그것을 듣습니다. 이때 통화의 ended_reason은 transferred입니다.
handoffs를 쓰면 안내 데스크가 예약을 예약 에이전트에게 넘기듯, 에이전트가 워크스페이스의 게시된 다른 에이전트에게 통화를 넘길 수 있습니다. 에이전트는 when으로 고릅니다. 다른 에이전트는 지금까지의 대화를 모두 받아 같은 통화를 이어 가며, 자신의 지시문, 음성, 지식, 도구, 넘기기 설정으로 말합니다. 통화는 첫 번째 에이전트 아래에 표시되고 분석되고 과금되며, 대화 기록에 넘긴 지점이 표시됩니다.
번호는 대시보드에서 직접 보유한 통신사 계정으로 연결합니다. GET /api/v1/voice/phone-numbers 으로 목록을 보고, PATCH /api/v1/voice/phone-numbers/{id} 에 { "agent_id": "AGENT_ID" } 를 보내면 그 번호의 통화가 에이전트(게시된 버전)로 가며, "agent_id": null 이면 어느 에이전트로도 가지 않습니다. label 로 이름을 바꿉니다.
단어 목록에는 발신자가 말하지만 음성 인식이 잘못 알아듣기 쉬운 이름과 용어(제품명, 약 이름, 거리 이름 등)를 넣습니다. POST /api/v1/voice/vocabularies 에 { "name": "Products", "terms": ["Lumora", "Vantix Pro"] } 를 보내고(50자 이내 용어 최대 1,000개, 또는 "preset": "medical" 나 "legal" 에서 시작), 에이전트의 vocabulary_id 를 설정합니다. GET, PATCH (name, terms), DELETE /vocabularies/{id} 를 쓸 수 있으며, 에이전트가 사용 중인 목록은 삭제할 수 없습니다.
각 번호에는 전화번호 페이지에 통화 규칙이 있으며, 에이전트가 응답하기 전에 적용됩니다. 차단한 번호나 번호 앞부분(+1555), 번호를 숨긴 발신자는 응답 없이 거절되고 비용이 들지 않습니다. 지정한 발신자는 다른 번호로 바로 연결할 수 있습니다. 에이전트가 응답할 수 없을 때(게시되지 않음, Worker 없음, 연결 번호 없이 크레딧 부족)는 번호로 연결하거나 직접 정한 메시지를 들려줄 수 있습니다. 시작 조회(run_at_start)도 {"reject": true, "message": "..."}로 답해 발신자를 돌려보낼 수 있습니다. 에이전트는 메시지가 있으면 말하고 끊으며, ended_reason은 rejected입니다.
워크스페이스의 전화번호 중 하나로 한 통을 겁니다. 모든 요청에는 고유한 Idempotency-Key가 필요합니다. 같은 키를 다시 보내면 두 번 걸지 않고 같은 통화를 돌려줍니다.
curl https://evasay.com/api/v1/voice/calls \
-X POST \
-H "Authorization: Bearer sk-your-key" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "AGENT_ID",
"to": "+14155550100",
"variables": { "customer_name": "Alex", "order_id": "1234" }
}'
워크스페이스에 전화번호가 둘 이상이면 from이 필요합니다. variables는 에이전트의 첫 메시지와 프롬프트에 있는 {{customer_name}} 같은 자리 표시자를 채웁니다.
GET /api/v1/voice/calls/{id}는 통화의 상태, 통화 시간, 차감된 크레딧, 요약, 분석, 대화 기록을 돌려줍니다. 통화 목록은 GET /api/v1/voice/calls?agent_id=&status=&channel=&user_id=&page=로 가져옵니다(user_id: 웹 통화 토큰에서 온, 그 사람에 대한 내 쪽 ID). turns는 통화의 턴별 디버그 로그로, 발신자가 말을 마친 때부터 답을 듣기까지 시간이 어디에 쓰였는지 밀리초로 보여 줍니다. waitMs(발화 끝), sttMs(인식), llmMs(모델 첫 단어, 함수 호출마다 하나씩 추가), ttsMs(음성 첫 소리), firstAudioMs(답을 들은 시점)와 함께 호출된 함수, 끼어들기, 오류가 들어 있습니다. 통화 페이지에도 표시됩니다.
recording_enabled를 켜면 각 통화가 2채널 WAV로 녹음됩니다. 왼쪽은 발신자, 오른쪽은 에이전트이고, 샘플링 레이트는 통화와 같습니다. recording_consent: "announce"이면 에이전트가 먼저 녹음한다고 알리고, "ask"이면 물어보고 발신자가 동의할 때만 녹음을 보관합니다(답이 없으면 거부로 봅니다). "none"이면 아무 말도 하지 않습니다. 나와 발신자가 있는 곳의 법이 무엇을 요구하는지 확인하세요.
녹음은 EvaSay 저장소 또는 연동 → Webhook 및 알림 → 녹음 저장소에서 설정한 자체 S3 호환 버킷(AWS S3, Cloudflare R2, Google Cloud Storage, Supabase, MinIO)에 보관됩니다. 통화를 처리하는 Worker가 파일을 버킷에 직접 올립니다. 녹음이 저장되면 GET /api/v1/voice/calls/{id}가 1시간 동안 유효한 recordingUrl과 recording.status(recording, stored, declined 또는 failed)를 돌려줍니다.
DELETE /api/v1/voice/calls/{id}는 종료된 통화를 녹취록, 녹음(EvaSay 저장소 또는 고객 버킷), 분석, Webhook 전송 기록과 함께 삭제합니다. 사용한 Credits는 크레딧 내역에 남습니다. 아직 삭제할 수 없는 통화는 409(code는 call_not_deletable_yet)를 반환합니다. 통화가 진행 중이거나 방금 끝났거나, 정산 중이거나, 녹음이 업로드 중이거나, 잔액으로 결제하지 못한 Credits가 있는 경우입니다(먼저 Credits를 충전하세요).
본인이 데이터 삭제를 요청하면 POST /api/v1/voice/calls/delete-by-caller를 사용합니다. { "phone_number": "+14155550123" }를 보내면 이 번호의 모든 통화(수신은 발신 번호, 발신은 수신 번호 기준)가, { "user_id": "..." }를 보내면 토큰에 이 user_id가 있는 모든 웹 통화가 삭제됩니다. 응답은 { "deleted": 12, "kept_back": { "settling": 1 }, "more": false }입니다. 요청당 최대 200건이므로 more가 true인 동안 다시 호출하고, kept_back의 통화는 나중에 다시 삭제하세요. 번호가 수신 거부 목록에 있으면 목록에는 남습니다. 대시보드의 통화 → 발신자 데이터 삭제에서도 같은 작업을 할 수 있습니다.
통화가 진행되는 동안 POST /api/v1/voice/calls/{id}/control을 보내세요. 통화를 처리하는 Worker가 약 2초 안에 명령을 받고, API는 202로 답합니다. 통화를 처리하는 Worker에 연결할 수 없으면 API는 503(code call_unreachable)으로 답하고 명령은 실행되지 않습니다.
type | 동작 |
|---|---|
say | 에이전트가 지금 문장 뒤에 text를 말합니다. "interrupt": true면 바로 말합니다 |
note | 에이전트에게 text를 알려 주고(말하지 않음) 다음 답변부터 반영합니다. 대화 기록에 표시됩니다 |
end | text가 있으면 에이전트가 말한 뒤 끊습니다. text 없는 전화 통화는 바로 끊습니다 |
이때 통화의 ended_reason은 api_ended입니다. 대시보드의 통화 페이지에도 같은 제어가 있습니다.
POST /api/v1/voice/calls/{id}/listen(선택적으로 { "channels": 2 })은 2분 안에 열 수 있는 listen_url을 돌려줍니다. 이것은 WebSocket으로, 먼저 JSON 메시지 {"type":"start","sampleRate":8000,"channels":1,"encoding":"pcm_s16le"}가 오고, 이어서 발신자가 듣는 소리(두 목소리를 섞은 것, channels: 2면 왼쪽 발신자와 오른쪽 에이전트)가 16비트 리틀 엔디언 PCM 바이너리 프레임으로 옵니다. 통화가 끝나면 닫힙니다. 음성은 EvaSay의 컨트롤 플레인을 거치지 않고 통화를 처리하는 Worker에서 바로 옵니다. 대시보드 통화 페이지의 듣기 버튼도 같은 주소를 씁니다.
내 전화 시스템이나 앱 같은 자체 프로그램에서 통화 음성을 보내고 받습니다. 통화를 시작하세요.
curl https://evasay.com/api/v1/voice/calls/websocket \
-X POST \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{ "agent_id": "AGENT_ID", "encoding": "pcm_s16le", "sample_rate": 16000 }'
응답의 websocket_url에는 일회용 토큰이 들어 있으며 15분 안에 열 수 있습니다. encoding은 pcm_s16le(sample_rate 8000, 16000 또는 24000의 16비트 리틀 엔디언 PCM) 또는 mulaw(8 kHz μ-law)입니다. 음성은 그 형식의 바이너리 프레임으로 양방향으로 오가며, 프레임당 20 ms가 이상적입니다. 텍스트 프레임은 JSON입니다.
| 방향 | 메시지 |
|---|---|
| 나 → EvaSay | {"type":"dtmf","digits":"12#"} 키 입력. {"type":"end"} 끊기. {"type":"text","text":"…"} 발신자가 입력한 말(버튼으로 한 답은 "input":"button"). {"type":"context","text":"…","key":"cart"} 앱이 알리는 상황(에이전트는 답하지 않음). {"type":"tool_result","id":"…","result":{…}}(또는 "error") 페이지 함수의 답 |
| EvaSay → 나 | start(callId 포함). clear(발신자가 끼어듦: 아직 재생하지 않은 에이전트 음성을 버림). transcript(role, text, final, 입력한 말이면 input). user_started_speaking, user_stopped_speaking, agent_started_speaking, agent_stopped_speaking. cards(cards, quick_replies). tool(id, name, arguments: 페이지 함수 실행). notice(code: 입력한 말의 too_fast 또는 limit). ended(reason 포함, 그 뒤 소켓이 닫힘) |
연결하자마자 에이전트가 인사합니다. 요청에 user_id와 page_tools(앱이 답하는 페이지 함수, 아래 참고)를 추가할 수 있습니다. 통화는 다른 통화처럼 분 단위로 과금되며 통화 기록, 분석, Webhook도 같습니다.
웹 통화(위젯, 호스팅된 페이지, 내 앱)에서는 발신자에게 음성 옆에 채팅이 보이므로 에이전트가 말하는 것 이상을 할 수 있습니다. 전화 발신자에게는 해당되지 않습니다.
에이전트 → 지식 및 도구 → 발신자에게 보내기(에이전트 API에서는 send_to_caller)를 켜면 에이전트가 발신자 화면에 한 번에 최대 5장의 카드를 띄울 수 있습니다. 카드마다 제목, 그 아래 한 줄, 설명, 그림, 링크, 최대 2개의 답변 버튼을 넣을 수 있고, 질문 아래 최대 3개의 빠른 답변을 제시할 수도 있습니다. 링크는 소리 내어 읽지 않습니다.
에이전트는 그 통화에서 받은 링크와 그림만 보낼 수 있습니다. 지시문과 인사말, 지식 베이스(웹 페이지에서 읽은 문서에는 그 주소가 붙음), 함수의 답, 미리 만든 카드입니다. 링크는 allowed_hosts 중 하나에도 있어야 합니다(미리 만든 카드 자체의 사이트는 항상 허용). 에이전트가 지어낸 링크나 발신자가 읽어 준 링크는 보내지 않습니다. 에이전트의 말 속에 있는 그 사이트들의 링크는 채팅에서 클릭할 수 있습니다.
"send_to_caller": {
"enabled": true,
"allowed_hosts": ["shop.example.com", "*.cdn.example.com"],
"presets": [
{ "name": "hours", "when": "asked when we open", "title": "Opening hours",
"description": "Mon–Sat 9:00–18:00", "url": "https://shop.example.com/stores", "image_url": "" }
]
}
보낼 때마다 call.message_sent Webhook(channel, title과 url이 있는 items, quickReplies, blocked)이 전송됩니다. 한 통화에서 최대 10번 보낼 수 있습니다.
통화 중 발신자는 글자를 입력하거나(이메일 주소, 주문 번호) 답변 버튼을 탭할 수도 있습니다. 에이전트는 말한 것처럼 답하며, 말하는 중이었다면 먼저 멈춥니다. 메시지당 최대 500자, 초당 1개, 통화당 100개입니다. 통화 기록에서 이런 턴은 입력 또는 탭으로 표시됩니다.
위젯은 페이지에 evasay 객체를 제공합니다. 모두 선택 사항이며, 통화 프레임은 그 페이지가 위젯의 허용된 웹사이트(에이전트 → 배포)에 있을 때만 페이지의 메시지를 받습니다.
<script src="https://evasay.com/voice-widget.js" data-agent="AGENT_SLUG" async></script>
<script>
window.evasay = window.evasay || [];
evasay.push(function (agent) {
agent.setVariables({ plan: 'pro', page: location.pathname }); // 다음 통화용
agent.update('Cart: 2 items, $120', { key: 'cart' }); // 통화 중
agent.registerTool('add_to_cart', async function (args) {
// 내 코드, 방문자의 브라우저에서 실행
return { ok: true, checkout: 'https://shop.example.com/checkout' };
});
agent.on('cards', function (cards) {}); // 그 밖에 'live', 'ended'
});
</script>
setVariables 값은 지시문과 인사말의 {{name}}을 채웁니다. 방문자의 브라우저에서 오므로 에이전트에게 검증되지 않았다고 알려 주며, 함수나 시작 조회에는 절대 전달되지 않습니다.update는 페이지에서 일어나는 일을 에이전트에게 알리지만 답하게 하지는 않습니다. 같은 key의 새 업데이트가 이전 것을 대체합니다."kind": "page", url 없음)인 함수를 에이전트에 추가하고, 페이지에 같은 이름의 함수를 등록하세요. 에이전트는 그것을 등록한 페이지에서 온 통화에서만 호출할 수 있습니다. wait_for_result가 false가 아니면 timeout_sec(1–10, 기본 5)까지 답을 기다립니다. 방문자로 로그인한 상태에서 방문자의 클릭처럼 실행되므로, 비용이 들거나 되돌릴 수 없는 작업 전에는 내 페이지에서 확인을 받으세요. 답에 있는 링크는 카드로 보낼 수 있습니다.통화를 시작할 수 있는 사람을 토큰이 있는 사람만으로 설정하고, 서버에서 로그인한 사용자용 일회용 토큰을 받으세요.
curl https://evasay.com/api/v1/voice/web-calls \
-X POST \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{ "agent_id": "AGENT_ID", "user_id": "u_42", "variables": { "name": "Ann" }, "expires_in": 600 }'
응답에는 token(expires_in초 안에 한 통화, 30–3600, 기본 600), agent(슬러그), web_call_url이 있습니다. 위젯 스크립트에 data-token-url="/your/endpoint"를 붙여 토큰을 페이지에 넘기거나(방문자의 쿠키와 함께 그곳에 POST하고 { "token": "…" }를 읽음) agent.setToken(async () => token)을 쓰세요. 토큰의 variables는 검증된 값입니다. 지시문, 시작 조회, 함수 템플릿에서 쓸 수 있고 user_id도 마찬가지이며, user_id는 통화에 남습니다(통화의 userId, 통화 필터에는 user_id). API 키는 서버에 두세요.
내 앱에서는 { "token": "…", "pageTools": ["add_to_cart"] }를 https://evasay.com + web_call_url로 POST하세요. 응답은 통화 연결 정보(WebRTC offer 주소 또는 WebSocket 주소와 통화 토큰)이며, 웹, iOS, Android, React Native용 Pipecat 클라이언트 SDK 같은 RTVI 클라이언트에 씁니다. 카드, 페이지 함수, 알림은 RTVI server-message(evasay.cards, evasay.tool, evasay.notice)로 옵니다. 입력한 말은 send-text로, 페이지 보고와 답은 client-message의 evasay.context와 evasay.tool_result로 보내세요. 또는 WebSocket으로 음성 주고받기로 user_id와 page_tools를 넘기며 음성을 직접 다룰 수도 있습니다.
전화번호 → SIP에서 자체 PBX, SIP 전화기 또는 SIP 트렁크가 에이전트에게 직접 전화할 수 있습니다. 통화 요금은 SIP 제공업체가 청구합니다.
sip:ev…@<EvaSay의 SIP 도메인>. 수신 전용이며 PBX(3CX, FreePBX, Asterisk 등)와 SIP 전화기용입니다.from으로). 사람에게 연결할 때는 REFER로 트렁크에 넘깁니다. 트렁크가 자격 증명을 요구하면 EvaSay가 입력한 트렁크 사용자 이름과 비밀번호로 응답합니다.수신 전화는 EvaSay가 발급한 사용자 이름과 비밀번호로 다이제스트 인증합니다(realm은 SIP 도메인). 발신 IP를 제한할 수도 있고, 제공업체가 로그인할 수 없으면 IP만으로 받을 수도 있습니다(이때 IP는 필수). SIP 주소나 트렁크마다 동시 통화 제한이 있으며, 넘으면 486을 받습니다. 비밀번호는 만들거나 재설정할 때 한 번만 표시됩니다. UDP와 G.711(PCMU, PCMA)만 지원합니다. 키 입력은 RFC 4733 또는 SIP INFO. PBX 트렁크에서 등록(REGISTER)을 끄세요.
캠페인은 하나의 에이전트로 연락처 목록에 전화합니다. 통화 시간대와 동시 통화 제한(max_concurrency, 동시에 최대 100통, 워크스페이스의 동시 통화 제한을 넘지 않음) 안에서 걸고, 연결되지 않은 사람에게는 다시 겁니다.
curl https://evasay.com/api/v1/voice/campaigns \
-X POST \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{
"name": "Appointment reminders",
"agent_id": "AGENT_ID",
"phone_number_id": "PHONE_NUMBER_ID",
"timezone": "America/New_York",
"window_start": "09:00",
"window_end": "18:00",
"max_concurrency": 2,
"max_attempts": 2,
"retry_delay_min": 60,
"consent_confirmed": true,
"contacts": [
{ "phone": "+14155550100", "name": "Alex", "appointment": "Tuesday 3 pm" }
]
}'
연락처는 첫 열에 전화번호가 있는 CSV 텍스트일 수도 있습니다. consent_confirmed: true는 필수입니다. 이로써 목록의 모든 사람이 AI 음성을 포함한 전화를 받는 데 동의했음을 확인하며, EvaSay는 그 확인을 캠페인에 보관합니다(consent_confirmed_at). 캠페인은 초안으로 만들어집니다. POST /api/v1/voice/campaigns/{id}/start로 시작하고, pause와 cancel도 같은 방식으로 씁니다. 캠페인을 시작하기 전에 에이전트가 게시되어 있어야 합니다.
voicemail_detection이 켜져 있으면(기본값) 발신 통화는 에이전트가 인사하기 전에 상대가 말하기를 기다립니다. 상대가 말이 없으면 2~3초 뒤에 에이전트가 인사합니다. 음성사서함이나 통신사 녹음이 들리면 에이전트는 안내 멘트와 삐 소리가 끝나기를 기다렸다가 voicemail_message를 남기고, 없으면 끊습니다. 이런 통화는 ended_reason voicemail로 끝나고, 통화한 시간만큼 과금되며, 분석이나 알림은 하지 않습니다.
캠페인에서는 음성사서함으로 간 번호도 받지 않은 번호처럼 다시 걸고, 메시지는 마지막 시도에서만 남기므로 같은 메시지를 두 번 받는 사람은 없습니다.
캠페인이든 POST /api/v1/voice/calls든 모든 발신 통화는 먼저 워크스페이스의 수신 거부 목록을 확인합니다. 목록에 있는 번호로는 절대 걸지 않습니다. API는 code do_not_call과 함께 409로 답하고, 캠페인은 그 연락처를 outcome do_not_call인 canceled로 표시하고 다음으로 넘어갑니다.
에이전트가 건 통화에서 상대가 다시 전화하지 말라고 하면 에이전트가 직접 그 번호를 목록에 넣고 상대에게 알리며, call.do_not_call Webhook이 전송됩니다. 번호는 캠페인 → 수신 거부 목록 또는 API로도 추가하고 삭제할 수 있습니다.
curl https://evasay.com/api/v1/voice/do-not-call \
-X POST \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{ "phones": ["+14155550100"], "note": "Asked by email" }'
번호는 국제 형식이며 한 번에 최대 1,000개입니다. GET /api/v1/voice/do-not-call은 최신순으로 목록을 보여 주며(search, limit, page), 각각 source(call, dashboard 또는 api)가 있습니다. DELETE /api/v1/voice/do-not-call/%2B14155550100은 하나를 다시 빼지만, 그 사람이 전화 받기에 동의했을 때만 하세요.
POST /api/v1/voice/voices는 한 사람의 WAV 녹음으로 음성을 만듭니다(multipart audio, name, language, consent=true). 상태 running과 함께 202로 답합니다. ready가 될 때까지 GET /api/v1/voice/voices를 조회한 다음, 에이전트의 voice_id를 그 voice_id로 설정하세요. language는 녹음에서 쓴 언어로, 위의 에이전트 언어 중 하나입니다. 음성은 중국어를 제외한 모든 언어를 말하며, 중국어에서는 같은 성별의 기본 음성이 응답합니다. 녹음한 언어에서 화자와 가장 비슷하게 들립니다. 복제 음성마다 크레딧이 들며, 만들기 전에 대시보드에 표시됩니다.
연동 → Webhook 및 알림에서 https 주소를 추가하면 통화 이벤트를 받을 수 있습니다.
| 이벤트 | 시점 |
|---|---|
call.started | 에이전트가 전화를 받음 |
call.ended | 통화가 끝남(통화 시간과 크레딧 포함) |
call.analyzed | 요약, 결과, 추출한 필드, 대화 기록이 준비됨 |
call.transcript | 통화 중 몇 초마다 새 대화 기록 줄 |
call.tool_called | 에이전트가 내 함수를 호출함: name, arguments, status, durationMs |
call.handed_off | 통화가 다른 에이전트에게 넘어감: fromAgentId, toAgentId |
call.transferred | 통화가 사람에게 연결됨: destination |
call.voicemail | 발신 통화가 음성사서함으로 감 |
call.do_not_call | 발신 통화의 상대가 다시 전화하지 말라고 함: phone |
call.recording_ready | 2채널 녹음이 저장됨: recordingUrl(24시간 유효), durationMs |
call.message_sent | 에이전트가 웹이나 앱 발신자의 화면에 카드를 띄움: items, quickReplies |
alert.triggered | 경보(Webhook 및 알림 → 경보) 중 하나가 기준을 넘음 |
alert.resolved | 그 경보가 정상으로 돌아옴 |
통화 중에 보내는 이벤트에는 data.call(id, agentId, channel, direction, 번호)이 들어 있습니다. 각 요청은 서명됩니다.
EvaSay-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
EvaSay-Delivery: <delivery id>
Webhook을 만들 때 표시된 Webhook 시크릿으로 서명을 확인하고 2xx 상태로 답하세요. 실패한 전송은 1분, 5분, 30분, 2시간, 12시간 뒤에 같은 본문과 같은 EvaSay-Delivery ID로 재시도되므로 중복은 무시하면 됩니다. Webhook의 최근 전송(보낸 본문 그대로와 엔드포인트가 답한 상태)은 GET /webhooks/{id}/deliveries와 대시보드에서 볼 수 있고, POST /webhooks/{id}/deliveries/{delivery_id}로 하나를 다시 보낼 수 있습니다.
이메일과 alert.triggered / alert.resolved Webhook 외에도, 경보는 Webhook 및 알림 → 경보 → 추가 전송 대상에서 Slack 채널(Incoming Webhook), WeCom 그룹 로봇 또는 Telegram으로 보낼 수 있습니다.
오류는 하나의 형식을 씁니다.
{
"error": {
"type": "invalid_request_error",
"code": "invalid_agent_field",
"message": "status must be draft or published",
"param": "status"
}
}
| 상태 | 의미 |
|---|---|
| 400 | 입력이 잘못됨(code와 param이 어디인지 알려 줌) |
| 401 | API 키가 없거나 잘못됨 |
| 402 | 통화를 시작할 크레딧이 부족함 |
| 403 | 키가 읽기 전용이거나 에이전트를 쓸 수 없음 |
| 404 | 이 워크스페이스에 그런 에이전트, 통화, 캠페인이 없음 |
| 409 | 상태가 맞지 않음(예: 실행 중인 캠페인 시작), 제공업체 계정이 연결되지 않음, 또는 번호가 수신 거부 목록에 있음(do_not_call) |
| 429 | 요청이 너무 많음. 속도를 늦추고 다시 시도하세요 |
워크스페이스의 최근 30일 Voice API 요청(시간, 엔드포인트, 상태, 소요 시간, 키, 오류 코드, 본문 제외)은 API 키 → API 요청 로그에 표시됩니다.
읽기는 키당 분당 120개, 쓰기와 통화는 30개로 제한됩니다.
통화는 워크스페이스의 크레딧에서 분 단위로 과금되며, 대시보드와 같은 잔액을 씁니다. 현재 요금은 요금 페이지에 있습니다.
OpenAPI 계약으로 연동 클라이언트를 생성하세요. 공개 API, 대시보드, MCP 관리 엔드포인트는 같은 워크스페이스 리소스와 권한을 다룹니다.
| 기능 | 엔드포인트 |
|---|---|
| 재사용 가능한 도구 | GET / POST /api/v1/voice/tools |
| 도구 조회 / 수정 / 삭제 | GET / PATCH / DELETE /api/v1/voice/tools/{id} |
| 도구 미리보기 또는 테스트 | POST /api/v1/voice/tools/{id}/test |
| 에이전트에 도구 연결 | PUT /api/v1/voice/agents/{id}/tools |
| 지식 베이스 | GET / POST /api/v1/voice/knowledge-bases |
| 지식 베이스 조회 / 수정 / 삭제 | GET / PATCH / DELETE /api/v1/voice/knowledge-bases/{id} |
| 문서 추가 / 목록 | POST / GET /api/v1/voice/knowledge-bases/{id}/documents |
| 문서 조회 / 삭제 | GET / DELETE /api/v1/voice/knowledge-bases/{id}/documents/{doc_id} |
| 통화 이벤트 구독 | GET / POST /api/v1/voice/webhooks |
| 구독 조회 / 수정 / 삭제 | GET / PATCH / DELETE /api/v1/voice/webhooks/{id} |
| 서명된 테스트 이벤트 보내기 | POST /api/v1/voice/webhooks/{id}/test |
| 원격 MCP 도구 찾기 | POST /api/v1/voice/mcp/discover |
| MCP 관리 엔드포인트 | /api/v1/voice/mcp |
CRM 어댑터를 조회하는 HTTP 도구를 등록합니다.
{
"name": "lookup_customer",
"description": "Find a customer after confirming their customer ID.",
"kind": "http",
"url": "https://crm.example.com/customers/{{customer_id}}",
"method": "GET",
"read_only": true,
"credential_ref": "crm",
"input_schema": {
"type": "object",
"properties": { "customer_id": { "type": "string" } },
"required": ["customer_id"],
"additionalProperties": false
},
"response_path": "data"
}
POST /tools는 { "data": { "id": "TOOL_ID", ... } }를 돌려줍니다. PUT /agents/AGENT_ID/tools와 { "tool_ids": ["TOOL_ID"] }로 그 정의를 에이전트에 연결하세요. 이것은 도구 목록 전체를 스냅샷으로 바꿉니다. 라이브러리 도구가 바뀌면 다시 연결하세요. 라이브러리 정의를 삭제해도 기존 스냅샷은 남습니다. 에이전트 API의 tools 필드로 인라인 정의를 읽고 바꿀 수도 있습니다. 분석과 위젯은 에이전트 API의 analysis_config와 widget_config로 설정합니다.
도구는 GET, POST, PUT, PATCH, DELETE를 쓸 수 있습니다. query와 request_body는 { "customer": { "id": "{{customer_id}}" } } 같은 중첩 JSON 템플릿을 받습니다. 값 전체가 자리 표시자이면 원래 JSON 타입이 유지됩니다. URL 자리 표시자는 인코딩되며, 고정된 공개 HTTPS 호스트 뒤에만 올 수 있습니다. request_body가 없으면 GET이 아닌 도구는 { "arguments": {...}, "call": { "callId": "...", "agentId": "...", "toolCallId": "...", "idempotencyKey": "..." } }를 받습니다. response_path는 점 표기법으로 필드를 고릅니다. 단순한 parameters도 계속 받으며, input_schema는 중첩 객체, 배열, 열거형, 숫자 범위도 지원합니다. 외부 스키마 참조와 정규 표현식은 받지 않습니다.
static_args: 모델이 무엇을 쓰든 매번 보내는 값(예: 매장 ID). 주소와 본문의 {{name}}도 채웁니다.extract: [{name, path}]로 함수 답의 필드(data.id)를 보관해 이후 함수의 {{name}}에 씁니다. 조회로 얻은 customer_id를 모델이 되풀이하지 않아도 이어지는 주문에 넘길 수 있습니다. 시작 조회의 필드도 같은 방식으로 보관됩니다.reject_when: 최대 5개 규칙 {on: caller|agent, pattern, negate}. 마지막 말이 모두를 만족하는 동안에는 함수를 실행하지 않고 에이전트에게 reject_message를 줍니다.model_says: 호출할 때마다 모델이 한 문장을 쓰고, 함수가 실행되는 동안 그것을 말합니다. 고정된 speak_before 대신입니다.연동 → Webhook 및 알림 → 연결된 앱에서 앱을 연결한 다음, 에이전트의 지식 및 도구 탭에서 "준비된 함수 추가"를 쓰세요. 자체 엔드포인트가 필요 없습니다.
check_availability(날짜의 빈 시간)와 book_appointment(발신자가 고른 시간을 예약)를 추가합니다. Google 캘린더 시간은 설정한 시간대, 영업일, 영업시간, 예약 길이를 따릅니다. Cal.com은 이벤트 유형 자체의 가능 시간을 쓰며, 발신자가 이메일을 주지 않으면 설정한 대체 이메일을 씁니다.save_to_sheet를 추가해, 지정한 열에 통화 ID와 시간을 더해 한 행을 덧붙입니다.notify_team을 추가해, 에이전트가 쓴 메시지를 Slack 채널(Incoming Webhook)에 올립니다.이 함수들은 연결한 계정으로 EvaSay 컨트롤 플레인이 실행합니다. Worker도 에이전트 정의도 앱의 자격 증명을 갖지 않습니다. API로 에이전트를 읽으면 이것들은 kind: "app"(app, action, connection_id, app_config 포함)로 보입니다. tools를 바꿀 때 유지하려면 그대로 다시 보내세요. 앱 연결은 대시보드에서만 합니다.
에이전트 도구 중 하나에 run_at_start: true를 설정하면 각 통화가 시작될 때, 에이전트가 인사하거나 답하기 전에 호출됩니다. caller_number, called_number, direction, channel, call_id라는 이름의 매개변수에는 값이 채워집니다. 돌려받은 필드는 first_message, system_prompt, idle_messages의 {{variables}}를 채우고(한 단계 아래 필드는 {{customer.name}}), 답 전체가 배경 정보로 에이전트에게 전달됩니다. 변수가 있는 인사말은 답을 최대 3초 기다리고, 찾지 못한 것은 빈칸으로 둡니다. 시작 시 실행할 수 있는 도구는 에이전트당 하나입니다.
자격 증명은 워크스페이스와 대상별로 서버 환경에 설정됩니다. credential_ref 발급은 support@evasay.com에 요청하세요. API 키를 에이전트 정의, 헤더, 프롬프트, 도구 매개변수에 넣지 마세요. CRM 자격 증명은 어댑터 자체 환경에 둘 수 있습니다.
업무 시스템에 요청을 보내기 전에 도구를 미리 봅니다.
{
"arguments": { "customer_id": "customer-123" },
"dry_run": true,
"mock_result": { "name": "Alex", "order_status": "shipped" }
}
미리보기는 매개변수를 검증하고 변환된 요청을 보여 줍니다. 대화를 실행하거나 업무 시스템에 연결하지 않습니다. 실제로 실행하려면 dry_run=false로 설정하세요. 실제 쓰기 테스트에는 confirmed=true와 idempotency_key(8–128자의 영문자, 숫자, 점, 밑줄, 콜론, 하이픈)도 필요합니다. 테스트 응답에는 ok, status, duration_ms와 크기가 제한된 결과가 있으며, 자격 증명은 돌려주지 않습니다. 읽기와 쓰기 테스트에는 워크스페이스 쓰기 권한이 필요합니다.
통화 중 업무 요청에는 고정된 Idempotency-Key가 붙습니다. 업무 엔드포인트는 이를 지켜야 합니다. 한 통화 안의 같은 쓰기는 시간 초과로 결과를 알 수 없는 작업까지 포함해 중복 제거됩니다. 의도적으로 다른 작업을 하려면 operation_id 같은 다른 업무 인수가 필요합니다. EvaSay는 쓰기를 자동으로 다시 실행하지 않습니다. 도구 실행 이벤트에는 상태와 소요 시간이 있습니다. 대화 테스트 기능은 텍스트 시뮬레이션이며, 도구 실행 테스트가 아닙니다.
Streamable HTTP MCP 연결을 다음과 같이 설정하세요.
URL: https://evasay.com/api/v1/voice/mcp
Authorization: Bearer <your EvaSay API key>
예를 들어 Claude Code에서는:
claude mcp add --transport http evasay https://evasay.com/api/v1/voice/mcp --header "Authorization: Bearer $EVASAY_API_KEY"
연결하면 도구가 에이전트 만드는 순서를 알게 됩니다. 카탈로그를 읽고, 초안을 만들고, 지식·함수·단어 목록을 더하고, 테스트를 작성해 실행하고, 통과할 때까지 초안을 고치고, 에이전트의 dashboard_url 를 건네 직접 시험해 보게 하고, 게시와 번호 연결은 승인 후에만 합니다. 문서를 읽는 도구는 https://evasay.com/llms.txt 에서 시작할 수 있으며, 이 페이지와 OpenAPI 계약, MCP 주소로 연결됩니다.
클라이언트의 안전한 토큰 또는 환경 변수 설정을 쓰세요. 엔드포인트는 API 키 인증을 쓰며 OAuth 로그인 흐름이 아닙니다. 에이전트 생성/설정, 라이브러리 도구, 지식, Webhook, 통화 기록을 제공합니다. 게시와 실제 통화는 쓰기 작업이며, 발신 통화는 크레딧과 통신 요금을 씁니다. 도구를 호출할 때마다 워크스페이스 권한을 다시 확인합니다. 쓰기 작업에는 클라이언트의 확인 기능을 켜세요. 엔드포인트는 상태 없는 JSON 응답을 쓰며, 백그라운드 알림 스트림은 제공하지 않습니다.
코딩 도구에 줄 지시의 예: "EvaSay 초안 에이전트를 만들고, 내 CRM 조회 엔드포인트를 등록하고, 테스트 인수로 미리 본 뒤 에이전트에 연결하고, call.analyzed Webhook을 설정해 줘." 게시와 실제 통화는 검토한 뒤 명시적으로 허락하세요.
POST /mcp/discover는 { "url": "https://crm.example.com/mcp", "credential_ref": "crm" }를 받아 서버의 도구와 스키마를 돌려줍니다. 고른 도구만 등록하세요.
{
"name": "lookup_customer",
"description": "Find a CRM customer by confirmed ID.",
"kind": "mcp",
"url": "https://crm.example.com/mcp",
"mcp_tool": "get_customer",
"credential_ref": "crm",
"read_only": true,
"input_schema": {
"type": "object",
"properties": { "customer_id": { "type": "string" } },
"required": ["customer_id"]
}
}
에이전트는 등록한 도구만 쓸 수 있습니다. 원격 연결은 요청에 JSON 또는 SSE로 답하는 Streamable HTTP, 공개 HTTPS 대상, 미리 설정된 자격 증명을 지원합니다. 예전 SSE 엔드포인트, OAuth 협상, 서버가 시작하는 sampling/elicitation, 바이너리 첨부는 지원하지 않습니다. 찾기만으로 서버의 모든 도구에 접근 권한이 자동으로 주어지지는 않습니다. 원격 도구 스키마는 인라인이어야 하며 지원되는 JSON Schema 부분 집합을 써야 합니다.
POST /webhooks에 { "url": "https://crm.example.com/evasay-events", "events": ["call.analyzed"] }를 보내 Webhook을 만드세요. 응답에는 구독과 생성된 서명 시크릿이 들어 있으며, 시크릿은 만들 때만 돌려줍니다. EvaSay-Signature를 확인하고 EvaSay-Delivery로 중복을 제거한 뒤 CRM을 업데이트하세요. 이벤트 Webhook은 비동기 알림이며, 통화 중 도구는 결과를 에이전트에게 직접 돌려줍니다.