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

# 管理 WhatsApp 通话

> 配置 WhatsApp Calling、处理呼入与呼出通话信令、处理通话事件，以及下载录音或转录文本。

## 功能简介

YCloud 的 WhatsApp Calling API 用于管理 WhatsApp 用户与商业电话号码之间的语音通话信令。您的应用程序通过 YCloud 交换 SDP，而由您的 WebRTC 实现来处理音频连接。

通话可以从任一方向发起：

* **用户发起：** WhatsApp 用户致电您的企业。您的应用程序接收提议（offer）并接听或拒绝该通话。
* **企业发起：** 您的应用程序创建一个提议（offer）并请求 YCloud 致电 WhatsApp 用户。

<Info>
  Calling API 仅处理通话信令，不处理 WebRTC 媒体堆栈。您的应用程序负责对等连接建立、音频采集与播放、SDP 生成以及 WebRTC 资源清理。
</Info>

## API 全景

Calling API 和 Webhook 事件遵循相同的生命周期，但并不构成适用于每个通话的单一固定序列。完成通用设置后，按照用户发起或企业发起的流程进行操作。使用通话 ID（`wacid`）来关联每个操作和事件。

### 通用设置

| API | 何时使用 | 后续步骤 |
| - | - | - |
| [`GET settings`](/api-reference/whatsapp-phone-numbers/retrieve-phone-number-settings) 或 [`POST settings`](/api-reference/whatsapp-phone-numbers/save-phone-number-settings) | 在处理通话之前，或当 Calling 和采集设置发生变更时。 | 配置 Webhook 并准备您的 WebRTC 实现，然后按照对应通话方向的流程操作。 |

### 用户发起的通话

| 顺序 | API 或事件 | 后续步骤 |
| - | - | - |
| 1 | [`whatsapp.call.connect`](/zh/api-reference/guides/examples/webhook-examples/whatsapp-calling-connect-webhook-examples) | 接收 SDP offer、`phoneId` 和 `wacid`，然后创建 SDP answer。 |
| 2（可选） | [`POST /whatsapp/calls/preAccept`](/api-reference/whatsapp-calling/pre-accept-a-call) | 如果您计划接听通话，发送 SDP answer 以准备媒体路径。这不会接听通话。 |
| 3 | [`POST /whatsapp/calls/accept`](/api-reference/whatsapp-calling/accept-a-call) 或 [`POST /whatsapp/calls/reject`](/api-reference/whatsapp-calling/reject-a-call) | 二选一：使用 SDP answer 接听通话，或拒绝通话。 |

### 企业发起的通话

| 顺序 | API 或事件 | 后续步骤 |
| - | - | - |
| 1 | [`POST /whatsapp/calls/connect`](/api-reference/whatsapp-calling/connect-a-call) | 发送您的 SDP offer，发起通话，并存储返回的 `wacid`。 |
| 2 | [`whatsapp.call.connect`](/zh/api-reference/guides/examples/webhook-examples/whatsapp-calling-connect-webhook-examples) | 接收远端 SDP answer 并将其应用到同一个 WebRTC 对等连接。 |
| 3 | [`whatsapp.call.status.updated`](/zh/api-reference/guides/examples/webhook-examples/whatsapp-calling-status-update-webhook-examples) | 跟踪 `RINGING`、`ACCEPTED` 或 `REJECTED`。随着呼叫尝试状态的变化，此事件可能会到达多次。 |

### 通用通话结束处理

| API 或事件 | 何时使用 | 后续步骤 |
| - | - | - |
| [`POST /whatsapp/calls/terminate`](/api-reference/whatsapp-calling/terminate-a-call) | 可选。当您的应用程序需要挂断处于活动状态的呼入或呼出通话时调用。 | 在等待最终事件期间保持通话记录处于开启状态。 |
| [`whatsapp.call.terminate`](/zh/api-reference/guides/examples/webhook-examples/whatsapp-calling-terminate-webhook-examples) | 接收此事件以获取最终通话结果。 | 记录最终的 `COMPLETED` 或 `FAILED` 结果以及通话时长，然后释放剩余通话资源。 |

### 可选媒体处理

| API 或事件 | 何时使用 | 后续步骤 |
| - | - | - |
| [`whatsapp.call.recording.updated`](/zh/api-reference/webhooks/test-webhooks) 或 [`whatsapp.call.transcription.updated`](/zh/api-reference/webhooks/test-webhooks) | 当启用了采集且处理完成时。 | 如果事件报告了 `AVAILABLE`，读取其 `mediaAssetId`。`FAILED` 结果对于该资源来说是终态。 |
| [`GET /whatsapp/calls/media/{mediaAssetId}`](/api-reference/whatsapp-calling/download-call-media) | 仅在对应事件报告 `AVAILABLE` 后。 | 下载录音或转录文件。 |

这些表格描述了应用程序的工作流。它们并不保证 Webhook 的传送顺序会与表格行完全一致。请通过 `wacid` 关联事件并以幂等方式处理重新推送。

## 准备工作

在发起 Calling 请求之前，请准备好以下内容：

1. YCloud 账户 API 密钥。在 `X-API-Key` 标头中发送它。参见[身份验证](/zh/api-reference/guides/api-fundamentals/authentication)。
2. 在 YCloud 注册的 WhatsApp 商业账户和商业电话号码。
3. 已为该电话号码启用 Calling 功能。
4. 能够创建和应用 SDP offer 及 answer 的 WebRTC 音频实现。
5. 订阅了您集成所需的 Calling 事件的 YCloud Webhook 端点。参见[配置 Webhook](/zh/api-reference/guides/api-fundamentals/configure-webhooks)。
6. 当企业发起的通话需要用户通话权限时，需具备该权限。

联系您的 YCloud 客户代表以开通 Calling API 访问权限。有关外呼资格，请遵循[当前的 Calling 要求](/zh/documentation/calling/overview#business-initiated-calls-outbound)，包括商业资产（Business Portfolio）的 2,000 名客户消息层级以及受支持的商业号码国家/地区。原先的 1,000 次会话门槛已被当前要求取代。

以下示例使用这些环境变量：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
export YCLOUD_API_KEY="YOUR_API_KEY"
export WABA_ID="YOUR_WABA_ID"
export BUSINESS_PHONE_NUMBER="+16315551111"
```

请将 API 密钥保存在服务器上。切勿将其放入浏览器或移动应用程序代码中。

## 工作原理

首先配置商业电话号码。然后根据通话方向交换 SDP。API 响应会确认单个信令操作，而 Webhook 事件则报告状态更改和最终结果。如果启用了捕获功能，单独的事件会指示录音或转录何时可供下载。

## 请求

## 配置商业电话号码

通话和捕获设置归属于特定的 WhatsApp 商业电话号码。处理通话前请先对其进行配置。

### 读取 Calling 设置

使用 [`GET /whatsapp/phoneNumbers/{wabaId}/{phoneNumber}/settings`](/api-reference/whatsapp-phone-numbers/retrieve-phone-number-settings) 检查 Calling 是否已启用以及 Calling 图标是否可见：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request GET \
  "https://api.ycloud.com/v2/whatsapp/phoneNumbers/$WABA_ID/$BUSINESS_PHONE_NUMBER/settings?type=calling" \
  --header "X-API-Key: $YCLOUD_API_KEY"
```

如果省略 `type`，YCloud 会返回 Calling 设置响应。

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "calling": {
    "id": "19213232132",
    "status": "ENABLED",
    "iconVisibility": "DEFAULT"
  }
}
```

| 字段 | 取值 | 描述 |
| - | - | - |
| `calling.id` | 字符串 | WhatsApp 商业电话号码 ID。 |
| `calling.status` | `ENABLED`, `DISABLED` | 该电话号码是否已启用 Calling。 |
| `calling.iconVisibility` | `DEFAULT`, `DISABLE_ALL` | WhatsApp 是采用默认的 Calling 图标行为，还是隐藏所有 Calling 图标。 |

### 启用 Calling

在开始接听或拨打通话前，使用 [`POST /whatsapp/phoneNumbers/{wabaId}/{phoneNumber}/settings`](/api-reference/whatsapp-phone-numbers/save-phone-number-settings) 保存 Calling 设置：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request POST \
  "https://api.ycloud.com/v2/whatsapp/phoneNumbers/$WABA_ID/$BUSINESS_PHONE_NUMBER/settings" \
  --header "X-API-Key: $YCLOUD_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "calling": {
      "status": "ENABLED",
      "iconVisibility": "DEFAULT"
    }
  }'
```

响应包含已保存的 `calling` 对象。在处理实时通话之前，请完成 Webhook 和 WebRTC 会话的配置。

### 配置录音和转录

捕获设置适用于源自 API 的新通话。您可以启用录音、转录或两者均启用。

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request POST \
  "https://api.ycloud.com/v2/whatsapp/phoneNumbers/$WABA_ID/$BUSINESS_PHONE_NUMBER/settings" \
  --header "X-API-Key: $YCLOUD_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "capture": {
      "recordingEnabled": true,
      "transcriptionEnabled": true,
      "purpose": "quality_assurance",
      "announcementLanguage": "en_US"
    }
  }'
```

| 字段 | 类型 | 必填 | 描述 |
| - | - | - | - |
| `capture.recordingEnabled` | 布尔值 | 是 | 启用或停用录音捕获。 |
| `capture.transcriptionEnabled` | 布尔值 | 是 | 启用或停用转录捕获。 |
| `capture.purpose` | 字符串 | 条件必填 | 启用任一捕获选项时必填。最多 250 个字符。 |
| `capture.announcementLanguage` | 字符串 | 条件必填 | 启用任一捕获选项时必填。支持的值：`en`, `en_US`, `en_AU`, `en_CA`, `en_GB`, `en_IN`, `en_NZ`, `nl`, `fr`, `de`, `hi`, `it`, `kn`, `pt`, `es`, `es_ES`, `te`, `vi`。 |

若要读取捕获设置，请使用 `type=capture`：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request GET \
  "https://api.ycloud.com/v2/whatsapp/phoneNumbers/$WABA_ID/$BUSINESS_PHONE_NUMBER/settings?type=capture" \
  --header "X-API-Key: $YCLOUD_API_KEY"
```

您可以在同一个 `POST` 请求中包含 `calling` 和 `capture`。验证电话号码的访问权限后，YCloud 会尝试分别保存每个部分。如果任一部分保存失败，另一部分可能已经存储。发生错误后请读取两项设置，然后仅重试仍需更新的部分。

## 处理用户发起的通话

![用户发起的 Calling 流程](https://files.readme.io/65fa96a2414cfde54dbf36c30af6e6392ca36093d478674c23547879a14f9c4c-image.png)

在用户发起的通话中，WhatsApp 会发送 SDP offer。您的应用程序应答该 offer，然后接听或拒绝该通话。

### 1. 接收 connect 事件

订阅 [`whatsapp.call.connect`](/zh/api-reference/guides/examples/webhook-examples/whatsapp-calling-connect-webhook-examples)。用户发起的事件会将 `direction` 设置为 `USER_INITIATED`，并包含 SDP `offer`。

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "evt_call_connect_123",
  "type": "whatsapp.call.connect",
  "apiVersion": "v2",
  "createTime": "2024-01-01T12:00:00.000Z",
  "callingConnect": {
    "id": "6757b723960b25543b9ecc66",
    "wacid": "wacid.HBgNNjI4MTM2MTkwNTEzMxUCABIYIEF",
    "phoneId": "461269257068832",
    "from": "+6281361905133",
    "to": "+6283138205150",
    "direction": "USER_INITIATED",
    "dialTime": 1733826430000,
    "sdpType": "offer",
    "sdp": "SDP_OFFER"
  }
}
```

将 `callingConnect.wacid` 和 `callingConnect.phoneId` 一同存储。将收到的 SDP offer 应用到您的 WebRTC 对等连接，并创建 SDP answer。

### 2. 预接听通话

创建 SDP answer 之后、坐席接听通话之前调用 pre-accept（预接听）。这会准备好媒体路径，并能减少接听通话时的音频卡顿或截断。

**端点：** [`POST /whatsapp/calls/preAccept`](/api-reference/whatsapp-calling/pre-accept-a-call)

| 字段 | 类型 | 必填 | 描述 |
| - | - | - | - |
| `phoneId` | 字符串 | 是 | 来自 connect 事件的商业电话号码 ID。 |
| `wacid` | 字符串 | 是 | 来自 connect 事件的 WhatsApp 通话 ID。 |
| `sdpType` | 字符串 | 是 | 必须为 `answer`。 |
| `sdp` | 字符串 | 是 | 由您的 WebRTC 实现创建的 SDP answer。 |

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request POST https://api.ycloud.com/v2/whatsapp/calls/preAccept \
  --header "X-API-Key: $YCLOUD_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "phoneId": "461269257068832",
    "wacid": "wacid.HBgNNjI4MTM2MTkwNTEzMxUCABIYIEF",
    "sdpType": "answer",
    "sdp": "SDP_ANSWER"
  }'
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "wacid": "wacid.HBgNNjI4MTM2MTkwNTEzMxUCABIYIEF",
  "success": true
}
```

预接听成功后，请让通话保持在振铃或就绪状态。预接听并不会为用户接通通话。

### 3. 接听通话

当坐席接听时，向接听端点发送相同的 `phoneId`、`wacid`、SDP 类型和 SDP 应答 (answer)。

**端点：** [`POST /whatsapp/calls/accept`](/api-reference/whatsapp-calling/accept-a-call)

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request POST https://api.ycloud.com/v2/whatsapp/calls/accept \
  --header "X-API-Key: $YCLOUD_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "phoneId": "461269257068832",
    "wacid": "wacid.HBgNNjI4MTM2MTkwNTEzMxUCABIYIEF",
    "sdpType": "answer",
    "sdp": "SDP_ANSWER"
  }'
```

请求字段和响应结构与预接听相同。响应成功后，根据 WebRTC 连接状态判断媒体是否就绪，并等待 `whatsapp.call.terminate` 获取最终通话结果。

文档记录的呼入接听窗口期约为收到 connect Webhook 后的 30–60 秒。请及时接听；未接听的通话将在用户端以 **未接听** 通知结束，并触发 terminate Webhook。

即使 WebRTC 连接已建立，也仅在接听请求返回 HTTP `200` 后才开始发送音频。过早开始可能会导致前几个字被截断；过晚开始则会导致静音。

### 拒接而非接听

如果坐席无法接听呼入电话，请将其拒接，而不是创建活跃会话。

**端点：** [`POST /whatsapp/calls/reject`](/api-reference/whatsapp-calling/reject-a-call)

| 字段 | 类型 | 是否必填 | 说明 |
| - | - | - | - |
| `phoneId` | String | 是 | 来自 connect 事件的商业电话号码 ID。 |
| `wacid` | String | 是 | 来自 connect 事件的 WhatsApp 通话 ID。 |

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request POST https://api.ycloud.com/v2/whatsapp/calls/reject \
  --header "X-API-Key: $YCLOUD_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "phoneId": "461269257068832",
    "wacid": "wacid.HBgNNjI4MTM2MTkwNTEzMxUCABIYIEF"
  }'
```

响应使用标准通话响应格式。请求完成后释放本地对等连接（peer connection），如果后续收到该 `wacid` 的终止事件，仍应正常接收处理。

## 发起企业呼叫

在企业呼叫中，您的应用程序创建 SDP 提议 (offer) 并将其发送给 YCloud。

### 获取呼叫权限

在发起呼叫之前，请先获取用户的呼叫权限。在有效的客户服务窗口期内，可以发送交互式权限请求：

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "from": "+16315551111",
  "to": "+16315552222",
  "type": "interactive",
  "interactive": {
    "type": "call_permission_request",
    "action": { "name": "call_permission_request" },
    "body": { "text": "May we call you to help with your order?" }
  }
}
```

将此请求体发送至 `POST /v2/whatsapp/messages/sendDirectly` 或使用 `POST /v2/whatsapp/messages` 加入队列。

您也可以创建呼叫权限模板。例如，将此请求体提交至 `POST /v2/whatsapp/templates`，然后等待审批：

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "wabaId": "WABA_ID",
  "name": "call_permission_request_template",
  "language": "en_US",
  "category": "UTILITY",
  "components": [
    {
      "type": "BODY",
      "text": "May we call you about order {{1}}?",
      "example": { "body_text": [["ORDER_123"]] }
    },
    { "type": "call_permission_request" }
  ]
}
```

发送包含请求体参数的已审批模板：

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "from": "+16315551111",
  "to": "+16315552222",
  "type": "template",
  "template": {
    "name": "call_permission_request_template",
    "language": { "code": "en_US", "policy": "deterministic" },
    "components": [
      { "type": "body", "parameters": [{ "type": "text", "text": "ORDER_123" }] }
    ]
  }
}
```

当电话号码的呼叫设置中启用了 `callback_permission_status` 时，用户发起的呼叫可以授予回拨权限。用户也可以通过商业资料授予永久呼叫权限。

权限回复以 `whatsapp.inbound_message.received` 事件的形式送达。请检查 `interactive.call_permission_reply` 对象，而不仅仅是确认权限请求消息是否送达：

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "whatsapp.inbound_message.received",
  "whatsappInboundMessage": {
    "from": "+16315552222",
    "to": "+16315551111",
    "type": "interactive",
    "interactive": {
      "type": "call_permission_reply",
      "call_permission_reply": {
        "response": "accept",
        "is_permanent": true,
        "response_source": "user_action"
      }
    }
  }
}
```

| 字段 | 含义 |
| - | - |
| `response` | 用户接受或拒绝了权限请求。 |
| `is_permanent` | 授权是否为永久有效，而非限时有效。 |
| `expiration_timestamp` | 临时权限的过期时间（若有）。 |
| `response_source` | 回复是由用户操作触发还是自动触发。 |

被拒绝或权限过期后请勿发起呼叫。Meta 错误 `138006` 表示商业号码缺少所需的呼叫权限。有关服务商错误的详细信息，请参阅 [Meta 的呼叫错误](https://developers.facebook.com/documentation/business-messaging/whatsapp/calling/reference/errors)。

### 1. 创建 SDP 提议 (offer)

创建本地 WebRTC 对等连接并附加音频轨道。生成 SDP 提议 (offer)，将其设置为本地描述 (local description)，并在该操作完成后再将提议发送至 YCloud。

### 2. 发起通话连接

**端点：** [`POST /whatsapp/calls/connect`](/api-reference/whatsapp-calling/connect-a-call)

| 字段 | 类型 | 是否必填 | 说明 |
| - | - | - | - |
| `from` | String | 是 | 采用 E.164 格式的已注册商业电话号码。 |
| `to` | String | 条件必填 | 采用 E.164 格式的用户电话号码。未提供 `recipient` 时为必填项。 |
| `recipient` | String | 条件必填 | 用户 BSUID 或父级 BSUID。未提供 `to` 时为必填项。 |
| `sdpType` | String | 是 | 必须为 `offer`。 |
| `sdp` | String | 是 | 由您的 WebRTC 实现生成的 SDP 提议 (offer)。 |

请提供 `to` 或 `recipient` 中的至少一项。如果两者均提供，YCloud 将使用 `to` 并忽略 `recipient`。

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request POST https://api.ycloud.com/v2/whatsapp/calls/connect \
  --header "X-API-Key: $YCLOUD_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "from": "+16315551111",
    "to": "+16315552222",
    "sdpType": "offer",
    "sdp": "SDP_OFFER"
  }'
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "wacid": "wacid.HBgNNjI4MTM2MTkwNTEzMxUCABE",
  "success": true
}
```

请立即存储返回的 `wacid`。`success: true` 表示连接操作已被接受，并不代表用户已接听。

### 3. 应用应答并跟踪呼叫尝试

YCloud 会为该通话发送 [`whatsapp.call.connect`](/zh/api-reference/guides/examples/webhook-examples/whatsapp-calling-connect-webhook-examples)。对于企业发起的通话，该事件包含 `direction: BUSINESS_INITIATED` 并携带远端 SDP `answer`。将该应答作为同一对等连接的远端描述应用。

订阅 [`whatsapp.call.status.updated`](/zh/api-reference/guides/examples/webhook-examples/whatsapp-calling-status-update-webhook-examples) 以跟踪该尝试：

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "evt_676e5ab57a9cb742d02d7646",
  "type": "whatsapp.call.status.updated",
  "apiVersion": "v2",
  "createTime": "2024-12-27T07:41:28.422Z",
  "callingStatusUpdated": {
    "wabaId": "188234691048809",
    "wacid": "wacid.HBgNNjI4MTM2MTkwNTEzMxUCABE",
    "phoneId": "461269257068832",
    "status": "RINGING",
    "recipientPhone": "+16315552222"
  }
}
```

| 状态 | 含义 | 建议操作 |
| - | - | - |
| `RINGING` | 正在向用户振铃呼叫。 | 保持尝试开启并继续等待。 |
| `ACCEPTED` | 用户接受了通话。 | 使用 WebRTC 连接状态确认媒体就绪情况。 |
| `REJECTED` | 用户拒接了通话。 | 停止尝试并释放本地 WebRTC 资源。 |

确保事件处理具备幂等性，以便重新投递时不会重复执行客服操作、计费或清理。

## 终止进行中的通话

当您的应用程序需要结束进行中的呼入或呼出通话时，调用 terminate。

**端点：** [`POST /whatsapp/calls/terminate`](/api-reference/whatsapp-calling/terminate-a-call)

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request POST https://api.ycloud.com/v2/whatsapp/calls/terminate \
  --header "X-API-Key: $YCLOUD_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "phoneId": "461269257068832",
    "wacid": "wacid.HBgNNjI4MTM2MTkwNTEzMxUCABE"
  }'
```

请求字段与拒接请求一致。成功的响应确认 YCloud 已处理该终止操作。请保持通话记录开启，直到收到最终的终止事件或由您自己的恢复策略将其关闭。

## 响应

所有五个信令端点都返回相同的响应结构：

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "wacid": "wacid.HBgNNjI4MTM2MTkwNTEzMxUCABE",
  "success": true
}
```

`wacid` 标识与该操作关联的通话。`success: true` 确认信令操作成功；并不确认另一方参与者已接听或通话已完成。请使用 WebRTC 状态和呼叫 Webhook 事件来获取这些结果。

## 处理最终通话事件

[`whatsapp.call.terminate`](/zh/api-reference/guides/examples/webhook-examples/whatsapp-calling-terminate-webhook-examples) 是通话的生命周期终止事件。

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "evt_6757b889a5a42d369ef48481",
  "type": "whatsapp.call.terminate",
  "apiVersion": "v2",
  "createTime": "2024-12-10T03:42:01.822Z",
  "callingTerminate": {
    "id": "6757b889960b25543b9ecc67",
    "wacid": "wacid.HBgNNjI4MTM2MTkwNTEzMxUCABIYIEFENjB",
    "phoneId": "461269257068832",
    "from": "+6281361905133",
    "to": "+6283138205150",
    "direction": "USER_INITIATED",
    "startTime": 1733734738000,
    "endTime": 1733734771000,
    "duration": 33,
    "status": "COMPLETED"
  }
}
```

| 字段 | 描述 |
| - | - |
| `wacid` | 用于将事件与您的通话记录进行匹配的通话 ID。 |
| `direction` | `USER_INITIATED` 或 `BUSINESS_INITIATED`。 |
| `startTime`、`endTime` | 毫秒级的 Unix 时间戳。 |
| `duration` | 通话时长（秒）。 |
| `status` | 最终结果：`COMPLETED` 或 `FAILED`。 |
| `errorCode` | 通话失败时表示为字符串的数字错误代码。 |

收到此事件时，请完成通话记录并释放所有剩余的 WebRTC 资源。早前的 API 响应并不确认通话已完成。

## 接收录音与转录

启用捕获后，媒体处理会在通话生命周期结束后继续进行。录音和转录具有各自独立的终止事件：

| 事件 | 有效载荷属性 | 结果 |
| - | - | - |
| [`whatsapp.call.recording.updated`](/zh/api-reference/webhooks/test-webhooks) | `callingRecording` | 录音状态为 `AVAILABLE` 或已永久 `FAILED`。 |
| [`whatsapp.call.transcription.updated`](/zh/api-reference/webhooks/test-webhooks) | `callingTranscription` | 转录状态为 `AVAILABLE` 或已永久 `FAILED`。 |

以下示例显示了一个可用的录音：

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "evt_call_recording_01JZ8K4V7H3P6Q9R2T5W8X1Y4Z",
  "type": "whatsapp.call.recording.updated",
  "apiVersion": "v2",
  "createTime": "2026-08-04T08:00:00.000Z",
  "callingRecording": {
    "wacid": "wacid.HBgNNjI4MTM2MTkwNTEzMxUCABE",
    "phoneId": "461269257068832",
    "mediaAssetId": "66b1f0c2e4b05c2d8f1a3b47",
    "status": "AVAILABLE"
  }
}
```

两个有效载荷属性使用相同的字段：

| 字段 | 描述 |
| - | - |
| `wacid` | 与媒体资产关联的通话 ID。 |
| `phoneId` | 与通话关联的商业电话号码 ID。 |
| `mediaAssetId` | 媒体下载 API 使用的 YCloud 资产 ID。 |
| `status` | `AVAILABLE` 或 `FAILED`。 |
| `error.code` | 稳定的处理失败代码。当 `status` 为 `FAILED` 时存在。 |
| `error.retryable` | 重试上游媒体操作是否可能成功。当 `status` 为 `FAILED` 时存在。 |

### 下载可用资产

仅在相应事件报告 `AVAILABLE` 之后，再调用媒体端点。

**端点：** [`GET /whatsapp/calls/media/{mediaAssetId}`](/api-reference/whatsapp-calling/download-call-media)

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request GET \
  "https://api.ycloud.com/v2/whatsapp/calls/media/66b1f0c2e4b05c2d8f1a3b47" \
  --header "X-API-Key: $YCLOUD_API_KEY" \
  --output calling-media.ogg
```

该端点以附件形式返回完整文件，不支持字节范围下载。录音使用 `.ogg`；转录使用 `.json`。

只有所属的 YCloud 租户才能下载资产。资产自创建之日起保留 30 天可用。缺失、不可用、过期或非所属资产将返回 HTTP 404。

## 构建可靠的 Webhook 接收器

让您的端点订阅集成所需的事件：

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "enabledEvents": [
    "whatsapp.call.connect",
    "whatsapp.call.status.updated",
    "whatsapp.call.terminate",
    "whatsapp.call.recording.updated",
    "whatsapp.call.transcription.updated"
  ]
}
```

对于每个请求：

1. 保留原始请求体，并在信任该事件之前验证 `YCloud-Signature`。
2. 持久化存储该事件或将持久任务加入队列。
3. 及时返回成功的 `2xx` 响应。
4. 按顶级事件 `id` 进行去重。
5. 通过 `wacid` 关联通话数据；并保留 `phoneId` 以供后续操作使用。
6. 妥善处理时间间隔极短的相关事件，并容忍重复投递。

有关端点创建、签名验证和投递行为，请参阅[配置 Webhook](/zh/api-reference/guides/api-fundamentals/configure-webhooks)。[Webhook 有效负载示例](/zh/api-reference/guides/examples/webhook-examples/webhook-payload-examples)页面包含完整的生成示例。

## 处理错误与恢复

通话端点使用 YCloud 的标准 API 错误响应。有关响应结构和重试指引，请参阅[处理错误](/zh/api-reference/guides/api-fundamentals/handle-errors)。

排查常见通话故障时，请参考以下检查项：

| 情况 | 检查项 | 恢复方式 |
| - | - | - |
| 请求验证失败 | 必填 ID、E.164 格式、SDP 类型、SDP 内容或录制字段。 | 修正请求。不要在未修改输入的情况下重试。 |
| 连接目标无效 | `to` 或 `recipient` 必须至少提供一个。如果两者都提供，`to` 优先。 | 发送有效的 E.164 电话号码或 BSUID。 |
| 通话不可用 | 电话号码归属权、注册状态、通话设置、权限以及支持的目标地区。 | 在重试前修复配置或权限问题。 |
| Meta 拒绝信令 | 检查返回的错误详情，包括存在的 `whatsappApiError`。 | 根据错误的可重试性进行操作，并修正上游原因。 |
| 合并设置保存失败 | `calling` 或 `capture` 其中之一可能已经保存。 | 读取这两项设置，然后仅重试仍需更新的部分。 |
| 媒体下载返回 400 | 发送了非空的 `Range` 请求头。 | 请求完整资源且不带 `Range`。 |
| 媒体下载返回 404 | 资源不存在、尚未就绪、已过期或归属于其他租户。 | 确认事件状态、租户、资源 ID 和 30 天有效期。 |
| Webhook 重复 | 同一事件被再次投递。 | 返回 `2xx`，并通过事件 `id` 跳过重复的业务处理。 |

请求超时并不代表信令操作失败。在重试之前，请结合 Webhook 事件和当前本地通话状态进行对账核对。该操作可能已经送达 WhatsApp。

## 集成核对清单

* 在正确的商业电话号码上启用通话功能。
* 配置并测试所有必需的通话 Webhook 订阅。
* 验证 Webhook 签名并对事件进行去重。
* 将 `wacid`、`phoneId`、呼叫方向和当前状态一并存储。
* 将 API `success` 视为操作已受理，而非最终通话结果。
* 仅将 `preAccept` 用作准备阶段；调用 `accept` 进行接听。
* 在收到 `whatsapp.call.terminate` 后完成通话结束处理。
* 仅在收到 `AVAILABLE` 事件后且在 30 天内下载录制的媒体文件。
* 在拒绝、挂断、失败以及本地超时情况下释放 WebRTC 资源。
* 避免在常规应用程序日志中记录 API 密钥、完整 SDP 或参与者标识符。

<CardGroup cols={2}>
  <Card title="Calling API 参考" icon="phone" href="/api-reference/whatsapp-calling/connect-a-call">
    查看每个 Calling 端点的具体请求和响应结构规范。
  </Card>

  <Card title="Webhook 有效负载示例" icon="webhook" href="/zh/api-reference/guides/examples/webhook-examples/webhook-payload-examples">
    查看基于 Webhook 规范生成的完整通话事件示例。
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.