Draft. The packages work and are tested against Pikoman and Pusher, but are not published yet.

Switch from Pusher and keep your subscribe and bind

Pikoman has packages with the same calls as Pusher's libraries. You change an import and your keys. Your channels, events, subscribe, bind, trigger and your auth endpoint stay as they are.

Under the surface the packages speak Pikoman, so we still never hold a secret of yours: you sign with an Ed25519 key and we keep only the public half. Laravel Echo works through the same package, so Echo.private, Echo.join, whisper and toOthers() work as before.

In the browser
One import
On the server
The library and its keys
Price
Free to 100 connections, then €49 a month

1. Get your keys

Sign in at the dashboard with GitHub and create an app. Your browser makes the keys; the private ones are shown once.

At Pusher At Pikoman
In the browser Key a1b2c3d4e5f6a7b8c9d0 and a cluster App id app-yourapp, no cluster
Publishing App id, key and secret An API key sk_…, which may be limited to publishing
Letting users into private channels The same secret The app's private key, one line of base64
What the provider holds Your secret Public keys only

2. Laravel

Download the driver, install it and change .env. Your events, routes/channels.php and ShouldBroadcast classes stay the same.

terminal
mkdir -p packages
curl -o packages/pikoman-compat-laravel-0.1.0.zip https://pikoman.se/downloads/pikoman-compat-laravel-0.1.0.zip
composer config repositories.pikoman artifact ./packages
composer require pikoman/compat-laravel
npm install https://pikoman.se/downloads/pikoman-compat-0.1.0.tgz

Then the keys in .env. VITE_PUSHER_APP_KEY keeps its name, so Echo needs no new setting.

.env, at Pusher
BROADCAST_CONNECTION=pusher

PUSHER_APP_ID=1234567
PUSHER_APP_KEY=a1b2c3d4e5f6a7b8c9d0
PUSHER_APP_SECRET=f0e1d2c3b4a5...
PUSHER_APP_CLUSTER=eu

VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
.env, at Pikoman
BROADCAST_CONNECTION=pikoman

PIKOMAN_APP=app-yourapp
PIKOMAN_API_KEY=sk_...
PIKOMAN_PRIVATE_KEY=MC4CAQAwBQYDK2Vw...
# Only for encrypted channels. Your own key, never sent to us:
# openssl rand -base64 32
PIKOMAN_ENCRYPTION_KEY=...

VITE_PUSHER_APP_KEY="${PIKOMAN_APP}"
VITE_PUSHER_APP_CLUSTER=eu

In resources/js/echo.js, one line changes: the import.

echo.js, at Pusher
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: import.meta.env.VITE_PUSHER_APP_CLUSTER,
  forceTLS: true,
});
echo.js, at Pikoman
import Echo from "laravel-echo";
import Pusher from "@pikoman/compat";
window.Pusher = Pusher;

window.Echo = new Echo({
  broadcaster: "pusher",
  key: import.meta.env.VITE_PUSHER_APP_KEY,
  cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
  forceTLS: true,
});

Then php artisan config:clear and npm run build. Your listeners stay as they are:

Unchanged
Echo.private(`orders.${userId}`)
  .listen("OrderShipped", (e) => console.log(e.order.id));

Echo.join(`room.${roomId}`)
  .here((users) => showUsers(users))
  .joining((user) => addUser(user))
  .leaving((user) => removeUser(user))
  .whisper("typing", { name: me.name });

3. pusher-js without Laravel

Install it with npm install https://pikoman.se/downloads/pikoman-compat-0.1.0.tgz. The import and the key change; subscribe, bind and trigger do not.

At Pusher
import Pusher from "pusher-js";

const pusher = new Pusher("a1b2c3d4e5f6a7b8c9d0", {
  cluster: "eu",
  channelAuthorization: { endpoint: "/pusher/auth" },
});

const room = pusher.subscribe("presence-room");
room.bind("new-message", (data) => show(data));
room.trigger("client-typing", { on: true });
At Pikoman
import Pusher from "@pikoman/compat";

const pusher = new Pusher("app-yourapp", {
  cluster: "eu",
  channelAuthorization: { endpoint: "/pusher/auth" },
});

const room = pusher.subscribe("presence-room");
room.bind("new-message", (data) => show(data));
room.trigger("client-typing", { on: true });

4. Your server

The library and its settings change. trigger and authorizeChannel take the same arguments, so the code that calls them, your auth endpoint included, does not.

Node, at Pusher
import Pusher from "pusher";

const pusher = new Pusher({
  appId: "1234567",
  key: "a1b2c3d4e5f6a7b8c9d0",
  secret: process.env.PUSHER_SECRET,
  cluster: "eu",
});
Node, at Pikoman
import Pusher from "@pikoman/compat/server";

const pusher = new Pusher({
  app: "app-yourapp",
  apiKey: process.env.PIKOMAN_API_KEY,
  privateKey: process.env.PIKOMAN_PRIVATE_KEY,
});
Unchanged
await pusher.trigger("orders", "order-shipped", { id: 1042 });

app.post("/pusher/auth", (req, res) => {
  const { socket_id, channel_name } = req.body;
  if (!mayJoin(req.user, channel_name)) return res.sendStatus(403);
  res.send(pusher.authorizeChannel(socket_id, channel_name, {
    user_id: req.user.id,
    user_info: { name: req.user.name },
  }));
});

PHP without Laravel is not covered yet. Write to hello@pikoman.se if you need it, and we will add it.

5. Move without downtime

  1. Deploy the server change and the browser change together. New page loads connect to Pikoman.
  2. Pages that were open before the deploy still listen at Pusher. If that matters, publish to both for a day: keep the old Pusher client next to the new one and call trigger on each.
  3. When the Pusher dashboard shows no connections, cancel the Pusher plan.

What the adapter covers

Pusher feature With the adapter
Public, private and presence channelsYes
Presence members (here, joining, leaving, members.count)Yes, counted per user as at Pusher
Client events (client-…, Echo's whisper)Yes, 10 a second per connection, as at Pusher
trigger to one or many channelsYes
toOthers() and socket_idYes. The sender's id travels in the event's data, which then has to be an object, as Laravel's always is
private-encrypted- channelsYes. Your backend keeps the master key (PIKOMAN_ENCRYPTION_KEY, or encryptionMasterKeyBase64 as in Pusher's library) and we never see it. Encrypted channels
Channel queries from the server libraryNot yet; GET /channels works over HTTP
WebhooksPikoman's own webhooks, in a different format
User sign-in, watchlists, cache channelsNo
Pusher's iOS and Android libraries, push notifications (Beams)No

Need something marked "not yet"? Write to hello@pikoman.se and we will add it. Channel names follow the same rules as at Pusher: up to 164 characters from A-Z a-z 0-9 _ - = @ , . ;.

Try it on staging first

The free plan has 100 connections and 200,000 messages a day, enough to run your staging site on Pikoman before you move production.

We are rebuilding the dashboard

Self-service sign-up is closed while we do. Until the new dashboard is ready we set up apps by hand: write to us with a line about what you are building, and we will set up your app and get back to you.

Write to hello@pikoman.se