サポートシステムに WhatsApp 音声通話を組み込む:リンク作成から接続イベントまで
UnifyPort でサポートシステムに WhatsApp 音声通話を組み込むには、バックエンドが media_type: "voice" で通話を準備し、担当者に非公開のブラウザー用リンクを渡します。担当者がリンクを開き、Start call を押すと発信します。バックエンドでは返された call_id をサポートタスクに紐付け、通話イベントで結果を追跡します。
本記事の対象は、クローズドベータで提供される、担当者が手動で開始する1対1の発信通話です。UnifyPort は非公式インターフェースを使用しており、Meta の WhatsApp Business Calling API の手順ではありません。機能概要と申請方法は通話ベータの案内を参照してください。
1. アカウントを準備し、会話を選ぶ
まずテクニカルサポートにワークスペースの通話利用資格を申請します。API キーの作成だけでは有効になりません。is_beta は読み取り専用の資格フラグであり、ワークスペースの metadata から変更する設定ではありません。
通話操作を表示する前に、WhatsApp メッセージングアカウントが認証済みで、runtime_status が running であることを確認します。同じアカウントの個別会話を選び、API が返した conversation_id をそのまま保持してください。電話番号への置き換えや、プロバイダー識別子の独自生成は避けます。
アプリ側で選択したワークスペース、アカウント、会話、サポートタスクを今回の操作に固定し、バックエンドで担当者の操作権限を検証します。新着メッセージや会話の切り替えによって、準備済みの通話先が勝手に変わらない設計にします。
以下の例では API キーをサーバー側に保持し、ブラウザーには必要な非公開リンクだけを渡します。
2. バックエンドで音声通話を準備する
プレースホルダーをワークスペースの API キー、接続済みアカウント、選択した会話に置き換え、サーバー側で実行します。
curl -X POST 'https://api.unifyport.ai/v1/calls' \
-H 'X-Api-Key: <YOUR_API_KEY>' \
-H 'Content-Type: application/json' \
-d '{
"account_id": "acc_example",
"to": { "id": "<conversation_id>", "type": "user" },
"media_type": "voice"
}'
通話作成リファレンスでは、成功時に 201 Created と次の形式のレスポンスを返します。値は説明用であり、実際の通話結果ではありません。
{
"request_id": "req_example",
"data": {
"call_id": "call_example",
"direction": "outbound",
"media_type": "voice",
"state": "preparing",
"join_url": "https://calls.example.com/call/call_example#ticket=example-only",
"expires_at": "2026-10-27T08:02:00Z"
}
}
data.call_id を選択したアカウント、会話、タスクと一緒に保存し、調査用に request_id も保持します。data.join_url は機密情報として扱い、そのまま担当者に渡してください。完全な URL をログ、アクセス解析、公開メッセージに残してはいけません。
201 と preparing はリンクの準備完了を意味します。相手の電話が鳴っていることや、応答したことを意味しません。
3. 担当者がブラウザーで通話を開始する
業務画面には選択した顧客と「通話開始の準備完了」を表示し、対象の担当者だけに join_url を提供します。担当者は通話ページでマイクへのアクセスを許可し、expires_at より前に Start call を押します。
準備期間の既定値は作成から2分ですが、サーバーで変更できます。期限を固定計算せず、expires_at を参照してください。この期限は接続後の通話時間を制限しません。マイクの許可は必須で、カメラの許可は任意です。
作成レスポンスを待つ間は二重送信を防ぎます。レスポンスが失われた場合は結果未確認と表示し、現在の通話を照会してから再作成を検討します。ボタンの無効化だけでは複数担当者を調整できないため、バックエンドでもアカウント単位の競合を処理します。
4. Webhook をサポートタスクに紐付ける
Webhook エンドポイントでイベント名を明示して購読する場合、必要な既存の購読を保持したうえで、次の2つを含めます。これはフィールドの抜粋であり、エンドポイント作成リクエスト全体ではありません。
{
"subscribed_events": ["call.answered", "call.ended"]
}
ベータ対象ワークスペースでは、*、空のリスト、リストの省略でも、利用資格のある通話イベントが含まれます。ベータ対象外のワークスペースは、ワイルドカード購読によって通話イベントの権限を得ることはできません。
| 観測した情報 | アプリ側の処理 |
|---|---|
作成レスポンスの data.call_id | 選択したサポートタスクに紐付ける |
call.answered の data.call.id | この通話が接続した事実を記録する |
call.ended の data.call.id、data.reason、data.duration_seconds | 終了理由と接続時間を記録する |
署名付きイベントを処理する前に、エンドポイントに signing_secret を設定し、そのシークレットをサーバー側で管理します。
Webhook 配信仕様に従い、生のリクエストボディで署名を検証し、イベントエンベロープの id で重複を排除します。イベントの account_id と data.call.id を保存済みの関連情報と照合してください。遅延や重複イベントによって、終了した通話を画面上で通話中に戻さないようにします。
接続済みである証拠は別に保持してください。duration_seconds は小数点以下を切り捨てるため、0 だけで未接続とは判断できません。作成時の同期的な失敗には終了イベントがない場合があり、HTTP エラーは別途記録します。完全なペイロードは接続イベントと終了イベントを参照してください。通話終了を、サポートタスクの自動解決として扱うことも避けます。
5. 管理対象の通話を照会・終了する
状態を復元する場合や通話識別子を失った場合は、現在の通話を取得するエンドポイントを使います。
curl --get 'https://api.unifyport.ai/v1/calls/active' \
-H 'X-Api-Key: <YOUR_API_KEY>' \
--data-urlencode 'account_id=acc_example'
data: {} は、そのアカウントに現在 UnifyPort が管理する通話がないことを表します。通話履歴ではなく、前回の試行結果を証明する情報でもありません。WhatsApp ネイティブアプリでの通話を検出する用途には使えず、使い捨ての join_url も再発行されません。
担当者が既知の通話を終了すると判断したら、保存済みの call_id とそのアカウントを使います。
curl -X POST 'https://api.unifyport.ai/v1/calls/call_example/hangup' \
-H 'X-Api-Key: <YOUR_API_KEY>' \
-H 'Content-Type: application/json' \
-d '{"account_id":"acc_example"}'
通話終了エンドポイントは終端状態の ended または failed を返します。終了要求を繰り返すと、既存の終端状態が返る場合があります。failed というだけで要求を繰り返したり、作成競合を解消するために別の担当者の通話を終了したりしないでください。
よくある実装上の問題
| 状況 | 次の対応 |
|---|---|
403 beta_feature_not_enabled | サポートにワークスペースの利用資格を確認する。metadata では有効化できない |
| 準備リンクの期限切れ | 再利用をやめ、現在の通話を確認したうえで担当者が再準備を判断する |
| ブラウザーでマイクが使えない | 正常に発信できるか確認する前に、通話ページのマイク権限を確認する |
| 既存通話との作成競合 | 管理対象の通話を取得し、業務システムで担当者と目的を確認する |
| 作成・終了リクエストのタイムアウト | 結果未確認として保持し、現在の状態と受信済みイベントを照合してから再試行を判断する |
一連のサポートフローを検証する
同意を得たテスト用会話で、接続と終了、準備リンクの期限切れ、マイク権限の拒否、二重クリック、アカウントの競合、HTTP レスポンスの消失を確認します。イベントの再配信でタスク記録が重複せず、完全な通話リンクがログに残らないことも確認してください。これらは実装時に行う受け入れ確認であり、本記事が実施済みと報告するテスト結果ではありません。
電話番号のリストに自動発信できますか?
できません。このフローではアカウントに属する conversation_id と、ブラウザーで担当者が Start call を押す操作が必要です。
同じ実装で着信にも応答できますか?
現在のベータではできません。call.incoming は予約済みの契約であり、イベント一覧にあっても着信は利用できません。グループ通話、録音、文字起こし、SIP/PBX 連携、AI 音声エージェントも対象外です。
チームは何から始めればよいですか?
資格がまだ有効でなければ、ワークスペース ID と想定するサポート用途を添えてテクニカルサポートに申請してください。API キーや非公開の通話リンクは送らないでください。有効化後は通話作成リファレンスを使って選択した1件の会話を検証し、その後サポートフローに組み込みます。
メッセージ連携を安定したプロダクトパイプラインへ。
まずは 1 つの API で送信を始め、標準イベントですべての inbound メッセージを業務システムへ戻しましょう。