Dashboard
DocsLive Updates Messages and Actions Reference | BotSubscription

Messages & Actions

This page covers the shared live-updates protocol once your client is connected: naming conventions, the envelope on named broadcast events, the actions you send, the system messages you receive, and the error codes the gateway emits.

If you are integrating a Telegram subscription bot or Discord subscription bot client, expect to spend most of your time on this page — everything except the handshake and per-channel payloads lives here.


Case conventions

The live-updates surface follows one consistent set of naming rules, aligned with the rest of the BotSubscription platform. The shape is simple: JSON object keys are snake_case. Wire identifiers — channels and broadcast event names — are kebab-case, mirroring REST URL path segments and outbound webhook event types so a client that already speaks one of those surfaces sees the same conventions here.

Wire elementConventionExample
Payload field names (every direction)snake_caseconnection_id, payment_request_id, event_id, update_type
Handshake auth fieldsnake_caseauth.project_id
Channel values (in subscribe/unsubscribe + the channel field)kebab-case"payment-requests", "targets", "payment-methods"
Broadcast event names (the string passed to socket.on(...))dot-separated kebab — identical to outbound webhook event typespayment-request.updated, target.added, payment-method.added

System messages cover lifecycle and acknowledgements (ready, subscribed, error, pong, …) and arrive via socket.on("message", handler). Broadcast events carry channel payloads and arrive as named Socket.IO events under their dot-separated names.


Broadcast event envelope

Every named Socket.IO broadcast event documented in the channel pages carries two extra fields alongside its channel-specific body:

FieldTypeUse
event_idUUID v4 (string)Unique per server emission. If your client briefly disconnects mid-broadcast and the same emission is redelivered, it carries the same event_id — a usable client-side dedup key. Not stable across true reconnects: a fresh emission after reconnect gets a new event_id.
emitted_atinteger (ms since epoch, UTC)Server wall-clock at emit. Suitable for "approximately when" displays. Not monotonic across nodes — clocks may drift, so don't rely on it for causal ordering.

System messages (the message event) do not carry these fields — they apply to broadcasts only.

For applying state correctly, use the natural keys in each payload (payment_request_id, target.target_id, payment_method_id) plus the payload's own updated_at / created_at for last-write-wins. Treat each broadcast payload as a full snapshot, not a diff.


Client actions

All client payloads are JSON objects with an action field. Send them with socket.send(payload) or the equivalent socket.emit("message", payload).

subscribe

Start receiving updates for a channel.

FieldRequiredNotes
actionyes"subscribe"
channelyesOne of payment-requests, targets, payment-methods.
payment_request_id / provider_payment_idconditionalOnly for payment-requests. Exactly one must be present.

Example — subscribe to a specific payment request:

{
  "action": "subscribe",
  "channel": "payment-requests",
  "payment_request_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
}

Example — subscribe to all target events for the project:

{ "action": "subscribe", "channel": "targets" }

The server responds via the message event with either a subscribed confirmation or an error.

unsubscribe

Same shape as subscribe, with action: "unsubscribe". The response is unsubscribed on success.

ping

Optional application-level keep-alive. Use it when your client may stay otherwise quiet longer than the 9-minute idle timeout.

{ "action": "ping", "timestamp": 1700000000000 }

timestamp is optional (a client-supplied epoch in ms). The server replies with pong and echoes the value as received_timestamp.


System messages

Delivered via socket.on("message", handler) in snake_case.

ready

Sent once, immediately after the handshake succeeds.

{
  "event": "ready",
  "connection_id": "uuid",
  "channels": ["payment-requests", "targets", "payment-methods"]
}

Use it as the signal to send your initial subscribe actions.

subscribed / unsubscribed

Acknowledges a subscribe / unsubscribe action.

{
  "event": "subscribed",
  "channel": "payment-requests",
  "project_id": "uuid",
  "payment_request_id": "uuid | null",
  "provider_payment_id": "string | null"
}

The identifier fields echo the ones you sent. They are null for fields that don't apply to the channel.

pong

Reply to your ping action.

{
  "event": "pong",
  "timestamp": 1700000100000,
  "received_timestamp": 1700000000000
}

timestamp is the server time at reply. received_timestamp echoes the value you sent (null if you didn't include one).

error

Sent when the gateway can't process a frame.

{
  "event": "error",
  "code": "invalid_payload",
  "message": "Human-readable description",
  "meta": {
    "channel": "payment-requests",
    "action": "subscribe"
  }
}
  • code — a machine-readable string from the error codes table below.
  • message — a human-readable description suitable for logs.
  • meta.channel / meta.action — populated whenever the error was raised in a channel-aware code path: any subscribe/unsubscribe error, plus unsupported_channel and unsupported_action. They let you route the error back to the specific in-flight request that triggered it when multiple actions are pending concurrently. Errors raised before channel resolution (invalid_payload) do not carry these fields.
  • meta.errors — for invalid_payload, an array of field-level validation issues.

Error codes

CodeWhen it fires
invalid_payloadThe frame is not valid JSON, or it failed schema validation. meta.errors carries field-level details.
unsupported_channelThe channel value is not one the gateway knows.
unsupported_actionThe action value is not one the gateway knows.
forbiddenYou are not authorized to subscribe to the requested resource (for example, a payment request belonging to another project).
subscription_failedThe channel's subscribe handler returned an error.
unsubscribe_failedThe channel's unsubscribe handler returned an error.

When several subscribe / unsubscribe actions are in flight on the same socket, use meta.channel and meta.action to correlate each error with the action that produced it.


Reconnection

Subscriptions are session-local — the gateway does not persist them. When Socket.IO reconnects:

  1. Wait for the fresh ready message.
  2. Resubscribe to every channel / identifier your UI still cares about.
  3. event_id is not stable across the reconnect, so don't rely on it for cross-session dedup.
  4. Apply incoming broadcasts as full-snapshot overwrites keyed by the entity ID. Use payload timestamps (updated_at, created_at) to keep state monotonic if late frames arrive after a fresher one.
  5. When a subscription.closed event arrives on the payment-requests channel, clean up the local subscription instead of resubscribing — the entity has reached a terminal state.

Next steps

Last updated: