登入開始免費試用
Meta Tech Provider

從 PHP 和 Laravel 傳送及接收 WhatsApp 訊息

接收只需一條 Laravel 路由:回應 Meta 的驗證 GET 請求,並接受 POST。由於 PHP 會把點號轉成底線,參數要以 hub_mode、hub_verify_token 和 hub_challenge 讀取。傳送則是一次 Http::withToken() POST,目標是 Cloud API 的 messages 端點,並帶上該號碼的電話號碼 ID 和存取權杖。無需任何 SDK。

hub_challenge

PHP 會把傳入變數名稱中的點號轉成底線,所以 hub.challenge 要以 hub_challenge 讀取。

routes/api.php

由 install:api 建立的 Laravel 無狀態 API 路由,位於 /api 之下,不受 web 群組的 CSRF 保護所限。

v25.0

Graph API 版本取自 Meta 現行的傳送訊息範例,端點為 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 用戶端:向該電話號碼 ID 的 messages 端點發出一次 POST,並以 Bearer 權杖形式帶上存取權杖。這兩個值都來自連接,可在控制台查看,也可由 get_api_credentials 取得。請把它們放在 config/services.php,並由環境變數提供,不要寫進程式碼。

請求內容的格式取決於時間。在客戶最後一則訊息後的 24 小時內,可以使用 text 物件,如下所示。超出這個時限後,Meta 只接受 template 物件,並須指明已核准的範本及其語言,自由格式訊息會以錯誤 131047 失敗。回應是查看問題的地方:失敗的呼叫會回傳 error 物件,其中的 code 說明出了什麼問題,所以請記錄下來,不要丟棄。

請求中沒有任何內容是 EasyCoexistence 專屬的。這就是 Meta 官方文件中的呼叫,所以任何 PHP HTTP 用戶端,無論是 Guzzle 還是原生 cURL,運作方式都一樣。

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 處理程式還需要什麼?

需要真正的隊列、去重,以及時限檢查。

隊列之所以重要,是因為 Meta 會在回應過慢時重試。只有當隊列連接是真正的連接,而且有 worker 在運行時,HandleWhatsAppEvent::dispatch() 才能迅速返回。使用 sync 驅動程式時,作業會在請求內執行,一次較慢的模型呼叫或資料庫寫入,就會變成重複傳送。

請以訊息 ID 去重,也就是每則訊息中的 wamid,因為重試是常態,而非例外。在該欄位建立唯一索引,或使用以它為鍵的快取鎖,就已足夠。

另外,請記錄每位聯絡人最後一次發訊息的時間。有了這個時間戳記,傳送流程就能自行選擇 text 或 template,而不必等客戶已經在等候時,才從錯誤 131047 得知。在手機的 WhatsApp Business 應用程式輸入的回覆,會以 echo 事件的形式送到同一條路由,所以處理程式也能知道何時有人接手了對話。

常見錯誤

  • 以點號讀取 hub.challenge。PHP 已將它改名為 hub_challenge,處理程式因此回傳空白內容,Meta 永遠無法完成驗證。
  • 把 Webhook 放在 routes/web.php。Meta 的 POST 沒有 CSRF 權杖,會以 419 被拒絕。
  • 分派到 sync 隊列。作業在 200 回應之前執行,處理過慢會被重試,訊息因而被處理兩次。
  • 以 response()->json() 回傳 challenge。值周圍的引號會令 Meta 的檢查失敗。
使用 EasyCoexistence 完成這項設定

在 easycoexistence.com 連接號碼,把 Webhook 目的地設為您的 Laravel 路由(金鑰放在路徑中),然後從控制台讀取電話號碼 ID 和存取權杖。每個號碼每月 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 訊息