{"id":1121,"date":"2026-09-04T13:45:37","date_gmt":"2026-09-04T05:45:37","guid":{"rendered":"https:\/\/witlab.ph\/blog\/?p=1121"},"modified":"2026-09-04T13:45:38","modified_gmt":"2026-09-04T05:45:38","slug":"laravel-reverb-realtime-websockets-without-the-saas-bill","status":"publish","type":"post","link":"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/","title":{"rendered":"Laravel Reverb: realtime WebSockets without the SaaS bill"},"content":{"rendered":"\n<p><\/p>\n\n\n\n<p>Most of what we build with Laravel fits comfortably inside one shape: the browser asks, the server answers, the connection closes. Load a page. Save a record. Fetch some JSON. HTTP was designed for exactly this, and it&#8217;s very good at it.<\/p>\n\n\n\n<p>Then a product manager asks for a chat box.<\/p>\n\n\n\n<p>Suddenly the server knows something the client never asked about. A message arrived. A job finished. Someone joined the room. There is no open request to answer, and refreshing the page is not a feature.<\/p>\n\n\n\n<p>This post walks through how Laravel solves that today \u2014 with **Reverb**, its own first-party WebSocket server \u2014 and includes a small demo app with public, private, and presence channels wired end to end.<\/p>\n\n\n\n<h2 class=\"wp-block-heading has-medium-font-size\">What &#8220;realtime&#8221; actually means<\/h2>\n\n\n\n<p>Realtime isn&#8217;t &#8220;fast.&#8221; It&#8217;s <em>*directional*<\/em>. The server can initiate.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A chat message appears in every open browser the moment it&#8217;s sent.<\/li>\n\n\n\n<li>A notification lands only on one user&#8217;s tab.<\/li>\n\n\n\n<li>You can see who is currently in the room.<\/li>\n\n\n\n<li>An announcement fans out to every visitor at once.<\/li>\n<\/ul>\n\n\n\n<p>Before WebSockets, we faked this three ways, and each has a tell:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td>Approach<\/td><td>Direction<\/td><td>Latency<\/td><td>Cost<\/td><\/tr><tr><td>Short polling<\/td><td>Client \u2192 server<\/td><td>Interval delay<\/td><td>Mostly empty requests<\/td><\/tr><tr><td>Long polling<\/td><td>Client \u2192 server (held)<\/td><td>Low<\/td><td>Holds a connection <em>*and*<\/em> a worker<\/td><\/tr><tr><td>Server-Sent Events<\/td><td>Server \u2192 client<\/td><td>Low<\/td><td>Light, but one-way<\/td><\/tr><tr><td>**WebSockets**<\/td><td>**Both ways, persistent**<\/td><td>**Near instant**<\/td><td>**One connection, then tiny frames**<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Polling every three seconds means a hundred users generate two thousand requests a minute to tell you nothing happened. SSE fixes the wasted requests but only flows one way, which rules out chat and typing indicators. WebSockets start life as an HTTP request, get a `101 Switching Protocols` back, and then leave a two-way pipe open. Either side can speak at any time.<\/p>\n\n\n\n<h2 class=\"wp-block-heading has-medium-font-size\">Laravel&#8217;s model: you don&#8217;t touch sockets<\/h2>\n\n\n\n<p><\/p>\n\n\n\n<p>The thing that trips people up is expecting to write socket code. You don&#8217;t. Laravel&#8217;s broadcasting is an event pipeline:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Event (ShouldBroadcast) \u2192 Broadcaster (Reverb\/Pusher\/Ably) \u2192 Channel \u2192 Echo (browser)<\/code><\/pre>\n\n\n\n<p>Your PHP dispatches an event. A broadcaster delivers it to a WebSocket server. The browser subscribes with **Laravel Echo**. The application code never manages a connection.<\/p>\n\n\n\n<p>That indirection exists for a very PHP-shaped reason: a typical PHP process is request-scoped. It boots, responds, and dies. It&#8217;s a terrible place to hold ten thousand open sockets for six hours. So the sockets live somewhere else.<\/p>\n\n\n\n<h2 class=\"wp-block-heading has-medium-font-size\">Where Pusher comes in \u2014 and why the name is everywhere<\/h2>\n\n\n\n<p><\/p>\n\n\n\n<p>For years, &#8220;somewhere else&#8221; meant [Pusher](https:\/\/pusher.com), a hosted WebSocket service. You POST events to their cloud, they fan them out to connected clients, you pay per connection and per message.<\/p>\n\n\n\n<p>But Pusher is two things, and conflating them is what makes the docs confusing:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>**A company** selling hosted realtime infrastructure.<\/li>\n\n\n\n<li>**A protocol** \u2014 named channels, named events, a signed auth handshake for private channels \u2014 plus `pusher-js`, the browser library that speaks it.<\/li>\n<\/ul>\n\n\n\n<p>Laravel Echo has always been a thin wrapper over `pusher-js`. That&#8217;s the key detail, because it means anything that speaks the Pusher protocol can serve a Laravel frontend without touching the frontend.<\/p>\n\n\n\n<p>Hosted Pusher is genuinely good: zero ops, global edge, TLS handled, mature clients. The catch is the usual one. Your bill scales with concurrent connections, your users&#8217; messages traverse someone else&#8217;s infrastructure, and connection limits become an architectural constraint you didn&#8217;t choose.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading has-medium-font-size\">Reverb: the missing piece<\/h2>\n\n\n\n<p>Laravel already knew how to broadcast. What it lacked was its own server to broadcast <em>*to*<\/em>. That&#8217;s Reverb, shipped with Laravel 11:<\/p>\n\n\n\n<p>**First-party.** Installed with Composer, configured in `.env`, started with Artisan.<\/p>\n\n\n\n<p>**Pusher-protocol compatible.** Your events, channels, Echo code, and `pusher-js` keep working. Switching is mostly env + one config line.<\/p>\n\n\n\n<p>**Self-hosted.** The sockets terminate on your infrastructure. No per-connection invoice, and chat\/presence data never leaves your network.**Horizontally scalable.** Multiple Reverb nodes share connection state over Redis.<\/p>\n\n\n\n<p>The trade you&#8217;re making is explicit: you take on a long-running process to manage, and in return you get cost predictability and data locality. If you already run queue workers, you already run this kind of thing.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><\/td><td>Reverb<\/td><td>Pusher Cloud<\/td><\/tr><tr><td>Hosting<\/td><td>You run the process<\/td><td>They run it<\/td><\/tr><tr><td>Echo config<\/td><td>`broadcaster: &#8216;reverb&#8217;`<\/td><td>`broadcaster: &#8216;pusher&#8217;`<\/td><\/tr><tr><td>Cost model<\/td><td>Your compute<\/td><td>Per connection \/ message<\/td><\/tr><tr><td>Best when<\/td><td>You already operate infra<\/td><td>You want zero realtime ops<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading has-medium-font-size\">Setting it up<\/h2>\n\n\n\n<p class=\"has-medium-font-size\">1. Install<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>php artisan install:broadcasting\n\n# or explicitly\n\ncomposer require laravel\/reverb\nphp artisan reverb:install<\/code><\/pre>\n\n\n\n<p>The installer publishes `config\/reverb.php`, scaffolds Echo, and writes the env keys.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>2. Point broadcasting at Reverb<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>BROADCAST_CONNECTION=reverb\n\nREVERB_APP_ID=my-app-id\nREVERB_APP_KEY=my-app-key\nREVERB_APP_SECRET=my-app-secret\nREVERB_HOST=\"localhost\"\nREVERB_PORT=8080\nREVERB_SCHEME=http\n\nVITE_REVERB_APP_KEY=\"${REVERB_APP_KEY}\"\nVITE_REVERB_HOST=\"${REVERB_HOST}\"\nVITE_REVERB_PORT=\"${REVERB_PORT}\"\nVITE_REVERB_SCHEME=\"${REVERB_SCHEME}\"<\/code><\/pre>\n\n\n\n<p>The `VITE_` duplicates matter: only prefixed variables are exposed to the frontend bundle. Note that the secret is <em>*not*<\/em> among them \u2014 it stays server-side, where it signs channel authorization.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>3. Connect Echo<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ resources\/js\/echo.js\nimport Echo from 'laravel-echo';\nimport Pusher from 'pusher-js';\n\nwindow.Pusher = Pusher;\n\nwindow.Echo = new Echo({\n    broadcaster: 'reverb',\n    key: import.meta.env.VITE_REVERB_APP_KEY,\n    wsHost: import.meta.env.VITE_REVERB_HOST,\n    wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,\n    wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,\n    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',\n    enabledTransports: &#91;'ws', 'wss'],\n});<\/code><\/pre>\n\n\n\n<p>Yes, you still install `pusher-js` \u2014 Reverb speaks that protocol, so that&#8217;s the client.<\/p>\n\n\n\n<p>4. Broadcast an event<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class AnnouncementPublished implements ShouldBroadcastNow\n{\n    use Dispatchable, InteractsWithSockets, SerializesModels;\n\n    public function __construct(public string $message) {}\n\n    public function broadcastOn(): array\n    {\n        return &#91;new Channel('announcements')];\n    }\n\n    public function broadcastAs(): string\n    {\n        return 'announcement.published';\n    }\n\n    public function broadcastWith(): array\n    {\n        return &#91;\n            'message' => $this->message,\n            'published_at' => now()->toIso8601String(),\n        ];\n    }\n}<\/code><\/pre>\n\n\n\n<p>Three methods worth knowing:<\/p>\n\n\n\n<p>&#8211; `broadcastOn()` \u2014 which channel(s). `Channel`, `PrivateChannel`, or `PresenceChannel`.<\/p>\n\n\n\n<p>&#8211; `broadcastAs()` \u2014 a stable event name instead of the fully-qualified class name. Rename the class later without breaking the frontend.<\/p>\n\n\n\n<p>&#8211; `broadcastWith()` \u2014 the exact payload. Without it, all public properties get serialized, which is how internal fields leak to the browser.<\/p>\n\n\n\n<p>`ShouldBroadcastNow` sends inline during the request. `ShouldBroadcast` queues it. Use `Now` in demos, queue it under real load \u2014 otherwise fan-out latency lands inside your HTTP response time.<\/p>\n\n\n\n<p>5. Authorize private and presence channels<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/ routes\/channels.php\nBroadcast::channel('notifications.{userId}', function (User $user, int $userId): bool {\n    return $user->id === $userId;\n});\n\nBroadcast::channel('chat.{room}', function (User $user, string $room) {\n    if ($room !== 'lobby') {\n        return false;\n    }\n\n    return &#91;'id' => $user->id, 'name' => $user->name];\n});<\/code><\/pre>\n\n\n\n<p>This is the security boundary, and it&#8217;s easy to misread. When a client subscribes to a private or presence channel, `pusher-js` first POSTs to `\/broadcasting\/auth` with the channel name and socket id. Laravel runs the matching callback **using your normal session auth**, and only then hands back a signed token the WebSocket server will accept.<\/p>\n\n\n\n<p>The difference between the two callbacks is the return type. A boolean gates a private channel. An **array** turns it into a presence channel: that array becomes the member&#8217;s public info, visible to everyone else in the room.<\/p>\n\n\n\n<p>6. Listen in the browser<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>window.Echo.channel('announcements')\n    .listen('.announcement.published', (event) => { \/* ... *\/ });\n\nwindow.Echo.private(`notifications.${userId}`)\n    .listen('.notification.sent', (event) => { \/* ... *\/ });\n\nwindow.Echo.join('chat.lobby')\n    .here((users) => { \/* who is already here *\/ })\n    .joining((user) => { \/* someone arrived *\/ })\n    .leaving((user) => { \/* someone left *\/ })\n    .listen('.chat.message', (event) => { \/* ... *\/ });<\/code><\/pre>\n\n\n\n<p>Two details that cost people an afternoon each:<\/p>\n\n\n\n<p>**The leading dot.** `.listen(&#8216;.announcement.published&#8217;)` means &#8220;this is the literal event name.&#8221; Drop the dot and Echo prefixes your app namespace and expects `App\\Events\\&#8230;`. If your listener silently never fires, check the dot first.<\/p>\n\n\n\n<p>**Channel name prefixes.** `Echo.private(&#8216;notifications.1&#8217;)` talks to a channel the server sees as `private-notifications.1`, and `Echo.join(&#8216;chat.lobby&#8217;)` becomes `presence-chat.lobby`. You write the short name in `channels.php`; the prefix is added by the protocol. This surfaces the moment you write a test:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$this->actingAs($user)->post('\/broadcasting\/auth', &#91;\n    'socket_id' => '1234.5678',\n    'channel_name' => 'presence-chat.lobby',\n])->assertOk();<\/code><\/pre>\n\n\n\n<p>That endpoint is testable in isolation, which makes channel authorization one of the few parts of realtime you can cover without booting a WebSocket server at all. Assert that a user can authorize their own private channel, that another user gets a 403, and that guests get nothing.<\/p>\n\n\n\n<p>7. Run the server<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>php artisan reverb:start\n\n# with Sail\n.\/vendor\/bin\/sail artisan reverb:start\n\n# whole stack at once\ncomposer run dev<\/code><\/pre>\n\n\n\n<p>If you&#8217;re using Sail, remember to publish the port:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ports:\n    - '${APP_PORT:-80}:80'\n    - '${VITE_PORT:-5173}:${VITE_PORT:-5173}'\n    - '${REVERB_PORT:-8080}:8080'<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading has-medium-font-size\">The `X-Socket-ID` header, or: why you hear your own echo<\/h2>\n\n\n\n<p>Here&#8217;s the bug you will write. The user submits a chat message, your JS optimistically appends it to the list, the event broadcasts, and the same browser receives it back \u2014 so the message appears twice.<\/p>\n\n\n\n<p>Laravel&#8217;s answer is `toOthers()`, which excludes the originating connection. For that to work, the server has to know which connection sent the request, so the frontend must forward its socket id on the HTTP call:<\/p>\n\n\n\n<p>Then on the PHP side, `broadcast(new ChatMessageSent(&#8230;))->toOthers()` instead of `Event::dispatch`. Send the header even if you aren&#8217;t using `toOthers()` yet \u2014 it costs nothing and it&#8217;s there when you need it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading has-medium-font-size\">A working playground<\/h2>\n\n\n\n<p>The demo app pairs three routes with three channel types, which is enough to feel all the differences on one page:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td>Channel<\/td><td>Type<\/td><td>Who receives it<\/td><\/tr><tr><td>`announcements`<\/td><td>Public<\/td><td>Every visitor, logged in or not<\/td><\/tr><tr><td>`notifications.{id}`<\/td><td>Private<\/td><td>Only that one user<\/td><\/tr><tr><td>`chat.lobby`<\/td><td>Presence<\/td><td>Members of the room, plus join\/leave<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading has-medium-font-size\">The controller is unremarkable, which is the point:<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>public function announce(Request $request): JsonResponse\n{\n    $validated = $request->validate(&#91;\n        'message' => &#91;'required', 'string', 'max:255'],\n    ]);\n\n    AnnouncementPublished::dispatch($validated&#91;'message']);\n\n    return response()->json(&#91;'ok' => true]);\n}<\/code><\/pre>\n\n\n\n<p>Validate, dispatch, return. Nothing in your HTTP layer knows a WebSocket exists.<\/p>\n\n\n\n<p>The best way to actually <em>*see*<\/em> the distinction is to open two browsers and log in as two different users. Send an announcement: both see it. Send a notification: only one does. Watch the presence list update as the second browser opens and closes. Presence in particular is hard to appreciate until you watch a name appear in another window.<\/p>\n\n\n\n<p>One caution while you&#8217;re in there: **the channel name in `broadcastOn()` and the name in `channels.php` must match exactly.** Broadcasting on `new PresenceChannel(&#8216;chat.robby&#8217;)` while authorizing and joining `chat.lobby` produces no error anywhere \u2014 no exception, no log line, no failed request. The event goes out to a channel nobody is listening on, and the UI simply stays quiet. When realtime &#8220;doesn&#8217;t work,&#8221; compare those two strings before anything else.<\/p>\n\n\n\n<h2 class=\"wp-block-heading has-medium-font-size\">Before you ship it<\/h2>\n\n\n\n<p>Reverb in development is one Artisan command. Production asks for five more decisions:<\/p>\n\n\n\n<p>**TLS.** Browsers on an HTTPS page will refuse a `ws:\/\/` connection. Terminate SSL at Nginx, Caddy, or your load balancer and proxy through to Reverb, then set `REVERB_SCHEME=https` so Echo dials `wss:\/\/`.<\/p>\n\n\n\n<p>**A process manager.** `reverb:start` is a long-running process; if it dies, realtime silently stops while the rest of the app looks perfectly healthy. Supervisor, systemd, or a Forge daemon \u2014 same treatment as a queue worker. Restart it on deploy, too, since it holds your code in memory.<\/p>\n\n\n\n<p>**Scaling.** One Reverb node keeps connections in its own memory, so two nodes behind a load balancer can&#8217;t see each other&#8217;s subscribers. Turn on Redis scaling:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>REVERB_SCALING_ENABLED=true<\/code><\/pre>\n\n\n\n<p>Nodes then publish over a shared Redis channel and every connection gets its event regardless of which node it landed on.<\/p>\n\n\n\n<p>**Queues.** Once traffic is real, switch `ShouldBroadcastNow` to `ShouldBroadcast` so fan-out happens in a worker instead of inside the user&#8217;s request.<\/p>\n\n\n\n<p>**Allowed origins.** Restrict `config\/reverb.php` to your own domains rather than leaving the default wildcard.<\/p>\n\n\n\n<p>**File descriptors.** Every connection is an open socket. The default `ulimit -n` of 1024 will cap you long before your CPU does.<\/p>\n\n\n\n<h2 class=\"wp-block-heading has-medium-font-size\">The takeaway<\/h2>\n\n\n\n<p>Realtime is the server pushing events the moment they happen, and WebSockets are how you do it without burning requests on polling. Pusher popularized the protocol that Laravel Echo still speaks. Reverb implements that protocol on your own hardware, first-party, with the same PHP API you already know.<\/p>\n\n\n\n<p>The whole path is: install \u2192 env \u2192 Echo \u2192 events \u2192 channel auth \u2192 `reverb:start`. If you&#8217;ve been putting off the chat feature because the realtime bill looked scary, that excuse is gone.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Most of what we build with Laravel fits comfortably inside one shape: the browser asks, the server answers, the connection closes. Load a page. Save a record. Fetch some JSON. HTTP was designed for exactly this, and it&#8217;s very good at it. Then a product manager asks for a chat box. Suddenly the server knows [&hellip;]<\/p>\n","protected":false},"author":7,"featured_media":1133,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-1121","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-system"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v23.7 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Laravel Reverb: realtime WebSockets without the SaaS bill - WIT LAB %<\/title>\n<meta name=\"description\" content=\"We excel in utilizing cutting-edge technology, programming languages, and frameworks to deliver high-quality digital solutions.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Laravel Reverb: realtime WebSockets without the SaaS bill - WIT LAB %\" \/>\n<meta property=\"og:description\" content=\"We excel in utilizing cutting-edge technology, programming languages, and frameworks to deliver high-quality digital solutions.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/\" \/>\n<meta property=\"og:site_name\" content=\"WIT LAB\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/people\/WIT-LAB\/61567795364273\/\" \/>\n<meta property=\"article:published_time\" content=\"2026-09-04T05:45:37+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-04T05:45:38+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/witlab.ph\/blog\/wp-content\/uploads\/2026\/09\/cover_laravel_reverb.png\" \/>\n\t<meta property=\"og:image:width\" content=\"700\" \/>\n\t<meta property=\"og:image:height\" content=\"366\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Thuta Yar Moe\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Thuta Yar Moe\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"8 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":[\"Article\",\"BlogPosting\"],\"@id\":\"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/\"},\"author\":{\"name\":\"Thuta Yar Moe\",\"@id\":\"https:\/\/witlab.ph\/blog\/#\/schema\/person\/9a653900ccc3f52126d3e372603f3617\"},\"headline\":\"Laravel Reverb: realtime WebSockets without the SaaS bill\",\"datePublished\":\"2026-09-04T05:45:37+00:00\",\"dateModified\":\"2026-09-04T05:45:38+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/\"},\"wordCount\":1703,\"publisher\":{\"@id\":\"https:\/\/witlab.ph\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/witlab.ph\/blog\/wp-content\/uploads\/2026\/09\/cover_laravel_reverb.png\",\"articleSection\":[\"System\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/\",\"url\":\"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/\",\"name\":\"Laravel Reverb: realtime WebSockets without the SaaS bill - WIT LAB %\",\"isPartOf\":{\"@id\":\"https:\/\/witlab.ph\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/witlab.ph\/blog\/wp-content\/uploads\/2026\/09\/cover_laravel_reverb.png\",\"datePublished\":\"2026-09-04T05:45:37+00:00\",\"dateModified\":\"2026-09-04T05:45:38+00:00\",\"description\":\"We excel in utilizing cutting-edge technology, programming languages, and frameworks to deliver high-quality digital solutions.\",\"breadcrumb\":{\"@id\":\"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/#primaryimage\",\"url\":\"https:\/\/witlab.ph\/blog\/wp-content\/uploads\/2026\/09\/cover_laravel_reverb.png\",\"contentUrl\":\"https:\/\/witlab.ph\/blog\/wp-content\/uploads\/2026\/09\/cover_laravel_reverb.png\",\"width\":700,\"height\":366},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/witlab.ph\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Laravel Reverb: realtime WebSockets without the SaaS bill\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/witlab.ph\/blog\/#website\",\"url\":\"https:\/\/witlab.ph\/blog\/\",\"name\":\"WIT LAB\",\"description\":\"Web Development\",\"publisher\":{\"@id\":\"https:\/\/witlab.ph\/blog\/#organization\"},\"alternateName\":\"WIT LAB INC\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/witlab.ph\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/witlab.ph\/blog\/#organization\",\"name\":\"WIT LAB INC\",\"alternateName\":\"Spiceworks (Japan)\",\"url\":\"https:\/\/witlab.ph\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/witlab.ph\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/witlab.ph\/blog\/wp-content\/uploads\/2024\/09\/logo_witlab-Copy-Copy.png\",\"contentUrl\":\"https:\/\/witlab.ph\/blog\/wp-content\/uploads\/2024\/09\/logo_witlab-Copy-Copy.png\",\"width\":681,\"height\":616,\"caption\":\"WIT LAB INC\"},\"image\":{\"@id\":\"https:\/\/witlab.ph\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/people\/WIT-LAB\/61567795364273\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/witlab.ph\/blog\/#\/schema\/person\/9a653900ccc3f52126d3e372603f3617\",\"name\":\"Thuta Yar Moe\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/witlab.ph\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/b2af6b0444bf2ed0e9bc446ac7ee374a?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/b2af6b0444bf2ed0e9bc446ac7ee374a?s=96&d=mm&r=g\",\"caption\":\"Thuta Yar Moe\"},\"url\":\"https:\/\/witlab.ph\/blog\/author\/thuta\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Laravel Reverb: realtime WebSockets without the SaaS bill - WIT LAB %","description":"We excel in utilizing cutting-edge technology, programming languages, and frameworks to deliver high-quality digital solutions.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/","og_locale":"en_US","og_type":"article","og_title":"Laravel Reverb: realtime WebSockets without the SaaS bill - WIT LAB %","og_description":"We excel in utilizing cutting-edge technology, programming languages, and frameworks to deliver high-quality digital solutions.","og_url":"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/","og_site_name":"WIT LAB","article_publisher":"https:\/\/www.facebook.com\/people\/WIT-LAB\/61567795364273\/","article_published_time":"2026-09-04T05:45:37+00:00","article_modified_time":"2026-09-04T05:45:38+00:00","og_image":[{"width":700,"height":366,"url":"https:\/\/witlab.ph\/blog\/wp-content\/uploads\/2026\/09\/cover_laravel_reverb.png","type":"image\/png"}],"author":"Thuta Yar Moe","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Thuta Yar Moe","Est. reading time":"8 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":["Article","BlogPosting"],"@id":"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/#article","isPartOf":{"@id":"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/"},"author":{"name":"Thuta Yar Moe","@id":"https:\/\/witlab.ph\/blog\/#\/schema\/person\/9a653900ccc3f52126d3e372603f3617"},"headline":"Laravel Reverb: realtime WebSockets without the SaaS bill","datePublished":"2026-09-04T05:45:37+00:00","dateModified":"2026-09-04T05:45:38+00:00","mainEntityOfPage":{"@id":"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/"},"wordCount":1703,"publisher":{"@id":"https:\/\/witlab.ph\/blog\/#organization"},"image":{"@id":"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/#primaryimage"},"thumbnailUrl":"https:\/\/witlab.ph\/blog\/wp-content\/uploads\/2026\/09\/cover_laravel_reverb.png","articleSection":["System"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/","url":"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/","name":"Laravel Reverb: realtime WebSockets without the SaaS bill - WIT LAB %","isPartOf":{"@id":"https:\/\/witlab.ph\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/#primaryimage"},"image":{"@id":"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/#primaryimage"},"thumbnailUrl":"https:\/\/witlab.ph\/blog\/wp-content\/uploads\/2026\/09\/cover_laravel_reverb.png","datePublished":"2026-09-04T05:45:37+00:00","dateModified":"2026-09-04T05:45:38+00:00","description":"We excel in utilizing cutting-edge technology, programming languages, and frameworks to deliver high-quality digital solutions.","breadcrumb":{"@id":"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/#primaryimage","url":"https:\/\/witlab.ph\/blog\/wp-content\/uploads\/2026\/09\/cover_laravel_reverb.png","contentUrl":"https:\/\/witlab.ph\/blog\/wp-content\/uploads\/2026\/09\/cover_laravel_reverb.png","width":700,"height":366},{"@type":"BreadcrumbList","@id":"https:\/\/witlab.ph\/blog\/laravel-reverb-realtime-websockets-without-the-saas-bill\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/witlab.ph\/blog\/"},{"@type":"ListItem","position":2,"name":"Laravel Reverb: realtime WebSockets without the SaaS bill"}]},{"@type":"WebSite","@id":"https:\/\/witlab.ph\/blog\/#website","url":"https:\/\/witlab.ph\/blog\/","name":"WIT LAB","description":"Web Development","publisher":{"@id":"https:\/\/witlab.ph\/blog\/#organization"},"alternateName":"WIT LAB INC","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/witlab.ph\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/witlab.ph\/blog\/#organization","name":"WIT LAB INC","alternateName":"Spiceworks (Japan)","url":"https:\/\/witlab.ph\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/witlab.ph\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/witlab.ph\/blog\/wp-content\/uploads\/2024\/09\/logo_witlab-Copy-Copy.png","contentUrl":"https:\/\/witlab.ph\/blog\/wp-content\/uploads\/2024\/09\/logo_witlab-Copy-Copy.png","width":681,"height":616,"caption":"WIT LAB INC"},"image":{"@id":"https:\/\/witlab.ph\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/people\/WIT-LAB\/61567795364273\/"]},{"@type":"Person","@id":"https:\/\/witlab.ph\/blog\/#\/schema\/person\/9a653900ccc3f52126d3e372603f3617","name":"Thuta Yar Moe","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/witlab.ph\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/b2af6b0444bf2ed0e9bc446ac7ee374a?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/b2af6b0444bf2ed0e9bc446ac7ee374a?s=96&d=mm&r=g","caption":"Thuta Yar Moe"},"url":"https:\/\/witlab.ph\/blog\/author\/thuta\/"}]}},"_links":{"self":[{"href":"https:\/\/witlab.ph\/blog\/wp-json\/wp\/v2\/posts\/1121"}],"collection":[{"href":"https:\/\/witlab.ph\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/witlab.ph\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/witlab.ph\/blog\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/witlab.ph\/blog\/wp-json\/wp\/v2\/comments?post=1121"}],"version-history":[{"count":11,"href":"https:\/\/witlab.ph\/blog\/wp-json\/wp\/v2\/posts\/1121\/revisions"}],"predecessor-version":[{"id":1132,"href":"https:\/\/witlab.ph\/blog\/wp-json\/wp\/v2\/posts\/1121\/revisions\/1132"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/witlab.ph\/blog\/wp-json\/wp\/v2\/media\/1133"}],"wp:attachment":[{"href":"https:\/\/witlab.ph\/blog\/wp-json\/wp\/v2\/media?parent=1121"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/witlab.ph\/blog\/wp-json\/wp\/v2\/categories?post=1121"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/witlab.ph\/blog\/wp-json\/wp\/v2\/tags?post=1121"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}