revolution/laravel-line-sdk
A Laravel wrapper for the LINE Messaging API that streamlines building LINE bots, handling webhooks, sending rich messages, and integrating LINE Login into PHP applications with familiar Laravel conventions.
The package is a thin Laravel wrapper around the official linecorp/line-bot-sdk PHP SDK: it registers the Messaging API client(s) in the container, reads credentials from a published config file, and exposes a facade. Because it is thin, the fastest path to understanding it is to read the service provider in vendor/revolution/laravel-line-sdk/src/ — it tells you exactly which bindings and facade methods exist for the SDK version it pins.
1. Install
composer require revolution/laravel-line-sdk
Laravel auto-discovery registers the service provider. Publish the config (adjust the provider class to whatever the package ships):
php artisan vendor:publish --provider="Revolution\Line\LineServiceProvider"
2. Configure credentials
Get these from the LINE Developers Console → your Messaging API channel → Basic settings / Messaging API tab:
LINE_CHANNEL_ID=1655xxxxxx
LINE_CHANNEL_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
LINE_CHANNEL_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
The channel secret is only needed for webhook signature verification; the channel access token is needed for every outbound API call. Keep both in .env — never in config/line.php committed to git.
3. First use case: an echo webhook
Add a route (exempt from CSRF), verify the signature, and reply:
// routes/api.php
Route::post('/line/webhook', LineWebhookController::class);
public function __invoke(Request $request)
{
$events = Line::parseEvent($request); // verifies X-Line-Signature internally
foreach ($events as $event) {
if ($event->getType() !== 'message') {
continue;
}
Line::replyText($event->getReplyToken(), 'You said: ' . $event->getMessage()->getText());
}
return response()->noContent();
}
If the facade does not expose helpers like parseEvent/replyText, drop one level down and use the SDK objects it resolves: build a LINEBot (or the v8 MessagingApiApi + MessagingApiBlobApi) from the container and call replyMessage/pushMessage with SDK message builders. That is also the escape hatch for any endpoint the wrapper hasn't wrapped.
4. Verify it works
Click Verify in the LINE console's Messaging API → Webhook settings — LINE sends an empty events array, so a clean 200 with no events is a pass. Then add your bot as a friend via its QR code and send it a message.
| Reply | Push | |
|---|---|---|
| Token | replyToken from the event |
userId / groupId / roomId |
| Validity | Single use, ~1 minute | Any time |
| Cost | Free, not counted against quota | Counts against monthly message quota |
| Use for | Immediate response to a webhook event | Notifications, reminders, broadcasts, queued jobs |
Practical rule: in the webhook controller, always answer with a reply message so the interaction feels instant and costs nothing; use push from jobs, scheduled commands, and Laravel notifications.
LINE retries any webhook that doesn't return 2xx within a few seconds, which produces duplicate handling. A good pattern is: verify signature → push the event onto a queue → return 200 → process in a job.
foreach (Line::parseEvent($request) as $event) {
ProcessLineEvent::dispatch($event->toArray());
}
return response()->noContent();
Keep $event serialised as an array (SDK objects are not always cleanly serialisable) and rebuild the SDK object in the job if needed.
Link line_user_id to your users table (or a separate line_profiles table for users who only interact with the bot). Store the displayName, pictureUrl, and statusMessage from getProfile once, and refresh on demand — group and room members cannot be resolved to a profile at all.
$profile = Line::getProfile($event->getSource()->getUserId());
LineUser::updateOrCreate(
['line_user_id' => $profile->getUserId()],
['display_name' => $profile->getDisplayName(), 'avatar' => $profile->getPictureUrl()],
);
This is the highest-leverage day-to-day pattern: a custom Laravel notification channel means your existing Notification classes can fan out to LINE like they do to mail or Slack.
class LineChannel
{
public function send($notifiable, Notification $notification): void
{
if (! $userId = $notifiable->routeNotificationFor('line')) {
return;
}
Line::pushText($userId, $notification->toLine($notifiable)->text);
}
}
Then $user->notify(new InvoiceOverdue($invoice)) with via() returning [LineChannel::class] (or 'line' if you register a channel alias). Queue the notification and you get retry/backoff for free.
Inline message construction gets unreadable fast, especially for Flex Messages, whose payload is deeply nested JSON. Put each message shape in app/Line/Messages/ with a toArray() method, and keep reusable Flex templates in resources/line/ as JSON files you json_decode and inject values into. Test them by asserting the array structure rather than string-matching JSON.
Quick replies, imagemaps, and Flex carousels are all just extra keys on the message array — the builder in the package is a convenience, not a constraint, so you can always hand the SDK a raw array.
For bulk sends, chunk recipients, respect the SDK's per-request message limit, and throttle. Use broadcast for "everyone who follows the bot" and multicast for a known list (up to 500 IDs per call). Rich menu create/upload/set-default is a three-call sequence — wrap it in an artisan command so it is reproducible across environments.
The wrapper resolves concrete SDK clients, so mock at the boundary: bind a fake into the container, or use the facade's shouldReceive if it's a real facade. Do not rely on Http::fake() — the LINE SDK uses its own HTTP client, not Laravel's, so Http::fake() will not intercept it and you'll send real requests in tests.
Line::shouldReceive('replyText')->once()->with('token123', 'hello');
$request->getContent(), not $request->all(). Any middleware that re-encodes or trims the payload (or a proxy like some load balancers) will invalidate the HMAC and every event will fail verification.routes/web.php in many tutorials; either move it to api.php or add the path to VerifyCsrfToken::$except.http:// are rejected. Use ngrok/Expose for local development and point the console at the tunnel.events array. A crash on empty input is the single most common first-deploy failure — guard your loop.linecorp/line-bot-sdk version against current PHP 8.2/8.3 and Laravel 10/11. If composer blocks install, either pin an older Laravel, fork the provider, or — realistically — copy the ~50-line service provider into app/Providers and bind the SDK yourself. The wrapper adds little you can't reproduce, and the official SDK is the part that actually matters.userId per bot, and group members' profiles are not retrievable. Don't assume a line_user_id is globally unique across your channels.textV2 with substitution) replaces the older emoji placeholder syntax; if you're copy-pasting older tutorials the $ emoji indices won't render.How can I help you explore Laravel packages today?