Channels
Channels
Real-time pub/sub backed by Cloudflare Durable Objects. Each channel is a persistent WebSocket hub — publish from your API route, subscribe from the browser via WebSocket or SSE. Channels maintain presence state and message history automatically.
Architecture: Each unique
channel name maps to a dedicated Durable Object. The DO holds connections, fans out messages, tracks presence, and keeps a sliding history window. There's no separate "create channel" step — channels are created on first access.Publish Event
POST
/infra/channels/{channel}/publish
Publish a single event.
curl
curl -X POST https://api.saasignal.saastemly.com/infra/channels/{channel}/publish \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"event":"metric.updated","data":{"key":"value"}}'
json — 202 Accepted
{ "channel": "org:acme", "event_id": "evt_01JNXS...", "delivered": 3 }
typescript
await ss.infra.channels.publish('org:acme', {
event: 'metric.updated',
data: { mrr: 42840, delta: +1200 }
})
- Open Dashboard and select your project
- Go to Channels in the sidebar
- Enter a channel name, event name, and JSON payload
- Click Publish
The dashboard also shows a live subscriber count and message log.
Path param
Type
Description
channel requiredstring
Channel name (1–128 chars). Alphanumeric, hyphens, underscores, dots, colons,
@.1–128 charspattern
^[\w.:@-]+$Body field
Type
Description
event requiredstring
Event name (e.g.
metric.updated)data requiredany JSON
Event payload
idstring
Custom event ID for deduplication
user_idstring
Sender user ID (included in presence)
Error responses
401 Unauthorized
402 Insufficient tokens
429 Rate limited
Batch Publish
POST
/infra/channels/publish
Publish events to one or more channels (max 50 messages per request).
curl
curl -X POST https://api.saasignal.saastemly.com/infra/channels/publish \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"messages":[]}'
json — 202 Accepted
[
{ "channel": "org:acme", "event_id": "evt_01JNXS...", "delivered": 3 },
{ "channel": "org:beta", "event_id": "evt_01JNXT...", "delivered": 1 }
]
typescript
await ss.infra.channels.batchPublish([
{ channel: 'org:acme', event: 'metric.updated', data: { mrr: 42840 } },
{ channel: 'org:beta', event: 'alert', data: { level: 'warn' } },
])
- Batch publish is an API-only feature
- Use the REST API or TypeScript SDK for bulk publishing
You can publish to individual channels in Dashboard → Channels
Body field
Type
Description
messages requiredarray
Array of messages (max 50). Each:
{ channel, event, data, id?, user_id? }.1–50 items
Error responses
401 Unauthorized
402 Insufficient tokens
429 Rate limited
Subscribe to Channel
GET
/infra/channels/{channel}/subscribe
Subscribe to a channel. Send Upgrade: websocket to get a WebSocket connection; otherwise you get an SSE stream (text/event-stream).
javascript
const ws = new WebSocket(
'wss://api.saasignal.saastemly.com/infra/channels/org:acme/subscribe',
['Bearer', 'sk_live_...']
)
ws.onmessage = (e) => {
const { id, event, data } = JSON.parse(e.data)
console.log(event, data)
}
javascript
// SSE — proxy through your own API route for auth
const es = new EventSource('/api/subscribe?channel=org:acme')
es.addEventListener('metric.updated', (e) => {
const data = JSON.parse(e.data)
setMRR(data.mrr)
})
typescript
// Browser SDK handles auth via your /api/saasignal proxy
const channel = ss.infra.channels.subscribe('org:acme')
channel.on('metric.updated', (data) => setMRR(data.mrr))
channel.on('user.joined', (data) => addUser(data))
// Cleanup
return () => channel.close()
- Open Dashboard and select your project
- Go to Channels and select a channel
- The channel monitor shows live messages as they arrive
You can also view presence (who's connected) and full message history.
Resume on reconnect: Store the last event ID from
e.lastEventId and pass it as ?last_event_id= on reconnect. SaaSignal replays missed messages from the channel history.Path param
Type
Description
channel requiredstring
Channel name (1–128 chars)
1–128 charspattern
^[\w.:@-]+$Query param
Type
Description
last_event_idstring
Resume from this event ID. Replays missed messages (max 128 chars).
max 128 charspattern
^[\w-]+$Error responses
401 Unauthorized
402 Insufficient tokens
429 Rate limited
Get Presence
GET
/infra/channels/{channel}/presence
Get currently connected clients.
curl
curl https://api.saasignal.saastemly.com/infra/channels/{channel}/presence \
-H "Authorization: Bearer sk_live_..."
json — 200 OK
{
"channel": "org:acme",
"count": 3,
"users": [
{ "user_id": "u_123", "joined_at": "2026-03-04T12:00:00Z" },
{ "user_id": "u_456", "joined_at": "2026-03-04T12:01:30Z" }
]
}
typescript
const presence = await ss.infra.channels.presence('org:acme')
// presence.count, presence.users
- Open Dashboard and select your project
- Go to Channels and select a channel
- The Presence tab shows who's currently connected
Path param
Type
Description
channel requiredstring
Channel name (1–128 chars)
1–128 charspattern
^[\w.:@-]+$Error responses
401 Unauthorized
402 Insufficient tokens
429 Rate limited
Message History
GET
/infra/channels/{channel}/history
Retrieve message history for a channel. Use the before parameter for backward pagination.
curl
curl https://api.saasignal.saastemly.com/infra/channels/{channel}/history \
-H "Authorization: Bearer sk_live_..."
json — 200 OK
{ "status": "ok" }
typescript
const history = await ss.infra.channels.history('org:acme', { limit: 20 })
// history.messages
- Open Dashboard and select your project
- Go to Channels and select a channel
- The History tab shows recent messages
Path param
Type
Description
channel requiredstring
Channel name (1–128 chars)
1–128 charspattern
^[\w.:@-]+$Query param
Type
Description
limitinteger
Max messages to return. Range 1–1000, default
50.range ≥1 .. ≤1000default
50beforestring
Return messages before this event ID (for pagination).
max 128 charspattern
^[\w-]+$Error responses
401 Unauthorized
402 Insufficient tokens
429 Rate limited