Events

Events are the messages that flow through channels. Understanding how to send, receive, and respond to them, including the system events Soketify fires automatically, is the core of building anything real-time.

What is an Event?

An event is a named message sent on a channel. It has three parts:

  • Channel: where the event is sent (e.g., private-user-42)
  • Name: what happened (e.g., order-shipped)
  • Data: the JSON payload (e.g., { orderId: "ORD-1234" })

Events travel in one of three directions:

Your Server
Soketify
Client A
Client B
trigger event (server event)
order-shipped
order-shipped
pusher:subscription_succeeded (system event)
client-typing (client event)
client-typing
TypeDirectionPrefix
Server eventsYour server → subscribersAny name (your choice)
System eventsSoketify → your clientpusher:
Client eventsClient → other subscribersclient-

Event Naming Rules

  • Maximum 200 characters
  • Must not start with pusher:: that prefix is reserved for system events
  • Client events must start with client-
  • Everything else is valid: hyphens, underscores, dots, mixed case
Event naming
// Good event names
"order-shipped"
"message.created"
"user_status_changed"
"PRICE_UPDATE"
"payment:completed"    // custom namespacing is fine

// Invalid
"pusher:my-event"      // reserved prefix
"client-typing"        // only valid on private/presence channels
                       // and only from the client side

Naming Convention

Name events in past tense to describe what happened: order-shipped, message-created, user-joined. This makes your event log read like a timeline, which is exactly what it is.

Data Limits

LimitValue
Event payload size10 KB
Event name length200 characters
Channels per trigger call100
Client event rate limit10 per second per connection

Keep payloads small

The 10 KB limit is per event. If you need to share large data (images, documents), store it elsewhere and send a URL or reference ID in the event. Real-time events are notifications, so let your existing APIs serve the heavy content.

Binding to Events

There are four ways to bind to events, depending on how broadly you want to listen.

1. Channel-level binding

The most common pattern. Only fires for events on that specific channel.

channel-bind.js
javascript
1const channel = pusher.subscribe("orders");
2
3channel.bind("order-shipped", (data) => {
4  console.log("Order shipped:", data.orderId);
5});
6
7// Optional context object as third argument
8const context = { component: this };
9channel.bind("order-shipped", handler, context);

2. Global client binding

Fires whenever that event name appears on any subscribed channel. Useful for cross-channel listeners.

global-bind.js
javascript
1// Fires when "notification" arrives on ANY subscribed channel
2pusher.bind("notification", (data) => {
3  showToast(data.message);
4});

3. Catch-all binding

Fires for every event on every subscribed channel. Useful for debugging or logging.

bind-global.js
javascript
1pusher.bind_global((eventName, data) => {
2  console.log(`[${eventName}]`, data);
3});

4. Unbinding

unbind.js
javascript
1// Remove a specific callback
2channel.unbind("order-shipped", myHandler);
3
4// Remove all callbacks for an event name
5channel.unbind("order-shipped");
6
7// Remove all callbacks on a channel
8channel.unbind_all();
9
10// Remove a global binding
11pusher.unbind("notification", myHandler);

System Events

Soketify fires system events with the pusher: prefix. You cannot trigger these yourself: they are how Soketify tells your client what is happening with the connection and subscriptions.

pusher:subscription_succeeded

Fires when a channel subscription is confirmed. For presence channels it carries the full member list. For all other channels the payload is empty.

subscription-succeeded.js
javascript
1// Public or private channel
2channel.bind("pusher:subscription_succeeded", () => {
3  console.log("Subscribed!");
4  // Safe to trigger client events from here
5});
6
7// Presence channel — includes member data
8presenceChannel.bind("pusher:subscription_succeeded", (members) => {
9  console.log("Members online:", members.count);
10  members.each((member) => {
11    addToUserList(member.id, member.info);
12  });
13});

pusher:subscription_error

Fires when a private or presence channel subscription fails, usually because your auth endpoint returned a non-200 response.

subscription-error.js
javascript
1channel.bind("pusher:subscription_error", (error) => {
2  console.error("Subscription failed:", error.status, error.error);
3  // error.status: HTTP status from your auth endpoint (e.g., 403)
4  // error.type:   "AuthError" or "WebSocketError"
5  // error.error:  human-readable message
6});

pusher:member_added / pusher:member_removed

Presence-channel-only. Fires when a user joins or leaves.

member-events.js
javascript
1const channel = pusher.subscribe("presence-chat-room");
2
3channel.bind("pusher:member_added", (member) => {
4  console.log(`${member.info.name} joined`);
5  // member.id  — the user_id from your auth endpoint
6  // member.info — the user_info object from your auth endpoint
7});
8
9channel.bind("pusher:member_removed", (member) => {
10  console.log(`${member.info.name} left`);
11});

pusher:cache_miss

Cache-channel-only. Fires when you subscribe to a cache channel and there is no cached event yet. Use this to trigger an initial data load.

cache-miss.js
javascript
1const channel = pusher.subscribe("cache-live-score");
2
3channel.bind("score-update", (data) => {
4  updateScoreboard(data);
5});
6
7channel.bind("pusher:cache_miss", () => {
8  // No cached value — request the current score from your server
9  fetch("/api/current-score")
10    .then((r) => r.json())
11    .then((data) => updateScoreboard(data));
12});

pusher:subscription_count

Fires when the subscriber count changes on a channel (must be enabled in app settings). On channels with 100+ subscribers, fires at most every 5 seconds.

subscription-count.js
javascript
1channel.bind("pusher:subscription_count", (data) => {
2  viewerCountEl.textContent = `${data.subscription_count} watching`;
3});

pusher:error (connection-level)

Fires on the connection object (not a channel) when a connection-level error occurs. See the Connection guide for error codes.

connection-error.js
javascript
1// Note: bind on pusher.connection, not on a channel
2pusher.connection.bind("error", (err) => {
3  console.error("Connection error:", err.error.data.code);
4});

Triggering Server Events

Your server triggers events through the REST API. This is the primary way to push data to your clients.

Single channel, single event

trigger-single.js
javascript
1await pusher.trigger("orders", "order-shipped", {
2  orderId: "ORD-1234",
3  trackingNumber: "1Z999AA10123456784",
4});

Multiple channels at once

trigger-multi.js
javascript
1// Same event to up to 100 channels in one API call
2await pusher.trigger(
3  ["private-user-1", "private-user-2", "private-user-3"],
4  "notification",
5  { message: "System maintenance in 10 minutes." }
6);

Excluding the sender (echo prevention)

echo-prevention.js
javascript
1// Client sends the action + its socket_id
2// POST /api/messages  { text: "Hello", socket_id: "12345.67890" }
3
4app.post("/api/messages", async (req, res) => {
5  const { text, socket_id } = req.body;
6  await db.messages.create({ text });
7
8  // Trigger to all subscribers except the sender
9  await pusher.trigger("chat-general", "message-created", { text }, {
10    socket_id,
11  });
12
13  res.json({ ok: true });
14});

Batch triggering different events

batch-trigger.js
javascript
1// Up to 10 different events per batch call
2await pusher.triggerBatch([
3  {
4    channel: "private-user-1",
5    name: "order-shipped",
6    data: { orderId: "ORD-001" },
7  },
8  {
9    channel: "private-user-2",
10    name: "order-delivered",
11    data: { orderId: "ORD-002" },
12  },
13  {
14    channel: "admin-alerts",
15    name: "bulk-dispatch-completed",
16    data: { count: 2 },
17  },
18]);

See the API Reference for the raw HTTP endpoints and authentication details.

Client Events

Client events are sent directly from one connected browser to the other subscribers of the same channel, without a round-trip to your server. They must be prefixed with client- and are only available on private and presence channels.

Requirements

  • Client events must be enabled in your app’s Dashboard settings
  • Channel must be private- or presence-
  • Trigger only after pusher:subscription_succeeded fires
  • Rate limit: 10 client events per second per connection
  • The sender does not receive its own client events
  • Payload max 10 KB, same as server events

Typing Indicators Example

typing-indicator.js
javascript
1const channel = pusher.subscribe("private-chat-42");
2
3channel.bind("pusher:subscription_succeeded", () => {
4  let typingTimeout: ReturnType<typeof setTimeout>;
5
6  messageInput.addEventListener("input", () => {
7    channel.trigger("client-typing", { isTyping: true });
8
9    // Clear the "stopped typing" timer and restart it
10    clearTimeout(typingTimeout);
11    typingTimeout = setTimeout(() => {
12      channel.trigger("client-typing", { isTyping: false });
13    }, 1500);
14  });
15});
16
17// Receive typing events from the other party
18channel.bind("client-typing", (data) => {
19  typingIndicator.hidden = !data.isTyping;
20});

Collaborative Cursor Positions

cursor-tracking.js
javascript
1const channel = pusher.subscribe("presence-doc-abc123");
2
3channel.bind("pusher:subscription_succeeded", () => {
4  // Throttle to stay well under the 10/sec rate limit
5  let lastSent = 0;
6
7  document.addEventListener("mousemove", (e) => {
8    const now = Date.now();
9    if (now - lastSent < 100) return; // max 10/sec
10    lastSent = now;
11
12    channel.trigger("client-cursor-moved", {
13      x: Math.round(e.clientX),
14      y: Math.round(e.clientY),
15    });
16  });
17});
18
19// Render other users' cursors
20channel.bind("client-cursor-moved", (data, metadata) => {
21  updateCursor(metadata.user_id, data.x, data.y);
22  // Use metadata.user_id — signed by Soketify, not self-reported
23});

Use metadata.user_id on presence channels

On presence channels, the callback receives a second argument, metadata, with a user_id field set by Soketify from the auth endpoint. This is tamper-proof. If you embed a user ID in the event data payload, any client can fake it, so always use metadata.user_id for identity.

Quick Reference

System EventWhereWhen it fires
pusher:subscription_succeededAll channelsSubscription confirmed
pusher:subscription_errorPrivate, presenceAuth endpoint rejected the request
pusher:member_addedPresence onlyA user joins the channel
pusher:member_removedPresence onlyA user leaves the channel
pusher:cache_missCache channelsSubscribe when no cached event exists
pusher:subscription_countAll channels (if enabled)Subscriber count changes

Next Steps