Laravel Integration Guide
Vask is a drop-in Pusher replacement for Laravel Broadcasting and Laravel Echo. The fastest way in is the vask/laravel Composer package — it OAuths into your account, writes the PUSHER_* credentials to .env, and verifies the connection.
#Installation
#Recommended: the vask/laravel package
composer require vask/laravel
php artisan vask:installWhat vask:install does:
- Runs the OAuth device flow (you approve a short code in your browser — no git config or local tokens).
- Writes
PUSHER_APP_ID,PUSHER_APP_KEY,PUSHER_APP_SECRET,PUSHER_HOST,PUSHER_PORT,PUSHER_SCHEME, andPUSHER_APP_CLUSTERto your.env. - Runs
vask:doctorafterwards to confirm everything connects.
Source: github.com/vask-dev/laravel · packagist.org/packages/vask/laravel.
#Manual install
Prefer to wire it up by hand (CI, custom env management, audit)? Add your Vask app credentials to .env:
BROADCAST_CONNECTION=pusher
PUSHER_APP_ID=your_app_key
PUSHER_APP_KEY=your_app_key
PUSHER_APP_SECRET=your_app_secret
PUSHER_HOST=wss.vask.dev
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1Then follow Laravel's broadcasting docs for the rest of the standard setup.
#vask:doctor
After install (or any time you suspect drift), run the diagnostic:
php artisan vask:doctor
php artisan vask:doctor --no-ping --no-broadcast # skip live network checksIt validates your PUSHER_* config, optionally pings wss.vask.dev, and optionally fires a test broadcast end-to-end. Source: github.com/vask-dev/laravel.
#Demo route
vask/laravel ships a local-only page at /_vask/demo. Start your dev server, visit it, click an emoji, and watch the round-trip (Laravel → Vask → browser) — including latency — without writing any frontend code. It exercises both server-side broadcasts and Pusher client events.
The route only registers when app()->environment() === 'local'. Disable it entirely with:
VASK_NO_DEMO=true<?php
namespace App\Events;
use App\Models\Message;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class MessageSent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(
public Message $message,
public string $userName
) {}
public function broadcastOn(): Channel
{
return new Channel('chat.' . $this->message->room_id);
}
public function broadcastAs(): string
{
return 'message.sent';
}
public function broadcastWith(): array
{
return [
'id' => $this->message->id,
'content' => $this->message->content,
'user' => $this->userName,
'created_at' => $this->message->created_at->toISOString(),
];
}
}#Triggering Events
Broadcast events from your controllers or anywhere in your application:
use App\Events\MessageSent;
// In a controller method
public function sendMessage(Request $request)
{
$message = Message::create([
'room_id' => $request->room_id,
'user_id' => auth()->id(),
'content' => $request->content,
]);
// Broadcast the event
broadcast(new MessageSent(
$message,
auth()->user()->name
));
return response()->json($message);
}
// Or use the event() helper
event(new MessageSent($message, $userName));
// For immediate broadcasting (bypass queue)
broadcast(new MessageSent($message, $userName))->toOthers();#Client-Side Setup
#Configure Laravel Echo
Initialize Echo in your bootstrap.js or app.{js,ts,tsx} file:
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'pusher',
key: import.meta.env.VITE_PUSHER_APP_KEY,
cluster: 'vask',
wsHost: 'wss.vask.dev',
forceTLS: true,
enabledTransports: ['ws'],
});#Listening to Events
Subscribe to channels and listen for events in your components:
// Listen to a public channel
Echo.channel('chat.1').listen('.message.sent', (event) => {
console.log('New message:', event);
// Update your UI with the new message
addMessageToChat(event);
});
// Listen to multiple events
Echo.channel('notifications')
.listen('.user.joined', (e) => {
console.log(e.userName + ' joined');
})
.listen('.user.left', (e) => {
console.log(e.userName + ' left');
});
// Leave a channel
Echo.leave('chat.1');<?php
use App\Models\User;
use App\Models\Room;
use Illuminate\Support\Facades\Broadcast;
// Private channel authorization
Broadcast::channel('room.{roomId}', function (User $user, int $roomId) {
// Return true if user can access this room
return $user->rooms()->where('room_id', $roomId)->exists();
});
// Return data with authorization
Broadcast::channel('user.{userId}', function (User $user, int $userId) {
if ($user->id === $userId) {
return [
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar_url,
];
}
return false;
});#Broadcasting to Private Channels
use Illuminate\Broadcasting\PrivateChannel;
class OrderStatusUpdated implements ShouldBroadcast
{
public function broadcastOn(): Channel
{
return new PrivateChannel('user.' . $this->order->user_id);
}
}#Listening to Private Channels
// Subscribe to a private channel
Echo.private('user.' + userId)
.listen('.order.updated', (event) => {
console.log('Order updated:', event.order);
})
.listen('.payment.received', (event) => {
showNotification('Payment received: $' + event.amount);
});#Presence Channels
Presence channels let you track who's online in real-time.
#Authorization with User Data
Broadcast::channel('chat.{roomId}', function (User $user, int $roomId) {
if ($user->canJoinRoom($roomId)) {
return [
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar_url,
];
}
});#Using Presence Channels
use Illuminate\Broadcasting\PresenceChannel;
class UserJoinedRoom implements ShouldBroadcast
{
public function broadcastOn(): Channel
{
return new PresenceChannel('room.' . $this->roomId);
}
}#Client-Side Presence
let onlineUsers = [];
Echo.join('room.' + roomId)
.here((users) => {
// Initial users in the channel
console.log('Users online:', users);
onlineUsers = users;
updateUsersList(users);
})
.joining((user) => {
// User joined the channel
console.log(user.name + ' joined');
onlineUsers.push(user);
addUserToList(user);
})
.leaving((user) => {
// User left the channel
console.log(user.name + ' left');
onlineUsers = onlineUsers.filter((u) => u.id !== user.id);
removeUserFromList(user);
})
.error((error) => {
console.error('Connection error:', error);
})
.listen('.message.sent', (event) => {
// Listen to events on presence channel
addMessageToChat(event);
});#Webhooks
Vask sends webhooks for channel, presence, and client events. The vask/laravel package gives you typed payloads and auto-registers POST /webhooks/vask the first time it sees a handler — no handler, no route.
Register handlers in a service provider's boot():
use Vask\Laravel\Facades\Vask;
use Vask\Laravel\Webhooks\Payloads\ChannelOccupiedPayload;
use Vask\Laravel\Webhooks\Payloads\ChannelVacatedPayload;
use Vask\Laravel\Webhooks\Payloads\MemberAddedPayload;
use Vask\Laravel\Webhooks\Payloads\MemberRemovedPayload;
use Vask\Laravel\Webhooks\Payloads\ClientEventPayload;
public function boot(): void
{
Vask::onChannelOccupied(fn (ChannelOccupiedPayload $event) => /* ... */);
Vask::onChannelVacated(fn (ChannelVacatedPayload $event) => /* ... */);
Vask::onMemberAdded([MemberHandler::class, 'joined']);
Vask::onMemberRemoved([MemberHandler::class, 'left']);
Vask::onClientEvent(LogClientEvent::class); // invokable class
}The route is registered outside the web middleware group, so CSRF doesn't apply. Customise the path or take over registration yourself:
Vask::webhookPath('/api/vask-hooks');
Vask::disableAutoWebhookRoute();php artisan vask:doctorIt catches missing env vars, bad host config, and broken broadcasts before you go hunting through logs.
#Connection Issues
Problem: "Failed to connect to Pusher".
Solution: Check your .env credentials and ensure PUSHER_HOST is set to wss.vask.dev. Re-run php artisan vask:install to rewrite them.
#Events Not Broadcasting
Problem: Events are not being received by clients.
Solutions:
- Ensure your queue worker is running:
php artisan queue:work. - Check that
BROADCAST_CONNECTION=pusherin.env. - Verify the event implements
ShouldBroadcast. - Check Laravel logs for broadcasting errors.
#Private Channel Authorization Failing
Problem: 403 Forbidden when joining private channels.
Solutions:
- Verify authorization logic in
routes/channels.php. - Ensure user is authenticated before subscribing.
- Check that channel names match between backend and frontend.
- Verify CSRF token is being sent with authorization requests.
#Debug Mode
Enable Pusher debug mode to see detailed logs:
window.Echo = new Echo({
// ... other config
enabledTransports: ['ws', 'wss'],
// Enable debug mode
enableLogging: true,
});
// Also enable Pusher logging
Pusher.logToConsole = true;#Next Steps
vask/laravelpackage on GitHub: https://github.com/vask-dev/laravelvask/laravelon Packagist: https://packagist.org/packages/vask/laravel- Laravel Broadcasting Docs
- Laravel Echo on GitHub
Prefer raw markdown? View this page as markdown.