🔰

Vonage Voice API ガイド

2024/09/19に公開

Vonage Hackathon 2024

こんにちは。KDDI ウェブコミュニケーションズの小原です。

このガイドでは、Vonage API を利用した電話の受発信と、トラブルシューティングについて案内します。

Voice API とは

Vonage(ボネージ)の Voice API では、REST API と JSON を使って次のことが実現できます。

  • 電話の受発信
  • 050/0120/0800 番号の取得
    • 0120/0800 は持ち込み可能
  • IVR
    • DTMF / 音声認識(ASR)
  • 録音
  • テキストの読み上げ(Text-to-Speech)
  • 音声ファイルの再生
  • 通話転送
  • グループ通話
  • 匿名通話
  • WebRTC を利用したブラウザ、アプリ、電話との通話
  • WebSocket 連携
  • SIP Trunking

Voice API はコールセンターはもちろん、飲食店や宿泊、医療、美容、配車の予約システムや、障害通知、相談サービスなど、さまざまな商用サービスに利用できます。

仕様

  • 秒課金
  • 3 CPS(1 秒間 3 発信)
  • 電話番号は E.164/MSISDN 形式 (例:8190XXXXYYYY)

CPS

CPS は 1 秒間に API Endpoint に HTTP POST できる件数です。

3 CPS を超えると Vonage は 429 Too Many Requests エラーを返します。Vonage はキューを持たないため、エラーが戻った場合は 3 CPS を超えないように再リクエストする必要があります。

同時コール数

同時コール数は、同時刻に存在するコール数です。たとえば 1 コールあたり 1 分の通話であれば、33 秒間 3 CPS で発信すると 99 同時コール数になります。

受発信合わせて 100 同時コール数を超える場合や、3 CPS 以上の大量コールが必要な場合は、利用内容と想定数を添えて KWC までお問い合わせください。

料金表

Vonage は初期費用や月額固定料金のない従量課金です。

発着信料金と WebSocket は、通話時間の秒を月末に合算し、分に切り上げて課金します。

たとえば 11 秒と 22 秒の通話であれば、月末に合算されて 33 秒になり、分に切り上げて 1 分として課金します。

もし分課金であれば、11 秒→ 1 分、22 秒→ 1 分で合計 2 分になります。Vonage の秒課金であれば 1 分です。分課金に比べて、秒課金は短時間のコールにおいて 30 ~ 40% 安くなります。

電話番号料

月額 (税込)
050 165円
0120/0800 2,200円
  • 取得日から 1 か月単位で課金されます。日割りはありません
  • 電話番号リリース(削除)後の再取得(復活)について
    • 海外番号の再取得はできません
    • 日本の電話番号は再取得が可能な場合があるためお問い合わせください。再取得が可能な場合でも、内部システムの処理状況により数営業日から数週間かかることをご了承ください

発信料

分(税込)
固定電話 5.5円
携帯電話 18.0円
In-App Voice (WebRTC) 0.6円
SIP 0.6円

着信料

分(税込)
050 0.6円
0120/0800 18.0円
In-App Voice (WebRTC) 0.6円

その他料金

税込 備考
Text-to-Speech (TTS) 無料 テキストの音声読み上げ。男女三種類ずつ
SSML 無料 TTS の読み上げ速度や、間の制御
録音 無料 録音ファイルは 30 日後に自動削除
Premium TTS 0.6円 100 文字単位。TTS の高品質版
ASR 2.5円 15 秒単位。Speech-to-Text とも表記
WebSocket 0.6円 分単位

要件

Voice API を利用した電話の受発信には下記が必要です。

本ガイドは下記が必要です。

  • 簡単なプログラム知識
  • Webhook サーバ

このガイドは KWC 契約の URL を案内しています。もし Vonage 契約の場合は URL を読み替えてください。

契約先 ダッシュボード
KWC 契約 https://kwc.dashboard.vonage.com/
Vonage 契約 https://dashboard.nexmo.com/

Vonage 契約と KWC 契約で機能の差はありません。違いや、全体的な開発情報は
Vonage の開発スタートガイド をご参照ください。

共通設定

電話の受発信には共通して「Webhook サーバ」と「アプリケーション」の設定が必要です。

Webhook サーバ

Webhook サーバは、Vonage から送られる電話の着信や、通話の応答や終了といった情報を受け取るサーバです。Webhook は 3 種類あります。

種別 要件 用途
回答 URL 必須 着信時のアクションを定義
イベント URL 必須 発信時の進捗状況(応答、話中、切断など)を非同期で通知
フォールバック URL オプション 回答 URL かイベント URL の障害時に通知

Webhook サーバは Vonage からアクセスできるサーバであれば、AWS や自社サーバなど、どこでも構いません。本ガイドでは ngrok を利用します。

簡単な Webhook サーバを作成します。まずは Python のパッケージをインストールします。

pip install "uvicorn[standard]"
pip install fastapi
pip install vonage

下記を webhook.py として保存。

webhook.py
import json

from fastapi import FastAPI
from fastapi.responses import JSONResponse

app = FastAPI()


@app.get("/answer")
async def answer(vonage_request: dict = {}):
    ncco = [{"action": "talk", "text": "こんにちは、ボネージです", "language": "ja-JP"}]
    return JSONResponse(content=ncco)


@app.post("/event")
async def event(vonage_request: dict = {}):
    return vonage_request


@app.post("/fallback")
async def fallback(vonage_request: dict = {}):
    return vonage_request

Webhook サーバを立ち上げます。

$ uvicorn webhook:app --reload
INFO:     Will watch for changes in these directories: ['/home/kwcplus/vonage-voice-guide']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [18470303] using StatReload
INFO:     Started server process [18760303]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

次に新しいターミナルを開き、ngrok を起動します。

$ ngrok http 8000
(snip)
Web Interface   http://127.0.0.1:4040
Forwarding      https://your-sub-domain.ngrok-free.app -> http://localhost:8000

ngrok の Forwarding に表示された URL のメモをお願いします。この例では https://your-sub-domain.ngrok-free.app/ になります。Vonage に Webhook として登録するドメイン名になります。

下記が出力されたら Webhook サーバの起動に成功です。

$ curl https://your-sub-domain.ngrok-free.app/answer
[{"action":"talk","text":"こんにちは、ボネージです","language":"ja-JP"}]

Webhook の参考資料

アプリケーション

Vonage の「アプリケーション」は、利用する電話番号や、Voice API を使うための認証情報、Webhook を設定する単位です。

具体的な使い方として、本番と開発で環境を分けたり、予約受付システムと障害通知サービスのようにサービスごとに環境を分けたりするために使います。

アプリケーションの作成

アプリケーションの作成は、ダッシュボードの画面左にある「アプリケーション」から「新しいアプリケーションを作成する」をクリックします。

アプリケーションを作成する

  1. 「名前」に Voice API のテスト と入力
  2. 「公開鍵と秘密鍵を生成」をクリック。秘密鍵の private.key がダウンロードされるため、大切に保管してください
  3. 機能の「音声」にチェック
    1. 「回答 URL」を HTTP GET にして https://your-sub-domain.ngrok-free.app/answer と入力
    2. 「イベント URL」を HTTP POST にして https://your-sub-domain.ngrok-free.app/event と入力
    3. 「フォールバック URL」を HTTP POST にして https://your-sub-domain.ngrok-free.app/fallback と入力
  4. 「新しいアプリケーションの生成」をクリック

「新しいアプリケーションの生成」をクリックするとアプリケーション ID が発行されます。

アプリケーションに電話番号をリンク

  1. 「アプリケーション ID」が発行されるのでメモ
  2. 取得した電話番号の「リンク」をクリック。「リンク」が「リンク解除」になれば成功

以上でアプリケーションの作成は完了しました。

アプリケーションの参考資料

着信

Vonage で取得した番号に電話してみましょう。

「こんにちは、ボネージです」と流れましたか?

これは、アプリケーションにリンクした電話番号に着信したことにより、Vonage が「回答 URL」を呼んだためです。

NCCO

回答 URL の /answer は下記 JSON を返します。

[
  {
    "action": "talk",
    "text": "こんにちは、ボネージです",
    "language": "ja-JP"
  }
]

Vonage は電話着信時のアクションを NCCO (Nexmo Call Control Object) と呼ばれる JSON で制御します。Nexmo は Vonage が買収した会社名です。

NCCO のアクション

NCCO には便利なアクションが用意されています。

アクション 概要
record 通話の録音。録音と保存ともに無料。30 日後に自動削除
conversation 複数人との通話や、保留
connect 電話の転送
talk テキストの読み上げ。SSML も無料で利用可能
stream mp3 か wav(16bit) の再生
input IVR。DTMF は無料ですが、音声認識は有料
notify 指定 URL に POST/GET

回答 URL の ncco を connect アクションにすることで電話転送され、input アクションなら IVR を実現できます。

webhook.py
@app.get("/answer")
async def answer(vonage_request: dict = {}):
    ncco = [{"action": "talk", "text": "こんにちは、ボネージです", "language": "ja-JP"}]
    return JSONResponse(content=ncco)

NCCO の参考資料

回答 URL へのリクエスト

/answer には、電話着信時に Vonage から from/to が JSON で渡されます。CRM や DB と連携することで、発信者の特定が可能です。

Webhook へのリクエストは ngrok の Web Interface で確認できます。

{
    "from": "8180xxxxyyyy",
    "to": "8150xxxxyyyy",
    "uuid": "xxxx6ed87e66bd731aba84cca626xxxx",
    "conversation_uuid": "CON-a6077a9f-xxxx-xxxx-xxxx-02f43861d28f",
    "timestamp": "2024-08-22T14:51:38.554Z"
}

発信

発信の前に、Vonage で扱う電話番号の形式の案内と、環境変数の設定をします。

E.164/MSISDN

Vonage で取得した番号は (+81) 50XXXXYYYY と表記されています。Voice API に指定する電話番号は、日常で利用する 050-XXXX-YYYY は利用できません。

Vonage は世界中に受発信ができるため、国際電話番号を付与する必要があります。日本の国際電話番号は 81 です。そして電話番号の先頭の 0 を国際電話番号に置き換えます。この形式を E.164 もしくは MSISDN と呼びます。

電話番号 050-XXXX-YYYY
E.164/MSISDN 形式 8150XXXXYYYY

Vonage に設定する電話番号は、+ や ( ) のない数字のみの形式にします。

環境変数の設定

コードに埋め込むべきではない情報を .envrc ファイルに書き、環境変数として使えるようにします。

.envrc
#!/bin/env bash

# アプリケーション ID
# VONAGE_APPLICATION_ID=YOUR-APPLICATION-ID-1234-5678
export VONAGE_APPLICATION_ID=

# ダウンロードした private.key のフルパス
# VONAGE_APPLICATION_PRIVATE_KEY_PATH=/home/you/work/directory/private.key
export VONAGE_APPLICATION_PRIVATE_KEY_PATH=

# イベント URL
# Ex. VONAGE_EVENT_URL=https://your-sub-domain.ngrok-free.app/event
export VONAGE_EVENT_URL=

# リンクした電話番号(E.164)
# VONAGE_NUMBER=8150xxxxxxxx
export VONAGE_NUMBER=

source もしくは direnv で .envrc を読み込み、環境変数が設定されたか確認します。

source .envrc
echo $VONAGE_APPLICATION_ID
echo $VONAGE_APPLICATION_PRIVATE_KEY_PATH
echo $VONAGE_EVENT_URL
echo $VONAGE_NUMBER

call.py の保存

電話を発信する下記コードを call.py として保存します。

call.py
import os
import sys
from pprint import pprint

import vonage

VONAGE_APPLICATION_ID = os.environ.get("VONAGE_APPLICATION_ID")
VONAGE_APPLICATION_PRIVATE_KEY_PATH = os.environ.get(
    "VONAGE_APPLICATION_PRIVATE_KEY_PATH"
)
VONAGE_EVENT_URL = os.environ.get("VONAGE_EVENT_URL")

FROM_NUMBER = sys.argv[1]
TO_NUMBER = sys.argv[2]

client = vonage.Client(
    application_id=VONAGE_APPLICATION_ID,
    private_key=VONAGE_APPLICATION_PRIVATE_KEY_PATH,
)

response = client.voice.create_call(
    {
        "to": [{"type": "phone", "number": TO_NUMBER}],
        "from": {"type": "phone", "number": FROM_NUMBER},
        "event_url": [VONAGE_EVENT_URL],
        "ncco": [
            {
                "action": "talk",
                "text": "こんにちは、K W C PLUS です",
                "language": "ja-JP"
            }
        ]
    }
)

pprint(response)

call.py の実行

第一引数に From になる $VONAGE_NUMBER。第二引数に To の電話番号を指定します。自分の電話番号が 080-XXXX-YYYY でしたら E.164 形式の 8180XXXXYYYY にします。

$ python call.py $VONAGE_NUMBER 8180XXXXYYYY
{'conversation_uuid': 'CON-6a9d466b-xxxx-xxxx-xxxx-d8b480e660f9',
 'direction': 'outbound',
 'status': 'started',
 'uuid': '44ce7f3b-xxxx-xxxx-xxxx-b4bac2d03ab6'}

$VONAGE_NUMBER から電話が来ましたか?

Voice API の参考資料

イベント URL

イベント URL には非同期で電話の状態が届きます。

相手が応答し、会話後に通話終了であれば started → ringing → answered → completed が届きます。

相手がほかの電話に出ている場合は started → ringing → busy → completed になります。

busy であれば時間をおいて再架電するといった使い方ができます。

status 概要
started API を受け付け、発信の準備開始
ringing 呼び出し開始
answered 応答。留守番電話も含まれます
busy 他のコールと通話中
unanswered 呼び出しに応答しない。呼び出し音の時間切れなど
rejected 着信拒否
failed コール失敗
completed 発信処理の終了

Webhook に届く JSON のサンプルは下記です。

started

Vonage が API を受け付けた状態です。まだ電話発信をしていません。

{
    "headers": {},
    "from": "8150XXXXYYYY",
    "to": "8190XXXXYYYY",
    "uuid": "6cc93b0c-xxxx-xxxx-xxxx-6514a26de23e",
    "conversation_uuid": "CON-4a7d12ea-xxxx-xxxx-xxxx-7cc9d1a7aee8",
    "status": "started",
    "direction": "outbound",
    "timestamp": "2024-07-17T16:34:45.147Z"
}

ringing

相手の端末に呼び出し音を鳴らすようにキャリアに依頼を開始。

{
    "headers": {},
    "from": "8150XXXXYYYY",
    "to": "8190XXXXYYYY",
    "uuid": "296c0a20-xxxx-xxxx-xxxx-e534d83442d8",
    "conversation_uuid": "CON-130a76cc-xxxx-xxxx-xxxx-f243b21144cb",
    "status": "ringing",
    "direction": "outbound",
    "timestamp": "2024-07-17T16:13:49.585Z"
}

answered

相手が着信に応答した状態です。通話開始時刻の start_time は null です。completed で取得が可能です。

{
    "start_time": null,
    "headers": {},
    "rate": null,
    "from": "8150XXXXYYYY",
    "to": "8190XXXXYYYY",
    "uuid": "8f31079e-xxxx-xxxx-xxxx-c5a705b40a11",
    "conversation_uuid": "CON-4f4469d3-xxxx-xxxx-xxxx-ca30faef9ea6",
    "status": "answered",
    "direction": "outbound",
    "network": null,
    "timestamp": "2024-07-18T03:47:57.512Z"
}

unanswered

相手が着信に応答しない場合です。

detail には unavailable と timeout があります。

unavailable は発信専用の電話番号のため着信を受け付けていない場合や、キャリアの障害や端末の故障などで発生します。

timeout は ringing の時間切れです。Vonage はデフォルトで 60 秒間 ringing します(ringing_timer で秒数は変更可能です)。

この ringing の時間は、キャリアや相手端末の設定により短くなることがあります。

{
    "from": "8150XXXXYYYY",
    "to": "81800XXXYYY",
    "detail": "unavailable",
    "uuid": "bd003f74-xxxx-xxxx-xxxx-37bb0abe7bb6",
    "conversation_uuid": "CON-d0e00686-xxxx-xxxx-xxxx-f3a3f5cde3f5",
    "status": "unanswered",
    "direction": "outbound",
    "timestamp": "2024-07-17T16:25:19.462Z"
}

rejected

detail 概要
invalid_number 番号形式の間違い
restricted キャリアによる拒否
declined 宛先による拒否
{
    "from": "8150xxxxyyyy",
    "to": "81120xxxyyy",
    "detail": "restricted",
    "uuid": "09b07119-xxxx-xxxx-xxxx-f5c9712182a2",
    "conversation_uuid": "CON-29686127-xxxx-xxxx-xxxx-3da085db71b7",
    "status": "rejected",
    "direction": "outbound",
    "timestamp": "2024-08-31T12:46:49.296Z"
}

busy

相手が他のコールと電話中だったり、iPhone であれば着信時に電源ボタンを押すことでも busy になります。

キャリアや端末によっては unanswered になることもあります。

{
    "from": "8150XXXXYYYY",
    "to": "8190XXXXYYYY",
    "uuid": "296c0a20-xxxx-xxxx-xxxx-e534d83442d8",
    "conversation_uuid": "CON-130a76cc-xxxx-xxxx-xxxx-f243b21144cb",
    "status": "busy",
    "direction": "outbound",
    "timestamp": "2024-07-17T16:13:54.222Z"
}

failed

Vonage の内部的なエラーが発生している場合は failed になります。

Vonage API Status障害情報一覧 をご確認ください。

detail 概要
cannot_route 宛先が発信対応していない、もしくはブロックされている
number_out_of_service 番号が利用できない
internal_error Vonage の内部的なエラー
{
    "from": "8150xxxxyyyy",
    "to": "8170xxxxyyyy",
    "detail": "internal_error",
    "uuid": "09b07119-xxxx-xxxx-xxxx-f5c9712182a2",
    "conversation_uuid": "CON-29686127-xxxx-xxxx-xxxx-3da085db71b7",
    "status": "failed",
    "direction": "outbound",
    "timestamp": "2024-08-31T12:46:49.296Z"
}

completed ( answered の場合 )

busy/unanswered/rejected/failed の completed と異なり、通話終了時刻の end_time と通話秒数の duration が追加されます。

{
    "headers": {},
    "end_time": "2024-07-17T16:34:50.000Z",
    "uuid": "6cc93b0c-xxxx-xxxx-xxxx-6514a26de23e",
    "network": "44020",
    "duration": "3",
    "disconnected_by": "platform",
    "start_time": "2024-07-17T16:34:47.000Z",
    "rate": "0.10909000",
    "price": "0.00545450",
    "from": "8150XXXXYYYY",
    "to": "8190XXXXYYYY",
    "conversation_uuid": "CON-4a7d12ea-xxxx-xxxx-xxxx-7cc9d1a7aee8",
    "status": "completed",
    "direction": "outbound",
    "timestamp": "2024-07-17T16:34:50.218Z"
}

completed ( answered 以外の場合 )

{
    "headers": {},
    "uuid": "296c0a20-xxxx-xxxx-xxxx-e534d83442d8",
    "network": "44020",
    "duration": "0",
    "disconnected_by": "user",
    "start_time": null,
    "rate": "0.00000000",
    "price": "0.00000000",
    "from": "8150XXXXYYYY",
    "to": "8190XXXXYYYY",
    "conversation_uuid": "CON-130a76cc-xxxx-xxxx-xxxx-f243b21144cb",
    "status": "completed",
    "direction": "outbound",
    "timestamp": "2024-07-17T16:13:54.321Z"
}

イベント URL の参考資料

フォールバック URL

フォールバック URL は、回答 URL もしくはイベント URL がタイムアウトや、2XX を返さない場合に 2 回の施行後、呼ばれます。

タイムアウトの条件は下記です。

  • 3 秒以内の TCP 接続の確立
  • 5 秒以内の NCCO の応答

フォールバックした理由(reason)と、発生した URL(original_request)が JSON で送られます。該当 URL のサーバログをご確認ください。

{
  "reason": "Connection closed.",
  "original_request": {
    "url": "https://api.example.com/webhooks/event",
    "type": "event"
  }
}

ログ

音声ログ

音声ログは 30 日後に自動削除されます。30 日以上のログが必要な場合は Reports API(有料)か、イベント URL を独自に保存する必要があります。

「時間範囲」のデフォルトは過去 1 時間です。1 時間以上前のログは時間範囲を変更する必要があります。

「Type」のデフォルトは「アプリ内通話(WebRTC)」のため、「電話(PSTN/SIP)」を選ばないと電話のログを見ることができません。

通話に失敗したログは「拒否」タブにあります。

音声ログ

Voice Inspector

Voice Inspector は音声ログの詳細を確認できます。

音声ログか、デベロッパーツールの Voice Inspector からアクセスできます。

Voice Inspector

NCCO を動的に生成している場合は、Vonage が取得した NCCO を確認できるため、デバッグに役立つことがあります。

Voice Inspector の URL はダッシュボードと URL が異なるため、認証が必要になることがあります。

Voice Inspector の参考資料

音声分析

音声分析 は、リクエスト数や通話時間を日単位、月単位で確認できます。半年以上前のデータも確認可能です。

音声分析

音声配信

音声配信は通話のエラー状況が確認可能です。

音声配信

指標 概要
ASR busy や着信拒否を失敗とした通話の成功率。ユーザの指標になります
NER unanswered や busy といったユーザの行動によるものは成功とした通話の成功率。通信事業者にとっての指標
Reason SIP response code
PDD ダイヤルしてから呼び出し音がなるまでの秒数。6 秒以下が許容範囲とされています。ユーザが PDD を短縮する方法はありません
Avg 平均通話時間

トラブルシューティング

着信しない、もしくは発信しない場合は下記項目をご確認ください。

着信しない

音声ログの有無を確認してください。

音声ログがない(着信しない)

アプリケーションの設定を見直してください。

  1. 発信先に指定した電話番号の入力間違い
  2. アプリケーションに電話番号がリンクされているか
  3. アプリケーションに応答 URL が設定されているか
    1. 応答 URL がブラウザでアクセスできるか
    2. アプリケーションの応答 URL 欄の前後に空白文字がないか
    3. 応答 URL の GET と POST が正しいか
  4. あなたの番号にある鉛筆アイコンをクリックし、「転送先」が None で、かつ「アプリケーション設定あり」にアプリケーション名があることを確認
    あなたの番号
  5. Webhook が Basic 認証を利用している場合、アプリケーションの「高度な機能を表示する」をクリックし、「署名済み Webhook」のチェックが外れているか
    • 高度な機能を表示する
    • 署名済み Webhook のチェックを外す

音声ログがある(着信しない)

  1. イベント URL の status と内容を確認
  2. Voice Inspector の NCCO を確認
  3. 回答 URL のサーバもしくはプログラムのログを確認
    1. サーバは 2XX を返しているか
    2. プログラムがエラーを出していないか

発信しない

音声ログの有無を確認してください。

音声ログがない(発信しない)

アプリケーションの設定を見直してください。また、電話番号の形式が E.164/MSISDN かも確認してください。

  1. プログラムに指定した秘密鍵のパスや、アプリケーション ID の認証情報が正しいか
  2. 電話番号の宛先間違いがないか
  3. プログラムがエラーを出していないか

音声ログがある(発信しない)

  1. 電話番号の宛先間違いがないか
  2. イベント URL の status と内容を確認
KWCPLUS

Discussion