2023年5月1日を持ちまして、株式会社KDDIウェブコミュニケーションズのTwilioリセール事業が終了したため、本記事に記載されている内容は正確ではないことを予めご了承ください。
はじめに
本記事は、2019年5月27日に開催された「Twilio x NCMBハンズオン」のTwilioのパートで利用したものです。
実施にあたって、すでにTwilioのアカウントを取得していることとします。もしまだ取得していない場合は、こちらからオンラインサインアップしてください。
ハンズオン手順
今回のハンズオンでは、スマホからRestAPI経由でTwilio Studioを呼び出し、呼び出されたフローから電話を架けます。相手が応答するとメッセージを流して、なにかキー入力を行います。入力されたキーは、Twilio Syncを使ってアプリに通知します。
この流れを実装するために、このパートで実施する手順は以下のとおりです。
- Syncサービスの作成とAPIキー生成
- Token生成用Functionの生成
- 応答キーのSync書き込みFunctionの生成
- 電話番号の購入
- Twilio Studioのフロー作成
ハンズオン
それでは早速始めていきましょう。
Syncサービスの作成とAPIキー生成
Twilio Syncとは
- Twilio Sync
- 複数のデバイス間で情報を同期させるためのサービス
- WebSocketやSocket.ioのようなリアルタイム通信プラットフォーム
- Syncは、受け取ったデータをTwilio上に保存してから配信する
- Sync上のデータは、TTLを設定するか、明示的に削除しない限り消えない
- 月10,000アクションまでは無料、それ以降は1000アクション単位で1.5円
- Documents / Lists / Maps / Message Streams の4種類のデータを扱うことが可能
まずは、新しいSyncサービスを作成し、API KeyとAPI Tokenを発行します。
- 管理コンソールのスライドメニューから、DEVELOPER TOOLSの中のSyncを選択します。
- Syncメニューの中からServicesを選択します。
- 赤い**+**アイコンを押して、新しいSyncサービスを作成します。
- 新規Sync Serviceの作成ダイアログが表示されるので、名前欄に「NCMB」と入力し、Createボタンを押します。

- SERVICE SID(ISから始まる文字列)をメモ帳に保存しておきます。
- Syncメニューの**< Back**をクリックして、Syncサービスの一覧画面に戻ります。
- Syncメニューのツールを選択し、APIキーの一覧が表示されるのを確認します。
- Create API Keyボタンを押すか、**+**アイコンを押して、新しいAPI Keyを作成します。
- 名前欄に「NCMB」と入力し、キータイプは「Standard」を選択します。

- APIキーを作成するボタンを押します。
- 表示されるSID(SKから始まる文字列)と、SECRETに表示されている文字列の両方をメモ帳に保存します。
- **完了しました!**のチェックボックスにチェックを入れて、終了ボタンを押します。
Token生成用Functionの生成
上記手順でTwilio SyncのAPIキーを生成しましたが、実際にSyncを利用するためには、作成したAPIキーとSecretをつかって、Tokenを生成する必要があります。Tokenには有効期限があり、期限を超えるとTokenは使えなくなります。
Token自体は文字列で、TwilioにはTokenを作成するためのSDKが用意されています。今回は、Twilio Functionsというサーバーレス実行環境を使って、Tokenを動的に生成するためのプログラムを作ります。
- 管理コンソールから、Runtime > Functions > 設定を選択します。
- Enable ACCOUNT_SID and AUTH_TOKENのチェックボックスをONにします。
- Environmental Variablesの赤い**+**ボタンを3回押して、KEY/VALUE欄を3つ追加します。
- それぞれのKEY/VALUEを以下のように設定します。
KEY | VALUE |
---|---|
SYNC_SERVICE_SID | ISから始まるSyncサービスのSID |
TWILIO_API_KEY | SKから始まるAPIキー |
TWILIO_API_SECRET | APIキーとセットで設定されたSECRET |
- Saveボタンを押して設定を保存します。
- RuntimeメニューのFunctions > Manageを選択します。
- Create a Functionボタンを押すか、もしくは赤い**+**アイコンをクリックして、新しいFunctionを生成します。
- テンプレートの一覧からBlankを選択して、Createボタンを押します。
- FUNCTION NAMEに「SyncToken」と入力し、PATHには、「/sync-token」と入力します。

- ACCESS CONTROLのチェックは外しておきます。
- CODE欄に書かれているコードを以下のコードに置き換えます。
exports.handler = function(context, event, callback) {
const ACCOUNT_SID = context.ACCOUNT_SID;
const SERVICE_SID = context.SYNC_SERVICE_SID;
const API_KEY = context.TWILIO_API_KEY;
const API_SECRET = context.TWILIO_API_SECRET;
const IDENTITY = context.DOMAIN_NAME;
const AccessToken = Twilio.jwt.AccessToken;
const SyncGrant = AccessToken.SyncGrant;
const syncGrant = new SyncGrant({
serviceSid: SERVICE_SID
});
const accessToken = new AccessToken(
ACCOUNT_SID,
API_KEY,
API_SECRET
);
accessToken.addGrant(syncGrant);
accessToken.identity = IDENTITY;
if (event.callback) {
let response = new Twilio.Response();
response.setBody(`${event.callback}(${JSON.stringify({token: accessToken.toJwt()})})`);
response.appendHeader('Content-Type', 'application/javascript');
callback(null, response);
} else {
callback(null, {
token: accessToken.toJwt()
});
}
};
- Saveボタンを押して、デプロイされるのを待ちます。
- デプロイが完了したら、PATH欄のURLをコピーして、別のブラウザタブで実行してみます。
- JSON形式の長い文字列が表示されたら、トークンの生成は成功です。
応答キーのSync書き込みFunctionの生成
次に、電話に応答したあとに押されたキーをSyncに書き込むFunctionを作りましょう。
Syncで共有できるデータには、Documents、Maps、Listsがありますが、今回の目的であれば単純なKey/Valueで良いかと思いますので、Documentsを使うことにします。
Documentsオブジェクトとは
- 単一のJSON形式オブジェクト
- 1データのサイズは最大16KB
- 単純な値のPUB/SUBに向いている
- 履歴を取るような使い方には向いていない
- JavaScript SDKを使うと、Documentオブジェクトの生成、更新、購読、削除が可能
- サーバーサイドのREST API経由でも管理可能
作成するDocumentの例(Keyが電話番号、押されたキー値がValue)
{
+819012345678:1
}
これから作成するFunctionは、Twilio Studioから呼ばれます。呼ばれたときに、パラメータとして電話番号と押されたキー値を受け取ります。パラメータの名前は以下の通り定義しておきます。
パラメータ名 | 内容 |
---|---|
number | 電話番号 |
digit | 押されたキー値 |
- 管理コンソールのRuntimeメニューのFunctions > Manageを選択します。
- 赤い**+**アイコンをクリックして、新しいFunctionを生成します。
- テンプレートの一覧からBlankを選択して、Createボタンを押します。
- FUNCTION NAMEに「SetDigit」と入力し、PATHには、「/set-digit」と入力します。
- ACCESS CONTROLのチェックは外しておきます。
- CODE欄に書かれているコードを以下のコードに置き換えます。
exports.handler = function(context, event, callback) {
const number = event.number || '';
const digit = event.digit || '';
let sync = Runtime.getSync({serviceName: context.SYNC_SERVICE_SID});
let payload = {};
payload[number] = digit;
sync.documents("Digits")
.update({
data: payload
}).then(response => {
console.log(response)
callback(null, 'OK')
}).catch(error => {
sync.documents.create({
uniqueName: "Digits",
data: payload
}).then(response => {
console.log(response)
callback(null, 'OK')
}).catch(error => {
console.log(error)
callback(error)
})
})
};
- Saveボタンを押して、デプロイされるのを待ちます。
- デプロイが完了したら、PATH欄のURLをコピーして、メモ帳に保存しておきます。
電話番号の購入
では次に、電話をかけるための電話番号を購入します。
今回は050番号を購入します。
すでに電話番号を購入している人は、その番号を使うこともできます。
もしまだ購入していない人は、以下の手順で番号を購入します。
- ブラウザでログイン画面を表示し、ご自分のIDとパスワードでログインをします。
- 管理コンソールの左側にあるボタンアイコンを押すと、スライドメニューが表示されます。
- スライドメニューの一覧から、電話番号を選択します。
- Phone Numbersメニューの中のBuy a Numberを選択します。
- 国のプルダウンから「Japan(+81)」を選択し、検索ボタンを押します。
- 一覧表示されたリストの中から、TYPEがローカルになっている(108円)の番号を一つ選び、購入ボタンを押します。
- この番号を購入しますか?というダイアログが出たら、**この番号を購入する。**を押します。
- Congratulationのダイアログが表示されたら、購入完了です。閉じるボタンを押します。
- Phone Numbersの中のManage Numbersを選択し、今購入した電話番号が表示されることを確認します。
- 購入した電話番号はメモ帳にコピーしておいてください。
Twilio Studioのフロー作成
さて、いよいよ最後の作業となります。
Twilioを使って電話を架けて、相手が応答したらメッセージを流して、キー応答を受け付けます。受け付けたキー情報は、先ほど作成したFunctionに渡していきます。
意外と難しそうに見えますが、Twilio Studioを使うとドラッグアンドドロップでコールフローを作成できます。
今回はすでに作成済みのフローをインポートして再利用したいと思います。
- 管理コンソールの左側のスライドメニューから、Studioを選択します。
- 赤いプラスアイコンをクリックするか、Create a new flowを選択して、新しいフローを作成します。
- FLOW NAMEに「CallPhone」と入力して、NEXTボタンを押します。
- New Flowダイアログが表示されるので、Import from JSONを選択して、Nextボタンを押します。
- New Flowダイアログが開きますので、以下のJSONを貼り付けます。
{
"description": "CallPhone",
"states": [
{
"type": "InitialState",
"name": "Trigger",
"properties": {
"offset": {
"x": 0,
"y": 0
},
"flow_url": "https://webhooks.twilio.com/v1/Accounts/ACxxxx/Flows/FWxxxx"
},
"transitions": [
{
"event": "incomingMessage",
"conditions": [],
"next": null,
"uuid": "28d3857d-4e53-48d1-9a3b-c91fc8ee3ff9"
},
{
"event": "incomingCall",
"conditions": [],
"next": null,
"uuid": "2d918680-d0bb-4e24-9565-40e45b21990b"
},
{
"event": "incomingRequest",
"conditions": [],
"next": "FFde9b38ef0d899aef5b9137de7f9345b1",
"uuid": "5f50d2ee-7b21-47f4-acb9-f5e4027de178"
}
],
"sid": "FFe9513435797fbd0ed4d6ca2c40b0d900"
},
{
"sid": "FFde9b38ef0d899aef5b9137de7f9345b1",
"name": "CallPhone",
"type": "Dial",
"properties": {
"from": "{{flow.channel.address}}",
"to": "{{contact.channel.address}}",
"timeout": 60,
"offset": {
"x": 26.444444444444457,
"y": 219.44444444444446
}
},
"transitions": [
{
"widgetId": "FFde9b38ef0d899aef5b9137de7f9345b1",
"uuid": "b5cf69e9-750c-4481-a051-81de390c9c3a",
"event": "answered",
"conditions": [],
"next": "FF48e69d9c562d7965f647c47339359c8a"
},
{
"widgetId": "FFde9b38ef0d899aef5b9137de7f9345b1",
"uuid": "dfbe008b-6dc0-4b9c-9f2c-1163967d5bfe",
"event": "answeredByMachine",
"conditions": [],
"next": null
},
{
"widgetId": "FFde9b38ef0d899aef5b9137de7f9345b1",
"uuid": "b515fd2d-b2fd-44a2-8efa-9095c4135cab",
"event": "busy",
"conditions": [],
"next": null
},
{
"widgetId": "FFde9b38ef0d899aef5b9137de7f9345b1",
"uuid": "4328e132-0cc5-4bb7-87a8-fb0317c7b071",
"event": "noAnswer",
"conditions": [],
"next": null
},
{
"widgetId": "FFde9b38ef0d899aef5b9137de7f9345b1",
"uuid": "607880f6-3ee7-4c4f-8668-131abf284f15",
"event": "failed",
"conditions": [],
"next": null
}
]
},
{
"sid": "FF48e69d9c562d7965f647c47339359c8a",
"name": "GatherDigit",
"type": "Gather",
"properties": {
"loop": "3",
"timeout": 5,
"stop_gather": true,
"finish_on_key": "#",
"offset": {
"x": -90,
"y": 480
},
"say": "おてすうですが、いずれかの数字キーを押してください。",
"language": "ja-JP",
"voice": "Polly.Mizuki",
"gather_language": "en",
"play": null,
"number_of_digits": "1"
},
"transitions": [
{
"widgetId": "FF48e69d9c562d7965f647c47339359c8a",
"uuid": "fc2daf3d-ed8b-488a-9d4a-495aa810f6b5",
"event": "keypress",
"conditions": [],
"next": "FF8051dc505207a50adec485f061b56cbe"
},
{
"widgetId": "FF48e69d9c562d7965f647c47339359c8a",
"uuid": "9c2666ef-e7df-41f5-bf85-2d192320f34e",
"event": "speech",
"conditions": [],
"next": null
},
{
"widgetId": "FF48e69d9c562d7965f647c47339359c8a",
"uuid": "8068370c-0b32-4119-9a28-780eb2667370",
"event": "timeout",
"conditions": [],
"next": "FF48e69d9c562d7965f647c47339359c8a"
}
]
},
{
"sid": "FF8051dc505207a50adec485f061b56cbe",
"name": "SetDigit",
"type": "Function",
"properties": {
"offset": {
"x": -80,
"y": 730
},
"url": "https://xxxx-xxxx-xxxx.twil.io/set-digit",
"parameters": [
{
"key": "number",
"value": "{{widgets.CallPhone.To}}"
},
{
"key": "digit",
"value": "{{widgets.GatherDigit.Digits}}"
}
]
},
"transitions": [
{
"widgetId": "FF8051dc505207a50adec485f061b56cbe",
"uuid": "6c2d078a-d18e-42fb-b59f-3cbe078fba2b",
"event": "success",
"conditions": [],
"next": null
},
{
"widgetId": "FF8051dc505207a50adec485f061b56cbe",
"uuid": "2dfcc350-a4ee-41c9-b885-3960d1444100",
"event": "fail",
"conditions": [],
"next": null
}
]
}
]
}
- Nextボタンを押します。
- 次のようなフローが読み込まれます。

- setDigitウィジェットを選択します。
- FUNCTION URLのプルダウンリストから先程作成した「SetDigit」を選びます。
- Saveボタンを押します。
- 画面上部のPublishボタンを押します。
<重要>
もし値を変更したときは、Saveボタンで変更を保存した後、かならず画面上部の赤いPublishボタンを押してください。これを忘れると設定は反映しません。
このStudioフローをRestAPIで呼ぶためには以下の条件が必要です。
- StudioフローのTriggerウィジェットを選択すると、RestAPI用のURLが表示されます。
- POSTメソッドで呼び出してください。
- Basic認証が必要です(UsernameとPasswordは、SyncのAPIキーとSecretがそのまま利用できます)。
- 発信者番号と発信元番号は、それぞれToとFromパラメータで指定が必要です。
- Context-Typeは、application/x-www-form-urlencodedで送ってください。
Curlでの呼び出し例
curl -X POST \
https://studio.twilio.com/v1/Flows/FWxxxxxx/Executions \
-H 'Authorization: Basic ,Basic ' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'To=%2B8180XXXXXXXX&From=%2B8150XXXXXXXX'
Twilio(トゥイリオ)とは
https://twilio.kddi-web.com
Twilioは音声通話、メッセージング(SMS/チャット)、ビデオなどの 様々なコミュニケーション手段をアプリケーションやビジネスへ容易に組み込むことのできるクラウドAPIサービスです。初期費用不要な従量課金制で、各種開発言語に対応しているため、多くのハッカソンイベントやスタートアップなどにも、ご利用いただいております。