PHP と Laravel から WhatsApp を送受信する
受信は、Meta の認証用 GET に応答し POST を受け付ける Laravel のルートで行います。PHP はドットをアンダースコアに変換するため、パラメーターは hub_mode、hub_verify_token、hub_challenge として読み取ります。送信は、番号の phone number id とトークンを使って、Cloud API のメッセージ用エンドポイントに `Http::withToken()` で POST するだけです。SDK は不要です。
PHP は受信した変数名のドットをアンダースコアに変換するため、hub.challenge は hub_challenge として読み取られます。
install:api で作成される Laravel のステートレスな API ルートは /api 配下で提供され、web グループの CSRF 保護の対象外です。
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 のアプリシークレットで配信に署名するため、署名ヘッダーをこちら側で検証することはできません。
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 クライアントでも同じように動きます。
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.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 の設定は数分で完了し、数か月もかかりません。スマートフォン上のアプリも引き続き使えます。
無料トライアルを始める確認日