← 全記事
チュートリアル

WhatsApp Passkey QR認証を安全に完了し、Webhookで受信する手順

WhatsAppのQRペアリングでは、途中でPasskeyによる本人確認が必要になることがあります。認証状態が passkey_required になったら、アカウントを作り直すのではなく、同じ認証セッションを継続します。アカウント所有者がブラウザでWebAuthnのcredential promptを完了し、そのresponseをUnifyPortへ送信して、authorized と署名付きWebhookイベントを待ちます。

要点

  • WhatsApp公式ヘルプでは、Passkeyは指紋、顔認証、画面ロックなど端末のセキュリティシステムとアカウントを関連付ける仕組みだと説明されています。
  • WhatsApp Businessのlinked-device手順はQRコードまたは8文字コードを使い、どちらでも主端末で本人確認を求められる場合があります。
  • UnifyPortではPasskeyはWhatsApp QR認証の継続状態です:passkey_requiredpasskey_pending → 必要に応じて passkey_confirmationauthorized
  • 認証前にWebhookを登録しておくと、account.auth.requiredaccount.auth.succeeded、その後の message.received を取りこぼしにくくなります。
  • LINEを含む日本向けサポート基盤でも、受信レイヤーは先に固めておくべきです。まだ受信側を作っていない場合は Webhook-first inbound checklist を読み、署名検証は HMAC replay protection guide と併用してください。

WhatsAppが求めているもの

これは新しいMessaging APIのcredentialではありません。WhatsAppアカウントへアクセスする途中の本人確認ステップです。

WhatsAppのPasskeyドキュメントは、Passkeyが端末のセキュリティシステムを使い、WhatsAppが本人確認を必要とするときに利用できると説明しています。linked-device関連の公式ドキュメントも、WhatsApp BusinessでQRコードまたは電話番号コードを使ったペアリングを説明し、必要に応じて生体認証または端末ロックPINで確認するとしています。つまりUnifyPortのQRフローがPasskey分岐に入ったら、サーバーで生成できるsecretではなく、ユーザー参加型の認証フローとして扱うべきです。

バックエンドはUnifyPortのmessaging account状態とWebhook receiverを管理します。アカウント所有者はブラウザまたは承認済み端末でWebAuthn promptを完了します。完全な authorize_urlauth_payload.public_key、serialized credential responseをログへ残さないでください。

UnifyPortでの状態遷移

実装時は Create provider authorization sessionSubmit Passkey credential response を参照します。

状態意味対応
awaiting_qr_scanQRコードが有効所有者に表示し、GET /v1/accounts/{account_id}/auth または POST /v1/accounts/{account_id}/auth/qr/check をポーリングします。
passkey_requiredWhatsAppがWebAuthn credentialを要求Hosted authorize_url を開いてもらい、ブラウザのcredential promptを完了します。
passkey_pendingCredentialが送信済み新しいフローを開始せず、auth stateをポーリングします。
passkey_confirmation追加確認が必要所有者の確認後、POST /v1/accounts/{account_id}/auth/passkey-confirm を呼びます。
authorized認証完了runtimeは通常自動で開始されます。account.auth.succeededaccount.started を確認します。

1. 先にWebhookを作成する

curl -X POST https://api.unifyport.ai/v1/webhook-endpoints \
  -H "X-Api-Key: $UNIFYPORT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://ops.example.com/unifyport/webhook",
  "status": "active",
  "subscribed_events": ["account.auth.required", "account.auth.succeeded", "message.received"],
  "signing_secret": "stored-in-your-secret-manager",
  "retry_policy": { "max_attempts": 3 }
}'

本番receiverでは、JSON parseの前にraw bodyで X-Device-Signature を検証します。Webhook delivery は、HMAC-SHA256の入力を X-Device-Timestamp + "." + raw request body と定義しています。

2. WhatsApp messaging accountを作成する

curl -X POST https://api.unifyport.ai/v1/accounts \
  -H "X-Api-Key: $UNIFYPORT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "WhatsApp Support Passkey Test",
  "provider": "whatsapp",
  "region": "global",
  "status": "active",
  "auth_mode": "qrcode",
  "capabilities": ["send_message", "receive_message"],
  "provider_data": { "device_os": "Chrome", "device_platform": "web" },
  "metadata": { "environment": "staging" }
}'

返却された account_id を保存します。account objectには runtime_status があり、認証状態とQR/Passkey payloadは別のauthentication endpointから取得します。

3. QR認証を開始し、Passkeyを継続する

curl -X POST https://api.unifyport.ai/v1/accounts/acc_whatsapp_passkey_test/auth-sessions \
  -H "X-Api-Key: $UNIFYPORT_API_KEY"

responseまたは後続のauth checkが passkey_required を返したら、authorize_url をアカウント所有者に開いてもらいます。ブラウザはWebAuthn credential responseを生成します。信頼できる認証handoff層から POST /v1/accounts/{account_id}/auth/passkey-response へ送信します。状態が passkey_confirmation になった場合は、確認後に /auth/passkey-confirm を呼び、/authauthorized または failed までポーリングします。

4. 最初の受信イベントを確認する

認証が成功すると、通常はaccount eventが先に届き、その後に通常の受信メッセージが届きます。

{
  "id": "evt_2f9c1a4b7e",
  "type": "message.received",
  "provider": "whatsapp",
  "account_id": "acc_whatsapp_passkey_test",
  "occurred_at": "2026-09-10T08:15:21Z",
  "data": {
    "conversation": { "id": "8613912345678@s.whatsapp.net", "type": "user" },
    "sender": { "id": "8613912345678@s.whatsapp.net", "type": "user", "name": "Jordan Lee" },
    "message": { "id": "wamid.HBgM", "text": "Can you confirm my order?", "direction": "inbound", "sent_at": "2026-09-10T08:15:20Z" },
    "event": { "kind": "message_received" }
  }
}

ここから先はPasskey分岐ではなく、通常のWhatsApp inbound処理です。保存、重複排除、ルーティングを行います。全体設計を比較する場合は、Telegram user account webhook setupfirst API key webhook test checklist も参考になります。LINE、WhatsApp、Telegramを同じ受信キューへ寄せる場合も、署名付きイベントの扱いは同じです。

制限と使い分け

公式のbusiness identity、template、公式analytics、Metaが管理するポリシー面が必要ならWhatsApp Business Platformを選ぶべきです。既存の通常WhatsApp inboxを署名付きWebhookに接続し、LINEなど他チャネルと同じ受信基盤へ流したい場合は、UnifyPortのunofficial interfaceが現実的な選択肢になります。

Passkeyはユーザー確認をなくすものではありません。確認ステップをrunbookに明記し、ユーザーがcredential promptを完了し、サーバーが機密データを記録せず、Webhookが先に準備されている状態を作るための分岐です。

FAQ

passkey_required はエラーですか?

いいえ。WhatsApp QR認証の通常の継続状態です。同じsessionを継続し、ブラウザが生成したcredential responseを送信します。

QRがPasskeyに進んだら新しいaccountを作るべきですか?

いいえ。重複したprovider identity conflictを起こす可能性があります。現在のauth stateをポーリングして継続してください。

何を保存すべきですか?

account_id、auth state、Webhook event ID、受信メッセージを保存します。完全なauthorization URLやWebAuthn credential responseはログに保存しないでください。

2026-09-10に確認したソース

UnifyPort API

メッセージ連携を安定したプロダクトパイプラインへ。

まずは 1 つの API で送信を始め、標準イベントですべての inbound メッセージを業務システムへ戻しましょう。