Migrate from PubNub to Vask (Laravel). SDK Swap, Open Protocol.

PubNub to Vask is an SDK swap, not a host swap. Keep the business logic that reacts to messages; replace the transport with Laravel Broadcasting, laravel-echo, and pusher-js.

Migration summary

At a glance

medium risk
Type
SDK-swap migration
Typical time
1-3 days for most Laravel apps
Server SDK
Laravel Broadcasting / pusher-php-server
Client SDK
laravel-echo + pusher-js
Auth changes
replace PubNub tokens with Laravel channel auth
Rollback
feature flag or deploy back to the PubNub publish/subscribe path

Files touched

  • composer.json
  • .env
  • config/broadcasting.php
  • routes/channels.php
  • resources/js/bootstrap.js

Get started

Realtime made simple.

Free Tier: 500K broadcasts/mo and 100 concurrent across unlimited apps. $10/mo when you outgrow it.

Feature mapping

Vask targets the Pusher Channels protocol surface. Map PubNub features before changing code.

| PubNub concept | Vask / Laravel equivalent | Migration note | | ------------------------- | ------------------------------------------- | --------------------------------------------------------------- | | Publish message | broadcast(new Event(...)) | Move publish call sites to Laravel events. | | Subscribe to channel | Echo.channel(...).listen(...) | Move handlers from PubNub listeners to Echo listeners. | | Private channel access | routes/channels.php auth callback | Replace PubNub tokens or PAM checks with Laravel authorization. | | Presence | Echo.join(...).here().joining().leaving() | Same user-awareness goal, different API semantics. | | Client-originated events | Echo whisper on private/presence channels | Use only for ephemeral client events such as typing. | | Functions on publish path | Laravel service, listener, or queued job | Move enrichment/filtering into your application. | | Message history / storage | Your database or existing persistence layer | Not part of this channels cutover. |

Channel and event mapping

PubNub usually combines a flat channel name with a message payload field such as type. In Laravel Broadcasting, channel name and event name are separate.

| PubNub shape | Laravel / Echo call | Pusher wire channel | | ------------------------ | -------------------------- | ------------------- | | orders | Echo.channel('orders') | orders | | user.123 private feed | Echo.private('user.123') | private-user.123 | | room.456 presence room | Echo.join('room.456') | presence-room.456 |

Use broadcastAs() for explicit event names. Echo listeners for custom names need the leading dot:

public function broadcastAs(): string
{
    return 'order.created';
}
Echo.channel('orders').listen('.order.created', (event) => {
    handleOrderCreated(event);
});

If your PubNub payload was { type: 'order.created', order_id: 123 }, the Pusher event name becomes order.created and the handler receives the payload directly.

Out of scope

Keep these PubNub surfaces out of the Vask channels cutover:

| PubNub surface | Recommended path | | ----------------------------- | ----------------------------------------------------- | | Functions | Move logic into Laravel before broadcasting. | | Access Manager / PAM | Replace with Laravel policies and channel auth. | | Message Persistence / history | Store messages in your database before broadcasting. | | Files | Keep existing file storage or move to object storage. | | Mobile Push | Keep your push provider or migrate separately. |

Progressive rollout

Migrate by feature, not by whole application.

  1. Inventory PubNub channels, message types, presence usage, and any Functions on the publish path.
  2. Pick one low-risk feature or channel family.
  3. Add the Vask Broadcast event and Echo listener while PubNub remains live for everything else.
  4. Optionally dual-publish for that feature in staging or behind a feature flag.
  5. Switch the client surface to Echo, verify, then remove that feature's PubNub publish and subscribe path.
  6. Repeat for the next feature.

This keeps rollback local. A failed feature can return to the PubNub path without touching unmigrated features.

Laravel setup

Use the Vask installer if you want the shortest Laravel Broadcasting setup:

composer remove pubnub/pubnub-php
composer require vask/laravel
php artisan vask:install

Manual setup is also fine:

php artisan install:broadcasting --pusher
composer require pusher/pusher-php-server

Set Vask credentials in .env. If your SDK asks for an app id, use the Vask app key. Vask does not issue a separate customer-facing app id.

BROADCAST_CONNECTION=pusher

PUSHER_APP_ID=your_vask_key
PUSHER_APP_KEY=your_vask_key
PUSHER_APP_SECRET=your_vask_secret
PUSHER_HOST=wss.vask.dev
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1

VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

Confirm the pusher connection points at Vask:

'pusher' => [
    'driver' => 'pusher',
    'key' => env('PUSHER_APP_KEY'),
    'secret' => env('PUSHER_APP_SECRET'),
    'app_id' => env('PUSHER_APP_ID'),
    'options' => [
        'host' => env('PUSHER_HOST', 'wss.vask.dev'),
        'port' => env('PUSHER_PORT', 443),
        'scheme' => env('PUSHER_SCHEME', 'https'),
        'encrypted' => true,
        'useTLS' => true,
    ],
],

Server publish

Replace PubNub publish calls:

$pubnub->publish()
    ->channel('orders')
    ->message(['type' => 'order.created', 'order_id' => $id])
    ->sync();

With a Laravel Broadcast event:

use Illuminate\Broadcasting\Channel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class OrderCreated implements ShouldBroadcast
{
    /**
     * @param  array{order_id: int, status: string}  $order
     */
    public function __construct(public array $order) {}

    public function broadcastOn(): array
    {
        return [new Channel('orders')];
    }

    public function broadcastAs(): string
    {
        return 'order.created';
    }

    /**
     * @return array{order_id: int, status: string}
     */
    public function broadcastWith(): array
    {
        return $this->order;
    }
}
broadcast(new OrderCreated([
    'order_id' => $id,
    'status' => 'created',
]));

Client subscribe

Replace PubNub listeners:

const pubnub = new PubNub({ publishKey, subscribeKey });

pubnub.addListener({
    message: ({ message }) => {
        if (message.type === 'order.created') {
            handleOrderCreated(message);
        }
    },
});

pubnub.subscribe({ channels: ['orders'] });

With Echo over pusher-js:

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,
    wsHost: import.meta.env.VITE_PUSHER_HOST,
    wsPort: import.meta.env.VITE_PUSHER_PORT,
    wssPort: import.meta.env.VITE_PUSHER_PORT,
    forceTLS: true,
    enabledTransports: ['ws', 'wss'],
    cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
});

window.Echo.channel('orders').listen('.order.created', (event) => {
    handleOrderCreated(event);
});

Your handler logic stays. The subscription API changes.

Private and presence channels

Move PubNub access checks to Laravel channel authorization:

use App\Models\User;
use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('user.{id}', function (User $user, int $id): bool {
    return $user->id === $id;
});

Broadcast::channel('room.{id}', function (User $user, int $id): array|false {
    if (! $user->canJoinRoom($id)) {
        return false;
    }

    return ['id' => $user->id, 'name' => $user->name];
});
window.Echo.private(`user.${userId}`).listen(
    '.notification.sent',
    handleNotification,
);

window.Echo.join(`room.${roomId}`)
    .here(setPresentUsers)
    .joining(addPresentUser)
    .leaving(removePresentUser);

Verify

Before production cutover:

  1. Trigger one public event and confirm the browser receives it from wss.vask.dev.
  2. Trigger one private event and confirm unauthorized users get a failed auth response.
  3. Join one presence channel and confirm here, joining, and leaving match your UI expectations.
  4. Compare payload shape against the old PubNub handler.
  5. Test reconnect after network interruption.
  6. If dual-publishing, confirm duplicate events do not reach production users.

Rollback

This is not an env-only rollback. Keep the PubNub code path until each migrated feature is stable.

For a migrated feature, rollback by disabling the Echo subscription, restoring the PubNub subscription, and sending server publishes back through the PubNub SDK. If you use feature flags, keep one flag for server publish path and one for client subscribe path so you can unwind safely.

Gotchas

  • Payload envelope changes. PubNub listeners receive a message envelope. Echo listeners receive the event payload directly.
  • Custom event names need a dot. If Laravel broadcastAs() returns order.created, listen with .order.created.
  • Presence state is not method-compatible. PubNub hereNow() and getState() map to Echo callbacks and your own persisted state.
  • Functions must move first. If a PubNub Function mutates an event, move that logic into Laravel before switching the channel.

Where to go next

If your migration runs into something this page does not cover, email [email protected] and you will get an answer from someone who can read your code.

Which PubNub features are unsupported?
Vask covers the Pusher Channels surface: public channels, private channels, presence channels, broadcasts, client events, and channel auth. PubNub Functions, Access Manager, Files, Mobile Push, message history, and storage are separate product surfaces. Keep or replace those outside this channels migration.
Can we migrate one feature at a time?
Yes. Move one feature, channel family, or screen at a time. Keep PubNub running for untouched features, optionally dual-publish during validation, then switch that feature's client subscription to Echo and remove the old PubNub path after it is stable.
Does presence work the same way as PubNub presence?
The behavior maps, but the API does not. PubNub hereNow and state calls become Echo presence callbacks such as here, joining, and leaving, backed by Laravel channel authorization. Re-test member identity, join/leave timing, and any custom state you previously stored in PubNub.
What should we test before production cutover?
Test one public channel, one private channel, one presence channel, failed auth, reconnect behavior, payload shape, duplicate delivery during any dual-publish window, and rollback to the PubNub client path. Run the test from the same browser/runtime mix your users use.

Get going

Replace the PubNub SDK feature by feature, keep the message-handling logic, and move delivery to the open Pusher Channels protocol at the Vask edge.

Make the switch

Open protocol. Edge delivery. Done.

Replace the PubNub SDK, keep your application logic, and roll out one feature at a time.