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:
| Type | Direction | Prefix |
|---|---|---|
| Server events | Your server → subscribers | Any name (your choice) |
| System events | Soketify → your client | pusher: |
| Client events | Client → other subscribers | client- |
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
// 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 sideNaming 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
| Limit | Value |
|---|---|
| Event payload size | 10 KB |
| Event name length | 200 characters |
| Channels per trigger call | 100 |
| Client event rate limit | 10 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.
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.
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.
1pusher.bind_global((eventName, data) => {
2 console.log(`[${eventName}]`, data);
3});
4. Unbinding
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.
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.
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.
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.
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.
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.
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
1await pusher.trigger("orders", "order-shipped", {
2 orderId: "ORD-1234",
3 trackingNumber: "1Z999AA10123456784",
4});
Multiple channels at once
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)
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
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-orpresence- - Trigger only after
pusher:subscription_succeededfires - 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
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
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 Event | Where | When it fires |
|---|---|---|
pusher:subscription_succeeded | All channels | Subscription confirmed |
pusher:subscription_error | Private, presence | Auth endpoint rejected the request |
pusher:member_added | Presence only | A user joins the channel |
pusher:member_removed | Presence only | A user leaves the channel |
pusher:cache_miss | Cache channels | Subscribe when no cached event exists |
pusher:subscription_count | All channels (if enabled) | Subscriber count changes |
Next Steps
- Set up authentication to enable private and presence channels
- Configure webhooks to receive server-side callbacks on channel and member events
- Review all channel types to choose the right one for your use case
- API Reference for triggering events via the REST API