> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tryvox.co/llms.txt
> Use this file to discover all available pages before exploring further.

# 에이전트 단위 웹훅

> 에이전트별 통화 이벤트와 웹훅 페이로드 예시를 안내합니다.

## 개요

에이전트 단위 웹훅은 **에이전트별 통화 이벤트**를 외부 서버로 전송합니다.
적용 우선순위는 [웹훅 개요](/docs/operate/monitor/webhooks/overview)를 참고하세요.

<Info>
  이 문서는 v2를 기준으로 설명합니다.
  새 연동은 v2로 구현하세요.
  v2는 통화 종료와 분석 완료를 별도 이벤트로 전송합니다.
</Info>

<Note>
  v1 선택지는 기존 수신 서버를 그대로 유지하거나 v2로 전환할 때만 사용하세요.
  신규 구축 기준은 v2입니다.
  설정을 변경하지 않으면 기존 v1 연동은 기존 형식을 유지합니다.
</Note>

<img src="https://mintcdn.com/fleek/jXswPEP73poEBYQh/images/agent-webhook-settings.png?fit=max&auto=format&n=jXswPEP73poEBYQh&q=85&s=13e65ad1aa9216208738919c01598119" alt="에이전트 설정 화면의 웹훅 설정 섹션과 통화 데이터 웹훅 URL 입력 필드" width="1920" height="1080" data-path="images/agent-webhook-settings.png" />

## 지원되는 이벤트

아래 이벤트는 v2 기준입니다.

| 이벤트             | 설명                                            |
| --------------- | --------------------------------------------- |
| `call_started`  | 통화가 시작되면 전송됩니다.                               |
| `mid_call`      | 통화 중 플로우 전환이나 발화가 발생하면 전송됩니다.                 |
| `call_ended`    | 통화가 끝나면 전송됩니다. 기본 통화 정보, 스크립트, 녹음 URL을 포함합니다. |
| `call_analyzed` | 비용, 요약, 감정 분석, 통화 정보 추출 결과가 준비되면 전송됩니다.       |

<Warning>
  v2의 `call_ended`에는 `call_cost`와 `call_analysis`가 포함되지 않습니다.
  비용과 분석 결과가 필요하면 `call_analyzed`를 처리하세요.
</Warning>

<Accordion title="v1 기존 유지용 이벤트">
  v1은 `call_analyzed`를 사용하지 않습니다.
  비용과 분석 결과는 `call_ended`에 함께 포함됩니다.

  | 이벤트            | 설명                                   |
  | -------------- | ------------------------------------ |
  | `call_started` | 통화가 시작되면 전송됩니다.                      |
  | `mid_call`     | 통화 중 플로우 전환이나 발화가 발생하면 전송됩니다.        |
  | `call_ended`   | 통화 종료 정보, 스크립트, 비용, 분석 결과를 함께 포함합니다. |
</Accordion>

## 종료 이벤트 흐름

v2에서는 종료 사실과 분석 완료를 분리해서 받을 수 있습니다.

```mermaid theme={null}
sequenceDiagram
    participant V as vox.ai
    participant E as 외부 서버
    V->>E: call_started
    Note over V,E: 통화 진행
    V->>E: mid_call
    Note over V,E: 통화 종료
    V->>E: call_ended
    Note over V,E: 비용·분석 준비
    V->>E: call_analyzed
```

<Accordion title="v1 기존 유지용 흐름">
  ```mermaid theme={null}
  sequenceDiagram
      participant V as vox.ai
      participant E as 외부 서버
      V->>E: call_started
      Note over V,E: 통화 진행
      V->>E: mid_call
      Note over V,E: 통화 종료와 분석 완료
      V->>E: call_ended
  ```
</Accordion>

## 활용 사례

아래 표는 v2 기준입니다.

| 원하는 작업              | 처리할 이벤트         |
| ------------------- | --------------- |
| 통화 종료 상태 저장         | `call_ended`    |
| 스크립트와 녹음 URL 저장     | `call_ended`    |
| 비용 저장               | `call_analyzed` |
| 요약, 감정 분석, 추출 결과 저장 | `call_analyzed` |
| 플로우 전환이나 실시간 발화 기록  | `mid_call`      |

<Note>
  v1 기존 유지용 연동에서는 비용과 분석 결과도 `call_ended`에서 처리합니다.
</Note>

## 설정

<Steps>
  <Step title="웹훅 엔드포인트 구현">
    웹훅 요청을 수신할 HTTPS 엔드포인트를 준비하세요.

    <CodeGroup>
      ```python FastAPI theme={null}
      from fastapi import FastAPI, Request
      from fastapi.responses import JSONResponse

      app = FastAPI()

      @app.post("/webhook")
      async def handle_webhook(request: Request):
          data = await request.json()
          event = data.get("event")
          webhook_version = data.get("webhook_version")
          call = data.get("call", {})

          if webhook_version != "v2":
              return JSONResponse(status_code=400, content={"error": "unsupported version"})

          if event == "call_ended":
              print("통화 종료:", call.get("call_id"))
          elif event == "call_analyzed":
              print("분석 완료:", call.get("call_analysis"))
          else:
              print("이벤트:", event)

          return JSONResponse(status_code=200, content={"received": True})
      ```

      ```javascript Node.js theme={null}
      const express = require("express");
      const app = express();
      app.use(express.json());

      app.post("/webhook", (req, res) => {
        const { event, webhook_version, call = {} } = req.body;

        if (webhook_version !== "v2") {
          return res.status(400).send({ error: "unsupported version" });
        }

        if (event === "call_ended") {
          console.log("통화 종료:", call.call_id);
        } else if (event === "call_analyzed") {
          console.log("분석 완료:", call.call_analysis);
        } else {
          console.log("이벤트:", event);
        }

        res.status(200).send({ received: true });
      });

      app.listen(8080);
      ```
    </CodeGroup>
  </Step>

  <Step title="에이전트별 웹훅 등록">
    대시보드 **에이전트 설정 > 웹훅 설정** 섹션에서 `통화 데이터 웹훅 URL`을 입력하세요.
    새 연동은 v2 기준으로 구현하세요.
    v1 선택지가 표시되면 기존 v1 수신 서버 유지 또는 v2 전환을 위한 마이그레이션용으로만 사용하세요.
  </Step>
</Steps>

## v2 웹훅 페이로드 예시

v2는 모든 이벤트를 `{ "event": "...", "webhook_version": "v2", "call": { ... } }` 형태로 전송합니다.
아래 예시는 새 연동에서 구현해야 하는 기준 페이로드입니다.

| 변경 지점     | v2                                      | v1 기존 유지용                          |
| --------- | --------------------------------------- | ---------------------------------- |
| 버전 식별     | `webhook_version`                       | 없음                                 |
| 에이전트      | `agent.agent_id`, `agent.agent_version` | `agent_id`                         |
| 발신 번호     | `from_number`                           | `call_from`                        |
| 수신 번호     | `to_number`                             | `call_to`                          |
| 시작 시각     | `start_at`                              | `start_timestamp`                  |
| 종료 시각     | `end_at`                                | `end_timestamp`                    |
| 중간 이벤트 시각 | `occurred_at`                           | `timestamp`                        |
| 스크립트      | `transcript`로 통합                        | `transcript_with_tool_calls` 별도 제공 |
| 비용        | `call_cost.total_cost`                  | `call_cost.total_credits_used`     |

### call\_started

```json theme={null}
{
  "event": "call_started",
  "webhook_version": "v2",
  "call": {
    "call_id": "3079f46c-d141-4cd9-805f-69f212136b66",
    "agent": {
      "agent_id": "7ea3516d-08f6-4129-ab40-a731e379b320",
      "agent_version": "v3"
    },
    "call_type": "inbound",
    "from_number": "01012345678",
    "to_number": "07012345678",
    "dynamic_variables": {
      "customer_name": "홍길동"
    },
    "metadata": {
      "source": "crm"
    },
    "start_at": 1741842354957,
    "opt_out_sensitive_data_storage": false
  }
}
```

### mid\_call

```json theme={null}
{
  "event": "mid_call",
  "webhook_version": "v2",
  "call": {
    "call_id": "3079f46c-d141-4cd9-805f-69f212136b66",
    "agent": {
      "agent_id": "7ea3516d-08f6-4129-ab40-a731e379b320",
      "agent_version": "v3"
    },
    "occurred_at": 1742158401445,
    "event_type": "call_message",
    "event_data": {
      "role": "user",
      "content": "주문한 제품이 언제 도착하나요?"
    }
  }
}
```

### call\_ended

```json theme={null}
{
  "event": "call_ended",
  "webhook_version": "v2",
  "call": {
    "call_id": "3079f46c-d141-4cd9-805f-69f212136b66",
    "agent": {
      "agent_id": "7ea3516d-08f6-4129-ab40-a731e379b320",
      "agent_version": "v3"
    },
    "call_type": "inbound",
    "from_number": "01012345678",
    "to_number": "07012345678",
    "dynamic_variables": {
      "customer_name": "홍길동"
    },
    "metadata": {
      "source": "crm"
    },
    "call_status": "ended",
    "disconnection_reason": "user_hangup",
    "start_at": 1741652312146,
    "end_at": 1741652351189,
    "transcript": [
      { "role": "agent", "content": "안녕하세요. 무엇을 도와드릴까요?" },
      { "role": "user", "content": "주문 상태를 확인하고 싶습니다." }
    ],
    "recording_url": "https://storage.googleapis.com/agentify/egress/audio_recording.ogg",
    "opt_out_sensitive_data_storage": false
  }
}
```

### call\_analyzed

```json theme={null}
{
  "event": "call_analyzed",
  "webhook_version": "v2",
  "call": {
    "call_id": "3079f46c-d141-4cd9-805f-69f212136b66",
    "agent": {
      "agent_id": "7ea3516d-08f6-4129-ab40-a731e379b320",
      "agent_version": "v3"
    },
    "call_type": "inbound",
    "from_number": "01012345678",
    "to_number": "07012345678",
    "dynamic_variables": {
      "customer_name": "홍길동"
    },
    "metadata": {
      "source": "crm"
    },
    "call_status": "ended",
    "disconnection_reason": "user_hangup",
    "start_at": 1741652312146,
    "end_at": 1741652351189,
    "transcript": [
      { "role": "agent", "content": "안녕하세요. 무엇을 도와드릴까요?" },
      { "role": "user", "content": "주문 상태를 확인하고 싶습니다." }
    ],
    "recording_url": "https://storage.googleapis.com/agentify/egress/audio_recording.ogg",
    "call_cost": {
      "total_cost": 45
    },
    "call_analysis": {
      "summary": "고객이 주문 상태를 문의했고, 상담원이 배송 예정일을 안내했습니다.",
      "user_sentiment": "neutral",
      "custom_analysis_data": [
        { "type": "string", "name": "문의 유형", "value": "배송 상태 문의" },
        { "type": "boolean", "name": "문제 해결 여부", "value": true }
      ]
    },
    "opt_out_sensitive_data_storage": false
  }
}
```

<Note>
  시간 필드는 모두 UNIX 타임스탬프 밀리초 단위입니다.
  v2에는 `duration_ms`가 없으므로 `end_at - start_at`으로 계산하세요.
  `agent.agent_version`은 특정 배포 버전이면 `v3`처럼 표시되고, 현재 설정으로 실행된 통화면 `current`입니다.
</Note>

<Accordion title="v1 기존 유지용 페이로드">
  v1은 기존 수신 서버와의 호환을 위한 형식입니다.
  새 연동에서는 v2 예시를 기준으로 구현하세요.

  ### call\_started

  ```json theme={null}
  {
    "event": "call_started",
    "call": {
      "agent_id": "7ea3516d-08f6-4129-ab40-a731e379b320",
      "call_id": "3079f46c-d141-4cd9-805f-69f212136b66",
      "call_type": "inbound",
      "call_from": "01012345678",
      "call_to": "07012345678",
      "dynamic_variables": {
        "customer_name": "홍길동"
      },
      "metadata": {
        "source": "crm"
      },
      "start_timestamp": 1741842354957,
      "opt_out_sensitive_data_storage": false
    }
  }
  ```

  ### mid\_call

  ```json theme={null}
  {
    "event": "mid_call",
    "mid_call": {
      "agent_id": "7ea3516d-08f6-4129-ab40-a731e379b320",
      "call_id": "3079f46c-d141-4cd9-805f-69f212136b66",
      "timestamp": 1742158401445,
      "event_type": "call_message",
      "event_data": {
        "role": "user",
        "content": "주문한 제품이 언제 도착하나요?"
      }
    }
  }
  ```

  ### call\_ended

  ```json theme={null}
  {
    "event": "call_ended",
    "call": {
      "agent_id": "7ea3516d-08f6-4129-ab40-a731e379b320",
      "call_id": "3079f46c-d141-4cd9-805f-69f212136b66",
      "call_type": "inbound",
      "call_from": "01012345678",
      "call_to": "07012345678",
      "dynamic_variables": {
        "customer_name": "홍길동"
      },
      "metadata": {
        "source": "crm"
      },
      "disconnection_reason": "user_hangup",
      "start_timestamp": 1741652312146,
      "end_timestamp": 1741652351189,
      "duration_ms": 39043,
      "transcript": [
        { "role": "agent", "content": "안녕하세요. 무엇을 도와드릴까요?" },
        { "role": "user", "content": "주문 상태를 확인하고 싶습니다." }
      ],
      "transcript_with_tool_calls": [
        { "role": "agent", "content": "안녕하세요. 무엇을 도와드릴까요?" },
        { "role": "user", "content": "주문 상태를 확인하고 싶습니다." }
      ],
      "recording_url": "https://storage.googleapis.com/agentify/egress/audio_recording.ogg",
      "call_cost": {
        "total_credits_used": 45,
        "duration_seconds": 39
      },
      "call_analysis": {
        "summary": "고객이 주문 상태를 문의했고, 상담원이 배송 예정일을 안내했습니다.",
        "user_sentiment": "neutral",
        "custom_analysis_data": [
          { "type": "string", "name": "문의 유형", "value": "배송 상태 문의" },
          { "type": "boolean", "name": "문제 해결 여부", "value": true }
        ]
      },
      "opt_out_sensitive_data_storage": false
    }
  }
  ```
</Accordion>

<Tip>
  `민감 데이터 저장 금지`를 활성화하면 일부 통화 정보가 비워질 수 있습니다.
  데이터를 계속 활용하려면 웹훅 수신 서버에서 필요한 데이터를 별도 저장하세요.
</Tip>

## 관련 문서

* [웹훅 개요](/docs/operate/monitor/webhooks/overview) — 웹훅 유형과 우선순위
* [워크스페이스 단위 웹훅](/docs/operate/monitor/webhooks/organization) — HMAC 서명 검증과 보안 설정
* [웹훅 스키마](/docs/operate/monitor/webhooks/schema) — 이벤트별 필드 레퍼런스
* [통화 후 분석](/docs/operate/monitor/post-call) — 분석 결과 확인
* [빌드: 웹훅 설정](/docs/build/security/webhooks) — 에이전트 빌드 시 웹훅 설정

***

<Accordion title="연관 검색어">
  에이전트 웹훅, agent webhook, call\_started, call\_ended, call\_analyzed, mid\_call, webhook v2, webhook v1, 페이로드, 통화 이벤트
</Accordion>
