# 통화 컨텍스트 (CallContext) (/docs/webhooks/call-context)

**CallContext** 는 저장된 에이전트 설정을 건드리지 않고 **이 통화 한 건에만** 적용할 지시와 변수입니다.
같은 에이전트가 상대가 누구냐에 따라 다르게 말해야 할 때 씁니다 — 예약 확인 전화에서 예약 시각을 알려 주거나,
걸려온 번호로 고객을 찾아 이전 상담 내용을 들려주는 식입니다.

주입 경로는 셋입니다.

| 경로                | 전달 방법                                         | 시점                 |
| ------------------- | ------------------------------------------------- | -------------------- |
| **발신 (outbound)** | 발신 요청 본문의 `CallContext` 필드               | 발신을 거는 순간     |
| **배치 발신**       | 명단의 수신자별 `Tasks[].Variables`               | 그 수신자에게 걸 때  |
| **착신 (inbound)**  | 번호에 등록한 `callContextUrl` 로 한국AI통신이 조회 | 전화를 받기 **직전** |

  어느 경로든 결과는 같습니다 — 그 통화의 에이전트 지시문 뒤에 지시와 변수가 덧붙습니다. 지시문에
  `{{변수 이름}}` 을 쓰면 그 자리도 값으로 바뀝니다. 저장된
  에이전트 설정은 바뀌지 않으므로, 동시에 진행되는 다른 통화에 내용이 새지 않습니다.

통화에 실린 지시와 변수는 대시보드 **통화 기록 → 통화 상세** 의 「이번 통화의 요청」에 표시됩니다. 30일 동안 보관합니다.

## 페이로드

모든 경로가 **같은 모양**을 씁니다(배치는 이 중 `Variables` 만 씁니다).

- `Instruction` (string, 선택): 이 통화에만 적용할 지시. 4,000자 이내. 데이터만 넘길 때는 생략합니다.
- `Variables` (object, 선택): 이 통화에만 쓸 데이터. 최대 50개, 직렬화 후 8KB 이내.

`Instruction`·`Variables` 중 **하나는 있어야** 합니다. 둘 다 비어 있으면 형식 위반입니다.

`Variables` 의 이름은 `[A-Za-z_][A-Za-z0-9_]*` 형식이어야 합니다. 값은 문자열·숫자·불리언을 받아 **문자열로 저장**합니다.

```json
{
  "Instruction": "이 고객은 어제 배송 지연으로 문의한 이력이 있습니다. 먼저 사과하고 현재 배송 상태를 안내하세요.",
  "Variables": {
    "customer_name": "김지은",
    "order_id": "ORD-20260812-001",
    "delayed_days": 2
  }
}
```

## 발신 — 요청 본문으로

발신 API 본문에 `CallContext` 를 넣습니다.

```
POST /v1/accounts/{accountId}/calls

Body:
{
  "To": "01012345678",
  "From": "07011112222",
  "AgentId": "ag_...",
  "CallContext": {
    "Instruction": "예약 확인 전화입니다. 예약 시각을 안내하고 변경 의사를 물어보세요.",
    "Variables": { "reserved_at": "8월 14일 오후 3시" }
  }
}
```

형식이 잘못되면 발신이 **400 으로 거절**됩니다 — 잘못된 컨텍스트로 통화가 나가는 일은 없습니다.

### 에이전트에게 어떻게 보이나

지시와 변수는 저장된 에이전트 지시문 **뒤에** 이렇게 붙습니다. 지시를 생략하면 `[이번 통화의 요청]` 절 없이 데이터만 붙습니다.

```text
<저장된 에이전트 지시문>

[이번 통화의 요청]
예약 확인 전화입니다. 예약 시각을 안내하고 변경 의사를 물어보세요.
[이번 통화의 데이터]
{"reserved_at":"8월 14일 오후 3시"}
```

지시문에 `{{customer_name}}` 처럼 쓰면 그 자리가 `Variables` 의 값으로 바뀝니다(콜 플로우·문자와 같은 문법).

- 넘기지 않은 변수는 `{{customer_name}}` 글자 그대로 모델에 갑니다.
- 이름은 대소문자를 구분합니다.
- 값은 지시문의 문장으로 들어갑니다. 최종 사용자가 입력한 값을 넣으면 그 내용이 지시처럼 읽힐 수 있습니다.

예를 들어 저장된 지시문이 `{{reserved_at}} 예약을 확인하는 전화입니다.` 이고 `Variables` 가 위 예시와 같으면, 모델에는 `8월 14일 오후 3시 예약을 확인하는 전화입니다.` 로 전달되고 그 뒤에 위 블록이 붙습니다.

상대 번호·오늘 날짜는 [통화 변수](/docs/voice-agent/call-variables)(`{{call.customer_number}}`·`{{today}}`)로 넣을 수 있습니다.

## 배치 발신 — 명단의 Variables 로

[배치 발신](/docs/call-batches)을 `AgentId` 로 만들면, 수신자마다 넣은 `Tasks[].Variables` 가 그 통화의
`CallContext.Variables` 로 전달됩니다. 대시보드에서 명단 열을 변수로 매핑한 경우도 같습니다.

```
POST /v1/accounts/{accountId}/call-batches

Body:
{
  "Name": "10월 재주문 안내",
  "From": "07011112222",
  "AgentId": "ag_...",
  "Tasks": [
    {
      "To": "0212345678",
      "Variables": { "company": "(주)한빛", "contact_name": "김지은 과장" }
    },
    {
      "To": "0312223333",
      "Variables": { "company": "대성정밀", "contact_name": "박민수 대리" }
    }
  ]
}
```

배치에는 수신자별 `Instruction` 이 없습니다. 모든 수신자에게 같은 지시는 에이전트 지시문에 적고,
수신자마다 다른 것은 `Variables` 로 넘기세요. `Variables` 가 없는 수신자는 컨텍스트 없이 저장된
에이전트 그대로 통화합니다.

## 착신 — 조회 webhook 으로

착신은 거는 쪽이 우리가 아니라 고객이라, 발신처럼 미리 실어 보낼 수 없습니다.
대신 번호에 **조회 endpoint** 를 등록해 두면, 전화가 오는 순간 한국AI통신이 그 주소로 물어봅니다.

### 등록

대시보드에서는 **번호 상세 → 인바운드 라우팅 → 에이전트** 를 고른 뒤 _통화 컨텍스트 URL_ 에 입력합니다.

REST 로도 설정할 수 있습니다.

```
PUT /v1/accounts/{accountId}/numbers/{number}

Body:
{
  "callContextUrl": "https://my-app.com/clawops/call-context"
}
```


  `callContextUrl` 은 **`routingType` 이 `agent` 인 번호에서만** 사용할 수 있습니다. 라우팅을
  에이전트가 아닌 값으로 바꾸면 이 설정도 함께 해제됩니다.

### 요청

`Content-Type: application/x-www-form-urlencoded` POST 입니다.

- `CallId` (string, 필수): 통화 ID
- `AccountId` (string, 필수): 계정 ID
- `From` (string, 필수): 발신자 번호 — 고객을 식별할 때 쓰는 값
- `To` (string, 필수): 착신된 우리 번호
- `ForwardedFrom` (string, 선택): 착신전환으로 들어온 통화에서 발신자가 원래 건 번호 — 여러 번호를 우리 번호 하나로 착신전환했을 때 어느 번호로 걸려왔는지에 따라 다른 컨텍스트를 돌려줄 수 있습니다. 착신전환을 실행한 통신사가 전환 정보를 함께 보내온 경우에만 실립니다
- `CallerIdRestricted` (string, 필수): 발신자가 번호 표시제한(`*23#` 등)을 걸고 건 통화면 `true`, 아니면 `false`. 번호는 `From` 에 그대로 전달되고, 표시제한 여부만 이 값으로 알려 드립니다. 통신사가 표시제한 여부를 보내지 않은 통화는 `false` 로 올 수 있습니다 — 발신자가 번호를 숨기려 한 통화에 다르게 응대하려면 이 값으로 가르세요
- `CallStatus` (string, 필수): 항상 `ringing` — 아직 전화를 받기 전입니다
- `Direction` (string, 필수): 항상 `inbound`

요청에는 다른 webhook 과 같은 `X-Signature` 헤더가 실립니다. 검증 방법은
[서명 검증](/docs/webhooks/signature-verification) 을 참고하세요.

### 응답

`CallContext` 를 담은 JSON 을 돌려주세요.

```json
{
  "CallContext": {
    "Instruction": "이 고객은 VIP 입니다. 대기 없이 바로 담당자에게 연결하세요.",
    "Variables": { "customer_name": "김지은", "tier": "vip" }
  }
}
```

줄 컨텍스트가 없으면 **빈 본문**으로 응답하세요. 정상으로 처리되며, 저장된 에이전트 설정만으로 통화가 진행됩니다.

  `Instruction`·`Variables` 를 **응답 최상위에 올리지 마세요.** 반드시 `CallContext` 로 한 겹 감싸야
  합니다. 이 연동에서 가장 흔한 실수이고, 200 을 받은 채로 지시만 조용히 빠진 것처럼 보입니다.
  감싸지 않은 응답은 「컨텍스트 없음」이 아니라 **형식 위반**으로 판정해 실패로 기록합니다.


```json
// ❌ 지시가 실리지 않습니다
{ "Instruction": "...", "Variables": {} }
```

```json
// ✅ CallContext 로 감쌉니다
{ "CallContext": { "Instruction": "...", "Variables": {} } }
```

### 실패해도 통화는 진행됩니다

이 조회는 통화를 막지 않습니다. 아래 경우 모두 **컨텍스트 없이 통화가 그대로 이어집니다.**

- 응답이 없거나 타임아웃 (**10초**)
- 2xx 가 아닌 상태 코드
- JSON 파싱 실패, 형식 위반 (`Instruction`·`Variables` 가 둘 다 없음 등)

컨텍스트가 통화 품질에 필수라면, 이 fail-open 동작을 전제로 설계하세요 —
조회가 실패한 통화도 에이전트는 정상적으로 응대합니다.

조회가 실패한 통화에서는 지시문의 `{{변수}}` 가 글자 그대로 남습니다.

  이 조회는 **전화를 받기 전에** 동기로 일어납니다. 응답이 느리면 그만큼 발신자가 듣는 링백이
  길어집니다. 무거운 조회는 미리 캐시해 두고, 이 endpoint 는 빠르게 응답하도록 만드세요.

성공·실패 모두 통화 이벤트로 기록됩니다. 대시보드 **통화 기록 → 통화 상세** 의 이벤트 타임라인에
「통화 컨텍스트 조회」와 「통화 컨텍스트 조회 실패」로 표시됩니다.
[통화 이벤트 조회 API](/docs/api/calls/listCallEvents) 로도 같은 항목이 내려갑니다 —
`managed_agent.call_context_resolved` 의 `hasContext` 는 컨텍스트를 실제로 받았는지,
`managed_agent.call_context_failed` 의 `error` 는 판정 사유입니다.

### 그 밖의 제약

- 응답 본문은 **1MB** 까지 받습니다.
- 리다이렉트는 따라가지 않습니다 — 최종 주소를 직접 등록하세요.
- 공개된 HTTP(S) 주소만 등록할 수 있습니다(내부·사설 IP 차단).
