Twilio Functionsを利用し、自前のDBに保存している転送設定で通話を転送する


はじめに

電話転送ってご存知でしょうか?
webサイトの予約などで、web上にある電話番号に電話をかけると一瞬コールして、再度別のコール音が流れてお店の人が出る、みたいな経験したことある方多いと思うのですが、これって実はwebサービスを提供している会社が用意した電話番号に電話をかけさせて、その通話をお店の電話番号へ転送するみたいなことをしているはずなんですよね。(紹介するお店に反響課金するためとか)

このような通話転送は「Twilio」というサービスを利用すると、簡単に実現することができるのです。

Twilioについて

Twilioは音声通話、メッセージング(SMS/チャット)、ビデオなどの 様々なコミュニケーション手段をアプリケーションやビジネスへ容易に組み込むことのできるクラウドAPIサービスです。

らしいです。
https://twilio.kddi-web.com/availability/

やりたいこと

twilioの電話転送サービスを利用して、電話番号に電話がかかった時に別のサーバーから転送先電話番号を取得してその電話番号へ通話を転送する。(Twiml Binsは使わない)

なぜやりたいのか?

Twilioのコンソール上で、「TwiML Bins」という機能を利用すると、ほぼノンプログラミングで実現できるのですが
これだと電話番号ごとに設定していく必要があり面倒で、どんな設定があるのか確認も大変です。
そのため、自前のDBに保存してある転送設定を参照して、適切な電話番号へと通話を転送できればなってことで作ってみました。

概要

これから実現するのは、下記の図のような転送です。※雑です
アプリケーションサーバーのリクエスト/レスポンス部分は、今回は省略してTwilioの設定のみ説明します。

今回の電話転送を実装するのに、Twilioの「Functions」という機能を利用します。
Twilio「Functions」に関する説明は、以下で詳しく紹介されていたのでそちらをご覧になっていただくのが良いと思います。
簡単にいうと、Twilioの電話番号にかかってきた電話に対して、Node(javascript)でプログラムを実行することができる機能です。

Functionsの設定

Functionsを新規で作成して、下記のように設定を行います。

  • FUNCTION NAME・・・任意
  • PATH・・・任意
  • EVENT・・・「Incoming Voice Calls」を選択する

環境変数の設定&パッケージの準備

 環境変数

以下の3つを環境変数として定義しています。
設定した環境変数は、プログラム内で「context.XX」でアクセスすることができます。

  • FAILED_VOICE_MESSAGE・・・転送処理が失敗した場合に、架電側に再生する自動音声の台本
  • API_KEY・・・リクエストの際に必要な認証キー
  • HTTP_REQUEST_URL・・・リクエスト先のURL

 パッケージ

  • request

転送先取得APIの仕様(想定)

Input

名前 説明
twilio_number Twilioの電話番号

Output

返却パラメータ 説明
forwarding_number 転送先の電話番号

コード

/**
 *  Call Forward Template
 * 
 *  This Function will forward a call to another phone number. If the call isn't answered or the line is busy, 
 *  the call is optionally forwarded to a specified URL. You can optionally restrict which calling phones 
 *  will be forwarded.
 */

exports.handler = function(context, event, callback) {
  // set-up the variables that this Function will use to forward a phone call using TwiML

    var request = require('request');

    //ヘッダーを定義
    var headers = {
      'Content-Type':'application/json',
      'Cache-Control': 'no-cache',
      'Authorization': context.API_KEY
    }

    //GETパラメータ
    //event.Toで、コールされたTwilioの電話番号を取得できる
    param = "?twilio_number=" + encodeURIComponent(event.To)

    var options = {
        uri: context.HTTP_REQUEST_URL + param,
        method: 'GET',
        headers: headers,
        json: true
    };

    // 転送先の電話番号をhttp getで取得
    request(options, function(error, response, body){

        // 転送先電話番号
        let phoneNumber = body.forwarding_number;
        // 転送元番号(twilio の番号)
        let callerNumber = event.To;
        // タイムアウト時間
        let timeout = 10;
        // 録音
        let record = true;

        // OPTIONAL
        let allowedCallers = event.allowedCallers || [];


        // generate the TwiML to tell Twilio how to forward this call
        let twiml = new Twilio.twiml.VoiceResponse();

        let allowedThrough = true
        if (body.forwarding_number === undefined) {
            allowedThrough = false
        }

        let dialParams = {};
        if (callerNumber) {
            dialParams.callerId = callerNumber
        }
        if (timeout) {
            dialParams.timeout = timeout
        }
        if (record) {
            dialParams.record = record
        }

        let numberParams = {}

        console.log(allowedThrough)
        if (allowedThrough) {
          //転送用番号の取得に成功した場合は、転送先へダイヤルする
          const dial = twiml.dial(dialParams);
          dial.number(numberParams, phoneNumber);
        } else {
          //番号の取得に失敗した場合は、失敗用メッセージ(FAILED_VOICE_MESSAGE)を再生する
          twiml.say(
              {
                  voice: 'woman',
                  language: 'ja-JP',
                  loop: 2
              },
              context.FAILED_VOICE_MESSAGE
          );
        }

        // return the TwiML
        console.log(twiml.toString())
        callback(null, twiml);
    });
};

電話番号の着信時に設定

作成したFunctionsを購入した電話番号の、「通話着信時」に設定して完了です。

最後に

今回は、requestパッケージを使って実装しましたが他にももっといいやり方があるかもしれません。また、アプリケーションサーバーへリクエストした結果を今回は電話番号が返却される想定で作成しましたが、TwiMLをアプリケーションサーバーで作成して返却するという形でも問題ないと思います。(電話している方は待っているので、リクエストのやりとりはできるだけ短い方が良いと考えて電話番号のみのやりとりとしました。)