casperlaitw/laravel-fb-messenger
Laravel wrapper for the Facebook Messenger Platform that simplifies building bots: handle webhooks, send text and rich messages, manage persistent menus, and integrate Messenger features into your Laravel app with a clean, expressive API.
Minimal steps to a working bot:
Install
composer require casperlaitw/laravel-fb-messenger
On Laravel ≥5.5 package auto-discovery registers CasperLaitw\LaravelFbMessenger\LaravelFbMessengerServiceProvider and the FacebookMessenger facade. On older versions add both manually in config/app.php.
Publish config
php artisan vendor:publish --provider="CasperLaitw\LaravelFbMessenger\LaravelFbMessengerServiceProvider"
This drops config/laravel-fb-messenger.php.
Fill .env — this is where most first-run failures happen:
FB_MESSENGER_APP_TOKEN= # App Secret from the Facebook app dashboard
FB_MESSENGER_PAGE_TOKEN= # Page Access Token (long-lived, not user token)
FB_MESSENGER_WEBHOOK_TOKEN= # Arbitrary string you also paste into the FB dashboard
FB_MESSENGER_API_VERSION=v18.0
The config ships with an old default API version (2.x-era). Bump it; most endpoints still work but the SDK's defaults are stale.
Create a handler. The package's idiomatic entry point is a class that receives parsed events (message, postback, authentication). Extend the package's base Handler (or implement its handler interface — check src/ in your installed version for the exact method names, since this is a thin, small package and the contract has shifted across releases):
use CasperLaitw\LaravelFbMessenger\Handler;
class EchoBot extends Handler
{
public function handleMessage($message)
{
$this->sender->sendText($message->getSenderId(), 'You said: '.$message->getText());
}
public function handlePostback($postback) { /* menu clicks */ }
public function handleAuthentication($auth) { /* account linking */ }
}
Expose the webhook. Two routes, and they are not interchangeable:
Route::get('/webhook', 'WebhookController@verify'); // FB subscription handshake
Route::post('/webhook', 'WebhookController@handle'); // event delivery
public function verify(Request $request)
{
if ($request->input('hub_verify_token') === config('laravel-fb-messenger.webhook_token')) {
return response($request->input('hub_challenge'), 200)
->header('Content-Type', 'text/plain');
}
abort(403);
}
public function handle(Request $request)
{
FacebookMessenger::handle($request->all()); // dispatch into your handler
return response('OK', 200);
}
Where to look first: config/laravel-fb-messenger.php for tokens/version, then the package's src/Handler.php / src/Contracts/ for the exact dispatch signatures, then the underlying pimax/fb-messenger-php vendor source for message-building helpers. Don't trust blog posts — the API surface is small enough to read in ten minutes.
First use case: an echo bot that replies to text messages, then a postback handler that fires from a "Get Started" button. That covers 90% of what this package is used for in practice.
Event dispatch into a handler. Rather than parsing entry[].messaging[] yourself, keep all routing inside your handler class: one method per event type. This is the package's main value-add over raw cURL — it normalizes the webhook envelope into typed objects (MessageReceived, PostbackReceived, AuthenticationReceived, etc.).
Sending rich content. Go through the facade (or the sender you get inside the handler). The underlying SDK exposes text, quick replies, buttons, generic (card carousel) templates, attachments, and sender actions ("typing_on"). Compose these inline:
$this->sender->sendButtonTemplate($userId, 'Pick one', [
['type' => 'postback', 'title' => 'Yes', 'payload' => 'CONFIRM_YES'],
['type' => 'postback', 'title' => 'No', 'payload' => 'CONFIRM_NO'],
]);
Webhook controller should do nothing but route. Keep handle() to: parse, dispatch to a queued job, return 200. Facebook gives you a short window before it retries; slow handlers cause duplicate deliveries.
public function handle(Request $request)
{
Log::channel('fb')->info('webhook', $request->all()); // keep raw payloads
ProcessMessengerEvent::dispatch($request->all());
return response('OK', 200);
}
Queue the work. The job runs the handler, does profile lookups, calls out to your domain logic, and sends replies. This also lets you retry transient send failures (rate limits, 500s from Graph API) without FB redelivering the event.
One-time Messenger Profile setup. Persistent menu, greeting text, "Get Started" button, and whitelisted domains are configuration, not per-message calls. Wrap them in an artisan command (php artisan fb:setup-profile) so they're reproducible across environments instead of being run once by hand in Graph API Explorer.
Testing. Fake the facade in feature tests (FacebookMessenger::shouldReceive('sendText')...) and post fixture payloads at your webhook route. Keep a small set of captured real webhook JSON bodies in tests/fixtures/ — message, postback, and delivery/read receipts at minimum — and assert your handler responds correctly. For the handshake, assert the challenge is echoed byte-for-byte.
Signature verification middleware. Add a middleware that computes hash_hmac('sha1', $rawBody, config('...app_token')) and compares against X-Hub-Signature. The package does not reliably do this for you; verifying it is your job and it's the difference between a bot and an open endpoint.
$except in app/Http/Middleware/VerifyCsrfToken.php. This is the single most common "my bot works locally but not in production" bug.response($challenge)->header('Content-Type','text/plain'). Laravel's default JSON response for strings will fail verification in the FB dashboard.(#190) Invalid OAuth access token with no further detail.FB_MESSENGER_API_VERSION to a current version. Newer Facebook features (handover protocol, icebreakers, some template fields) simply aren't in the bundled SDK — you'll be writing raw requests via Http:: for those.mid and dedupe in the job; it's cheaper than apologizing for double replies.pimax/fb-messenger-php. On Laravel 10/11 you may hit dependency conflicts or missing typed signatures. Before committing, verify it installs cleanly and budget time for a fork — the package is small (src/ is a few files), so vendoring or reimplementing the wrapper around the SDK is often cheaper than fighting compatibility.$request->all() before dispatch. Handler-level failures hide the envelope, and Facebook's webhook log in the app dashboard only shows delivery attempts, not your internal errors.Illuminate\Support\Facades\Http with the page token and version from config. Don't fight the wrapper — the package is a convenience around the Graph API, not an abstraction you should protect.How can I help you explore Laravel packages today?