ログイン無料トライアルを開始
Meta Tech Provider

PHP と Laravel から WhatsApp を送受信する

受信は、Meta の認証用 GET に応答し POST を受け付ける Laravel のルートで行います。PHP はドットをアンダースコアに変換するため、パラメーターは hub_mode、hub_verify_token、hub_challenge として読み取ります。送信は、番号の phone number id とトークンを使って、Cloud API のメッセージ用エンドポイントに `Http::withToken()` で POST するだけです。SDK は不要です。

hub_challenge

PHP は受信した変数名のドットをアンダースコアに変換するため、hub.challenge は hub_challenge として読み取られます。

routes/api.php

install:api で作成される Laravel のステートレスな API ルートは /api 配下で提供され、web グループの CSRF 保護の対象外です。

v25.0

Meta の最新のメッセージ送信例で使われている Graph API のバージョン。送信先は POST /{phone-number-id}/messages です。

Laravel で WhatsApp のメッセージを受信するには?

routes/api.php に、同じパスで GET と POST を定義します。ファイルがない場合は、先に php artisan install:api を実行してください。API ルートはステートレスで web グループの外にあるため、Meta の POST が CSRF トークンなしで拒否されることはありません。routes/web.php のルートでは拒否されます。

GET は Meta の認証に応答します。Meta は hub.mode、hub.verify_token、hub.challenge を送りますが、PHP は受信した変数名のドットをアンダースコアに書き換えるため、Laravel には hub_mode、hub_verify_token、hub_challenge として届きます。ドット付きの名前で読むと null になり、認証は通りません。challenge は response()->json() ではなく、プレーンテキストで返してください。

POST は、ペイロードをキューに入れたジョブに渡し、すぐに 200 を返します。パスには長いランダムなキーを入れ、両方のルートで確認してください。Meta は Tech Provider のアプリシークレットで配信に署名するため、署名ヘッダーをこちら側で検証することはできません。

routes/api.php: verification and event intake
use App\Jobs\HandleWhatsAppEvent;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::get('/whatsapp/{key}', function (Request $request, string $key) {
    abort_unless(hash_equals(config('services.whatsapp.url_key'), $key), 404);

    // PHP turns hub.mode into hub_mode: read the underscored names
    if ($request->query('hub_mode') === 'subscribe'
        && hash_equals(config('services.whatsapp.verify_token'),
                       (string) $request->query('hub_verify_token'))) {
        return response((string) $request->query('hub_challenge'), 200)
            ->header('Content-Type', 'text/plain');
    }

    abort(403);
});

Route::post('/whatsapp/{key}', function (Request $request, string $key) {
    abort_unless(hash_equals(config('services.whatsapp.url_key'), $key), 404);

    HandleWhatsAppEvent::dispatch($request->json()->all());

    return response('', 200);
});

Laravel から WhatsApp のメッセージを送信するには?

Laravel の HTTP クライアントを使い、phone number id のメッセージ用エンドポイントに POST を 1 回送ります。アクセストークンは bearer トークンとして付けます。どちらの値も接続から取得でき、ダッシュボードに表示され、get_api_credentials でも返されます。コードに直接書かず、環境変数を参照する config/services.php に置いてください。

ボディの形式は経過時間によって変わります。お客様の最後のメッセージから 24 時間以内なら、以下のように `text` オブジェクトを使えます。その時間を過ぎると、Meta が受け付けるのは、承認済みのメッセージテンプレートとその言語を指定した `template` オブジェクトだけで、フリーテキストはエラー 131047 で失敗します。原因はレスポンスで確認できます。失敗した呼び出しは、問題の内容を示す code を持つ error オブジェクトを返すので、捨てずにログに残してください。

このリクエストに EasyCoexistence 固有の部分はありません。Meta 自身のドキュメントにある呼び出しそのものなので、Guzzle でも素の cURL でも、どの PHP の HTTP クライアントでも同じように動きます。

Send a text reply inside the 24 hour window
use Illuminate\Support\Facades\Http;

$response = Http::withToken(config('services.whatsapp.token'))
    ->post('https://graph.facebook.com/v25.0/'
        . config('services.whatsapp.phone_number_id') . '/messages', [
        'messaging_product' => 'whatsapp',
        'to' => '14155550188', // digits only, with country code
        'type' => 'text',
        'text' => ['body' => 'Thanks, we are on it.'],
    ]);

if ($response->failed()) {
    // error.code 131047: outside the window, send a template instead
    logger()->warning('WhatsApp send failed', $response->json('error', []));
}

本番の Laravel ハンドラーには、それ以外に何が必要ですか?

本物のキュー、重複排除、そして 24 時間の時間枠の確認です。

キューが重要なのは、Meta が遅い応答を再送するためです。HandleWhatsAppEvent::dispatch() がすぐに戻るのは、キュー接続が実際のもので、ワーカーが動いている場合だけです。sync ドライバーではジョブがリクエストの中で実行され、モデル呼び出しやデータベース書き込みが遅いと、重複配信につながります。

重複排除には、各メッセージに含まれるメッセージ ID の wamid を使います。再送は例外ではなく通常の動作だからです。その列にユニークインデックスを張るか、それをキーにしたキャッシュロックで十分です。

さらに、各連絡先が最後に書き込んだ時刻を保存してください。そのタイムスタンプがあれば、送信処理が自動でテキストかテンプレートかを選べます。お客様が待っている状態になってからエラー 131047 で気づく必要はありません。WhatsApp Business アプリで電話から入力した返信は、同じルートに echo イベントとして届くため、担当者が会話を引き継いだこともハンドラーで把握できます。

よくある間違い

  • hub.challenge をドット付きで読み取っている。PHP が hub_challenge に名前を変えているため、ハンドラーは空のボディを返し、Meta の認証は通りません。
  • Webhook を routes/web.php に置いている。Meta の POST には CSRF トークンがないため、419 で拒否されます。
  • sync キューにディスパッチしている。ジョブが 200 を返す前に実行され、処理が遅いと再送され、メッセージが二重に処理されます。
  • challenge を response()->json() で返している。値を囲む引用符のせいで、Meta のチェックに失敗します。
EasyCoexistence で実現する

easycoexistence.com で番号を接続し、Webhook の送信先に、キーをパスに含めた Laravel のルートを設定します。phone number id とトークンはダッシュボードで確認できます。料金は番号 1 件あたり月額 US$ 9 から、番号を追加するにつれて US$ 1 まで。最初の 3 日間は無料です。

よくある質問

PHP 用の SDK はありますか?

当社からは提供しておらず、必要もありません。API は Meta のもので、Meta がドキュメントを公開しており、Laravel の HTTP クライアントや Guzzle から直接呼び出せます。

Laravel なしでも使えますか?

はい。素の PHP なら、$_GET['hub_challenge'] を読み取って text/plain で出力し、送信は cURL で行います。アンダースコアの規則は、すべての PHP フレームワークに当てはまります。

なぜ URL のパスにキーが必要なのですか?

実際に機能する認証だからです。Meta の署名は Tech Provider のアプリシークレットを使い、これは共有されません。そのため、第三者による偽のイベントの送信を防ぐのは URL のシークレットです。

チームは引き続き電話を使えますか?

はい。Coexistence により、その番号で WhatsApp Business アプリが引き続き使え、電話から送ったメッセージは echo イベントとしてお使いのルートに届きます。

続きを読む

始める準備はできましたか?

WhatsApp Coexistence の設定は数分で完了し、数か月もかかりません。スマートフォン上のアプリも引き続き使えます。

無料トライアルを始める

確認日

PHP と Laravel から WhatsApp を送信する