PHP answers a request and exits, so it cannot hold thousands of sockets open. Broadcasting splits the job: the app posts each event over HTTP to a WebSocket server, which pushes it to every subscribed client.

A public Channel needs no permission, which suits a stock ticker. A private PrivateChannel (prefix private-) asks your app first. The client posts its socket id to /broadcasting/auth, the rule in routes/channels.php answers, and a yes returns an HMAC-SHA256 of socket_id:channel keyed with the app secret, which Reverb 2,157 checks. A presence channel is private and also returns user data, so members see who else is online. A broadcast event implements ShouldBroadcast, which is queued and needs a worker (Queues and Scheduling), or ShouldBroadcastNow, which sends during the request:
class OrderShipped implements ShouldBroadcastNow
{
// ... Dispatchable, InteractsWithSockets, SerializesModels; public Order $order
public function broadcastOn(): array
{
return [new PrivateChannel('orders.'.$this->order->id)];
}
// ... broadcastAs() returns 'order.shipped'
public function broadcastWith(): array
{
return ['id' => $this->order->id, 'status' => $this->order->status,
'shipped_at' => $this->order->shipped_at->toIso8601String()];
}
}
// routes/channels.php
Broadcast::channel('orders.{order}', function (User $user, Order $order) {
return $user->id === $order->user_id;
});{order} is model bound (Route Model Binding). Without broadcastWith(), every public property is serialized, including the whole order.