התחברותהתחילו תקופת ניסיון
Meta Tech Provider

שליחה וקבלה של WhatsApp מ-PHP ומ-Laravel

הקבלה היא נתיב ב-Laravel שעונה לבקשת ה-GET של Meta לאימות ומקבל בקשות POST. את הפרמטרים קוראים בשמות hub_mode, hub_verify_token ו-hub_challenge, כי PHP הופכת נקודות לקווים תחתונים. השליחה היא בקשת POST אחת עם Http::withToken() לנקודת הקצה messages של Cloud API, עם phone number id והאסימון של המספר. אין צורך להתקין ספרייה נוספת.

hub_challenge

PHP ממירה נקודות בשמות משתנים נכנסים לקווים תחתונים, ולכן hub.challenge נקרא בשם hub_challenge, עם קו תחתון במקום נקודה.

routes/api.php

נתיבי API חסרי מצב ב-Laravel, שנוצרים באמצעות install:api, נטענים תחת /api ומחוץ לקבוצת web ולהגנת ה-CSRF שלה.

v25.0

הכתובת POST /{phone-number-id}/messages, בגרסת Graph API שבדוגמאות השליחה העדכניות של Meta עצמה.

איך מקבלים הודעות WhatsApp ב-Laravel?

מגדירים בקשת GET ובקשת POST על אותו נתיב בקובץ routes/api.php. אם הקובץ לא קיים, מריצים קודם php artisan install:api. נתיבי API הם חסרי מצב ויושבים מחוץ לקבוצת web, ולכן בקשת ה-POST של Meta לא נדחית בגלל חוסר באסימון 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 מעבירה את ה-payload למשימה בתור ומחזירה 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);
});

איך שולחים הודעת WhatsApp מ-Laravel?

בעזרת לקוח ה-HTTP של Laravel: בקשת POST אחת לנקודת הקצה messages של ה-phone number id, עם אסימון הגישה (Access Token) כ-bearer token. שני הערכים באים מהחיבור, מוצגים בלוח הבקרה ומוחזרים על ידי get_api_credentials, ומקומם ב-config/services.php מגובה במשתני סביבה ולא בקוד.

מבנה הגוף תלוי בזמן. עד 24 שעות מההודעה האחרונה של הלקוח, אפשר להשתמש באובייקט text, כמו בדוגמה. מחוץ לחלון הזה Meta מקבלת רק אובייקט template שמציין תבנית מאושרת ואת השפה שלה, וטקסט חופשי נכשל עם שגיאה 131047. את הסיבה רואים בתגובה: בקשה שנכשלה מחזירה אובייקט error שהקוד בו מסביר מה השתבש, ולכן רושמים אותו ללוג ולא זורקים.

שום דבר בבקשה לא ייחודי ל-EasyCoexistence. זו הקריאה מהתיעוד של Meta עצמה, ולכן כל לקוח HTTP של PHP, בין אם 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 שולחת שוב כשהתגובה איטית. HandleWhatsAppEvent::dispatch() חוזר מהר רק כשחיבור התור הוא אמיתי ויש worker שרץ. עם הדרייבר sync המשימה רצה בתוך הבקשה, וקריאה איטית למודל או כתיבה למסד נתונים הופכות למשלוח כפול.

מונעים כפילויות לפי מזהה ההודעה, ה-wamid שבכל הודעה, כי ניסיונות חוזרים הם דבר רגיל ולא חריג. מספיק אינדקס ייחודי על העמודה הזו, או נעילה במטמון שהמפתח שלה הוא המזהה.

ושומרים מתי כל איש קשר כתב לאחרונה. עם החותמת הזו נתיב השליחה בוחר לבד בין טקסט לתבנית, במקום לגלות את זה משגיאה 131047 אחרי שהלקוח כבר חיכה. תשובות שהוקלדו בטלפון באפליקציית WhatsApp Business מגיעות כאירועי echo לאותו נתיב, ולכן המטפל יכול גם לזהות שאדם השתלט על שיחה.

טעויות נפוצות

  • קריאת hub.challenge עם נקודה. PHP שינתה את השם ל-hub_challenge, המטפל מחזיר גוף ריק ו-Meta לא מאמתת.
  • הצבת וובהוק (Webhook) ב-routes/web.php. בבקשת ה-POST של Meta אין אסימון CSRF, והיא נדחית עם קוד שגיאה 419 בתגובה.
  • שליחה לתור מסוג sync. המשימה רצה לפני ה-200, טיפול איטי נשלח שוב, וההודעות מעובדות פעמיים.
  • החזרת ה-challenge עם response()->json(). המרכאות סביב הערך מכשילות את הבדיקה של Meta.
עושים את זה עם EasyCoexistence

מחברים את המספר ב-easycoexistence.com, מגדירים כיעד של הוובהוק את הנתיב שלכם ב-Laravel עם המפתח בנתיב, וקוראים את phone number id והאסימון מלוח הבקרה. החל מ-US$ 9 למספר בחודש, ויורד ל-US$ 1 ככל שמוסיפים מספרים, עם תקופת ניסיון של 3 ימים.

שאלות נפוצות

יש ספרייה ל-PHP?

לא מאיתנו, ואין בה צורך. ה-API הוא של Meta ומתועד על ידה, ואפשר לפנות אליו ישירות מלקוח ה-HTTP של Laravel או מ-Guzzle.

זה עובד גם בלי Laravel?

כן. ב-PHP רגילה קוראים את $_GET['hub_challenge'], מחזירים אותו כ-text/plain ושולחים עם cURL. כלל הקו התחתון חל על כל פריימוורק של PHP.

למה הכתובת צריכה מפתח בנתיב?

כי זה האימות היחיד שעובד. החתימה של Meta משתמשת בסוד האפליקציה של ה-Tech Provider, שלעולם לא משותף, ולכן הסוד בכתובת הוא מה שמונע מזרים לשלוח אירועים מזויפים.

הצוות עדיין יכול להשתמש בטלפון?

כן. Coexistence משאיר את אפליקציית WhatsApp Business פעילה על המספר, והודעות שנשלחות מהטלפון מגיעות לנתיב שלכם כאירועי echo.

המשיכו לקרוא

מוכנים להתחיל?

מגדירים WhatsApp Coexistence בתוך דקות, לא חודשים. האפליקציה ממשיכה לעבוד בטלפון.

התחילו תקופת ניסיון בחינם

נבדק ב-

שליחת WhatsApp מ-PHP ומ-Laravel