接收来电
工作区持有的号码可以将来电递送到 SIP 中继、转接到已验证的号码、运行已发布的序列,或拒接来电。一个号码同一时间只有一条路由:它自己的路由,或者在没有自身路由时使用工作区的默认路由。
工作区默认路由初始设为拒绝,因此未经配置的号码会拒绝来电者,而不是没有任何应答方式。更改默认路由可为所有此类号码提供相同的应答方式。请参阅设置默认路由。
要在 Bird 原生流程中处理来电,请发布序列并将号码绑定到其通话入口。所选入口必须接受空数据。
前提条件
在将号码指向某种接听方式之前:
- 能够接收来电的号码。 打开 Voice > Numbers,检查 Directions 列是否有入站标记。从其他运营商注册为主叫号码的号码不会在此接收来电:该运营商负责路由拨打到该号码的呼叫,因此它不携带任何接听方式。
- 递送到中继: 一个已开启入站呼叫且至少有一个递送网关的 SIP 中继。
- 转接: 一个用于转接的已验证主叫号码。
- 用于序列: 同一工作区中一个已激活并发布的序列,其呼叫入口接受空的入口数据。在 Inbound routing 下选择 Run a sequence,选择序列和呼叫入口,然后保存。序列构建器指南介绍了发布和来电草稿测试。
- 通过 API 或 CLI 更改设置: 一个持有
voice_management作用域写入权限的 API 密钥。该作用域涵盖语音配置;voice作用域涵盖呼叫流量和统计数据,因此读取通话记录需要另一个作用域。
将来电递送到 SIP 中继
递送会拨打您在中继上声明的地址来连接您自己的电话系统。请先开启该方向,因为号码只能指向已接受入站呼叫的中继。
- 打开 Voice > SIP 中继,打开该中继,在 Inbound calling 下选择 Enable inbound。
- 在同一部分下添加至少一个网关。没有网关的中继会拒绝所有拨入其应答号码的来电。
- 打开 Voice > Numbers,打开该号码,在 Inbound routing 下选择 Deliver to a SIP trunk。
- 选择中继并点击 Save。列表中只会显示已开启入站呼叫的中继。
Numbers 列表上的 Used for 列随后会显示该号码已递送到该中继,中继自身的页面也会列出它应答的号码。
通过 API,使用 inbound_enabled: true 更新中继,添加网关,然后将号码的语音记录指向该中继。语音记录的 ID 以 vnu_ 开头,与 /v1/numbers 为同一号码返回的 nda_ ID 不同。将 nda_ ID 传给语音号码操作会被拒绝并返回 422。要找到语音记录,请按号码的数字搜索您的语音号码:
for await (const number of bird.voice.numbers.list({ search: "31201234567" })) {
console.log(number.id, number.phone_number);
}for number, err := range client.Voice.Numbers.List(context.Background(), bird.VoiceNumbersListParams{
Search: "31201234567",
}) {
if err != nil {
log.Fatal(err)
}
fmt.Println(number.Id, number.PhoneNumber)
}foreach ($bird->voice->numbers->list(['search' => '31201234567']) as $number) {
echo $number->getId(), ' ', $number->getPhoneNumber(), "\n";
}bird voice numbers list --search 31201234567curl -X GET "https://{region}.platform.bird.com/v1/voice/numbers" \
-H "Authorization: Bearer $TOKEN" \
--url-query "search=31201234567"每条结果携带其 id、phone_number 和当前的 inbound_configuration.route。将中继路由发送到更新语音号码,使用该 id:
const number = await bird.voice.numbers.update("NUMBER_ID", {
inbound_configuration: {
route: { type: "trunk", trunk_id: "spt_01krdgeqcxet5s7t44vh8rt9mg" },
},
});
console.log(number.id, number.inbound_configuration?.route?.type);var route bird.VoiceCallRouteWritable
if err := route.FromVoiceCallRouteTrunk(bird.VoiceCallRouteTrunk{
TrunkId: "spt_01krdgeqcxet5s7t44vh8rt9mg",
}); err != nil {
log.Fatal(err)
}
number, err := client.Voice.Numbers.Update(context.Background(), "NUMBER_ID", bird.VoiceNumbersUpdateParams{
InboundConfiguration: &bird.VoiceInboundConfigurationPut{Route: route},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(number.Id)$number = $bird->voice->numbers->update(
'NUMBER_ID',
(new VoiceNumberUpdate())->setInboundConfiguration(
(new VoiceInboundConfigurationPut())->setRoute([
'type' => 'trunk',
'trunk_id' => 'spt_01krdgeqcxet5s7t44vh8rt9mg',
]),
),
);
echo $number->getId(), "\n";bird voice numbers update <number-id> --body-file - <<'JSON'
{
"inbound_configuration": {
"route": {
"type": "trunk",
"trunk_id": "spt_01krdgeqcxet5s7t44vh8rt9mg"
}
}
}
JSONcurl -X PATCH "https://{region}.platform.bird.com/v1/voice/numbers/{number_id}" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"inbound_configuration": {
"route": {
"type": "trunk",
"trunk_id": "spt_01krdgeqcxet5s7t44vh8rt9mg"
}
}
}'入站呼叫未开启的中继会被拒绝并返回 412 和 E21052。该路由会替换号码之前的任何设置。将 {"type": "reject"} 作为路由发送会无论默认设置如何都拒绝来电者,而发送 null 会将号码恢复为工作区默认路由。
网关需要什么
网关是来电被递送到的一个地址,以及对端希望呼叫中的两个号码如何呈现:
| 设置 | 含义 |
|---|---|
| SIP URI | 您电话系统的主机地址,可附带可选端口,如 sip:pbx.example.com:5060。只需提供主机部分:包含用户部分的 URI 会被拒绝 |
| 优先级 | 网关的尝试顺序,数值最小的优先 |
| 目的号码格式 | 被叫号码呈现给对端的格式。默认为 E.164 |
| 来源号码格式 | 主叫号码呈现给对端的格式,位于递送呼叫的 P-Asserted-Identity 请求头中。默认为 E.164 |
优先级相同的网关平分来电,任何一个都可能在某次呼叫中被首先尝试。要在递送失败时切换到第二个地址,请为该网关设置更大的优先级数值:当第一个网关未应答时才会尝试它。
两种号码格式都是基于一个占位符 {number} 的模板,该占位符代表去掉前导 + 的号码。目的地格式被放置在 SIP URI 主机之前,因此 1234#{number} 会将拨往 +31201234567 的呼叫递送为 sip:1234#31201234567@pbx.example.com:5060。两者的默认值均为 +{number},即 E.164。如果格式中完全不包含 {number},则中继应答的每个号码都会被发送到同一个固定地址。期望接收不带 + 的号码的对端,可以单独使用 {number} 作为格式。
通过 API,使用这些设置向中继添加网关。请先开启中继的入站呼叫:在未开启入站呼叫的中继上创建网关会被拒绝,并返回 412 和 E21052。
const gateway = await bird.voice.trunks.gateways.create("TRUNK_ID", {
sip_uri: "sip:pbx.example.com:5060",
priority: 0,
destination_format: "1234#{number}",
});
console.log(gateway.id, gateway.priority);gateway = client.voice.trunks.gateways.create(
"TRUNK_ID",
sip_uri="sip:pbx.example.com:5060",
priority=0,
destination_format="1234#{number}",
)
print(gateway.id, gateway.priority)gateway, err := client.Voice.Trunks.Gateways.Create(context.Background(), "TRUNK_ID", bird.VoiceTrunksGatewaysCreateParams{
SipURI: "sip:pbx.example.com:5060",
Priority: 0,
DestinationFormat: bird.Ptr("1234#{number}"),
})
if err != nil {
log.Fatal(err)
}
fmt.Println(gateway.Id, gateway.Priority)$gateway = $bird->voice->trunks->gateways->create(
'TRUNK_ID',
(new VoiceTrunkGatewayCreate())
->setSipUri('sip:pbx.example.com:5060')
->setPriority(0)
->setDestinationFormat('1234#{number}'),
);
echo $gateway->getId(), ' ', $gateway->getPriority(), "\n";bird voice trunks gateways create <trunk-id> \
--destination-format '1234#{number}' \
--priority 0 \
--sip-uri sip:pbx.example.com:5060curl -X POST "https://{region}.platform.bird.com/v1/voice/trunks/{trunk_id}/gateways" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"sip_uri": "sip:pbx.example.com:5060",
"priority": 0,
"destination_format": "1234#{number}"
}'之后可通过更新网关来更改其优先级或格式。
Warning: 关闭 trunk 的呼入功能或删除该 trunk,会将所有指向它的号码恢复为工作区默认路由。如果工作区默认路由本身指定了该 trunk,则会回退为拒接。重新开启呼入功能不会恢复这些设置,因此每个号码都需要重新指向一个 trunk。
设置默认路由
工作区默认路由为所有没有自身路由的号码接听来电。它的初始值为拒绝。拥有自身路由的号码在默认值更改时保留其原有路由。
- 打开 Voice > 号码。
- 在 没有自身路由的号码的来电 旁边,选择 更改,选择接听方式并保存。
更改从这些号码接收到的下一通来电开始生效。通过 API,更新语音设置:
curl -X PATCH "https://{region}.platform.bird.com/v1/voice/settings" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"inbound_configuration": {
"route": {
"type": "trunk",
"trunk_id": "spt_01krdgeqcxet5s7t44vh8rt9mg"
}
}
}'默认路由的检查方式与号码自身的路由相同。要将号码恢复为默认路由,请将其路由设置为 null,或在号码上选择 使用工作区默认值。
将来电转接到另一个号码
转接会先接听来电,再向您已验证的号码发起第二个呼叫,然后将两者连接起来。
- 打开 Voice > 号码,打开该号码,在 入站路由 下选择 转接到其他号码。
- 选择要转接到的号码。列表中显示的是您已验证的主叫号码,因为转接只能指向您已证明由您控制的号码。
- 选择转接呼叫向被叫方显示哪个号码作为主叫方,然后点击 保存。
通过 API,搜索您的语音号码以查找该号码的数字来读取其 vnu_ ID,然后发送一条包含 forward_to 的 forward 路由和 forward_as:
const number = await bird.voice.numbers.update("NUMBER_ID", {
inbound_configuration: {
route: { type: "forward", forward_to: "+14155551234", forward_as: "dialed_number" },
},
});
console.log(number.id, number.inbound_configuration?.route?.type);var route bird.VoiceCallRouteWritable
if err := route.FromVoiceCallRouteForward(bird.VoiceCallRouteForward{
ForwardTo: "+14155551234",
ForwardAs: "dialed_number",
}); err != nil {
log.Fatal(err)
}
number, err := client.Voice.Numbers.Update(context.Background(), "NUMBER_ID", bird.VoiceNumbersUpdateParams{
InboundConfiguration: &bird.VoiceInboundConfigurationPut{Route: route},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(number.Id)$number = $bird->voice->numbers->update(
'NUMBER_ID',
(new VoiceNumberUpdate())->setInboundConfiguration(
(new VoiceInboundConfigurationPut())->setRoute([
'type' => 'forward',
'forward_to' => '+14155551234',
'forward_as' => 'dialed_number',
]),
),
);
echo $number->getId(), "\n";bird voice numbers update <number-id> --route forward --forward-to +14155551234 --forward-as dialed_numbercurl -X PATCH "https://{region}.platform.bird.com/v1/voice/numbers/{number_id}" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"inbound_configuration": {
"route": {
"type": "forward",
"forward_to": "+14155551234",
"forward_as": "dialed_number"
}
}
}'forward_to 必须是验证已完成的主叫号码。未注册的号码或验证处于 pending 或 failed 状态的号码会被拒绝,并返回 412 和 E21053。主叫号码介绍了如何通过 API 注册和验证主叫号码。
转接目标在您设置时和每次转接呼叫时都会被检查。您之后移除的主叫号码会导致转接停止而非继续进行,此后的来电将被拒绝。
转接呼叫会振铃 45 秒后放弃,比中继递送的振铃时间更长,因为远端通常是个人手机而非电话系统。
转接会发起一个呼叫,因此出站规则适用于第二段呼叫:转接到您未在目的地中开启的国家会被拒绝,并返回 destination_not_enabled。
一次转接,两条通话记录
一次转接呼叫会产生共享同一个 call_id 的两条通话分段记录:
| 记录 | 含义 |
|---|---|
| 到达的呼叫 | direction 为 inbound,route 表明该号码被设为转接、转接到哪个号码,以及转接段呈现了哪个号码 |
| 转接的呼叫 | direction 为 outbound,从该段呈现的号码拨往您转接到的号码。它不携带自己的 route |
在通话分段列表中使用 call_id 筛选器查找相关连接,或打开 Voice > 通话。到达的记录说明来电到达时号码配置的处理方式。
选择转接呼叫向被叫方显示哪个号码作为主叫方
转接呼叫有两个号码可以向接听者呈现,该选择既影响对方看到的内容,也影响运营商干预该呼叫的可能性:
- 主叫号码即来电者自己的号码,因此电话振铃时看起来就像对方直接拨打的一样,可以从通话记录中回拨。由于该号码不是您拥有的,一些运营商(最常见于美国和欧洲部分地区)会将此类呼叫标记为未验证、替换号码或进行筛选。
- 被叫号码即来电者拨打的号码,是您的号码之一。接听者看到的是您的哪个号码被拨打,而非谁打来的。
通过 API 或 CLI 写入每个转接时都必须明确指定该选择。没有存储选择的旧配置在读取时会返回被叫号码。
号码的 inbound_configuration.forward_as_options 列出了编辑者可用的选项。当前选项包括主叫号码和被叫号码。构建集成时请读取这些选项,并使用返回的 forward_as 确认生效的设置。
在读取时,forward_as 是呼叫实际携带的值,可能与上次写入的值不同。
查看号码对来电的处理方式
来电记录在状态旁携带一个 route,route 是处理该呼叫时号码所设定的方式。之后更改号码的设置不会改变其过去呼叫的记录内容。
route.type | 号码的处理方式 |
|---|---|
trunk | 呼叫被递送到 trunk_id 中指定的 SIP 中继 |
forward | 呼叫被转接到 forward_to 中的号码,呈现 forward_as 中的号码 |
reject | 号码拒绝了该呼叫 |
sequence | 呼叫选择了 sequence_id 中的序列和 entry_node_id 中的入口 |
route 表示号码被设定的处理方式,而非该方式是否成功。在未接通的呼叫上出现 trunk 路由,表示号码指向了一个未接受该呼叫的中继,呼叫的状态才是承载结果的字段。route 在出站呼叫和该字段存在之前记录的呼叫上不存在。
在仪表板中,从 Voice > 通话分段 打开通话记录,查看 入站路由 行,该行链接到做出路由决定的号码。通过 API,route 位于 GET /v1/voice/legs/{leg_id} 和 GET /v1/voice/legs 上,direction 可将列表筛选为来电。
对于序列路由,还应查看序列的运行记录页面,以确认实际运行的入口和版本。号码的当前配置可能与之前通话保留的版本不同。
诊断被拒绝的来电
被拒绝的来电会以状态 rejected 记录。有两种不同的原因会产生该状态,rejection_reason 是区分它们的依据:
- 没有
rejection_reason的拒绝。 号码本身拒接了通话。通话没有在我们的任何检查中失败,因此未列出原因;route表示号码配置的处理方式。reject路由表示号码被设为拒接,或号码自身没有路由而工作区默认路由为拒绝。 - 有
rejection_reason的拒绝。 该呼叫在到达您的电话系统之前未通过我们的某项检查。原因中会标明是哪项检查。被拒绝的呼叫列出了所有原因及其修复方法。
failed 是另一种状态,不代表被拒绝:它表示呼叫已尝试但未成功,sip_response_code 携带了返回的响应。
将 route 和 rejection_reason 放在一起查看,以区分不同的拒绝情况:
route 与原因 | 原因 |
|---|---|
reject,无原因 | 该号码自身的路由设置为拒接,或者它没有自己的路由且工作区默认路由为拒接。打开该号码查看具体情况。删除 trunk 或关闭其呼入功能可能导致原本正常工作的号码落入此状态 |
trunk,no_route_found | 该号码指向了一个 trunk,但该 trunk 没有可用的网关来投递呼叫。在 trunk 页面上添加一个网关 |
forward,无原因 | 转接目标不再是已验证的来电显示号码。在来电显示号码中重新验证,或转接到其他号码 |
forward,destination_not_enabled | 无法向转接目标所在国家/地区发起第二段呼叫。在目的地中开启该国家/地区 |
账户限额同样适用于来电:超出钱包余额、组织的每日语音支出限额,或并发和每秒限额时,来电将以相应原因被拒绝。语音概览涵盖了这些限额本身。
查看接收来电的费用
接收来电是收费的。费率取决于接收来电号码的国家和类型,按国家发布在语音定价页面的 接收来电 下,与您拨出呼叫的费率并列。
转接按两次呼叫计费:到达的呼叫按接收费率计费,我们发起的通话分段按您转接到的号码的出站费率计费。单次处理费按整个呼叫收取一次,而非按每段收取。
来电递送前会检查钱包余额,因此余额不足以支付时来电会被拒绝,而非事后向您收费。费用与计费涵盖了可计费时长、费率和钱包在双向呼叫中的工作方式。