Meteorack Realtime exposes public channel aliases, not raw Phoenix topics. Your client joins names like project-events; the platform maps them to the internal transport topics server-side.
Public channel aliases
Current project-scoped aliases:
project-eventsdeveloper-facing event stream; publishable;ephemeralstorage-auditsubscribe-only storage audit stream;resumablestorage-usagesubscribe-only storage usage stream;ephemeralstorage-objectssubscribe-only storage object change stream;ephemeral
Only project-events is currently publishable from the public API.
1. Mint a subscription token
Call the public token route with an API key that has cloud.realtime:subscribe.
If your key is channel-restricted, request the exact aliases you want with the channels query parameter. That keeps the scope check unambiguous.
const response = await fetch(
`${apiBase}/api/v1/cloud/projects/${projectId}/realtime/token?channels=project-events,storage-audit`,
{
headers: {
Authorization: `Bearer ${apiKey}`,
},
},
);
if (!response.ok) {
throw new Error(`Token request failed with ${response.status}`);
}
const { data } = await response.json();Successful responses look like this:
{
"data": {
"token": "eyJhdWQiOiJtZXRlb3JhY2stcHJvamVjdC1yZWFsdGltZSIsLi4u",
"ws_url": "wss://realtime.meteorack.com/socket",
"engine": "phoenix",
"expires_at": "2026-04-22T21:10:00.000Z",
"channels": [
{
"name": "project-events",
"deliveryClass": "ephemeral",
"publishable": true
},
{
"name": "storage-audit",
"deliveryClass": "resumable",
"publishable": false
}
]
}
}The token payload does not expose internal project:<id> topics.
2. Connect and join a public alias
Use the Phoenix JavaScript client and join the alias directly.
import { Socket } from "phoenix";
const socket = new Socket(data.ws_url, {
params: { token: data.token },
});
socket.connect();
const projectEvents = socket.channel("project-events");
projectEvents.on("inventory.updated", (payload) => {
console.log("inventory update", payload);
});
await projectEvents.join();storage-audit, storage-usage, and storage-objects join the same way. The alias stays stable even if the internal transport changes later.
3. Publish a developer-facing event
Publishing uses the public control-plane route and requires a key with cloud.realtime:publish. Public browser keys are subscribe-only; use a secret key for publish calls.
const eventId = crypto.randomUUID();
const response = await fetch(
`${apiBase}/api/v1/cloud/projects/${projectId}/realtime/publish`,
{
method: "POST",
headers: {
Authorization: `Bearer ${secretApiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": eventId,
},
body: JSON.stringify({
channel: "project-events",
event: "inventory.updated",
data: {
sku: "sku_1",
available: 12,
},
}),
},
);
const result = await response.json();
if (
response.headers.get("Idempotency-Key") !== eventId ||
result.data?.event_id !== eventId
) {
throw new Error("Realtime publish identity mismatch");
}Reuse the same eventId whenever you retry this logical publish after a timeout or other ambiguous result. Only project-events is currently accepted by the public publish route. Subscribe-only aliases return a validation error if you try to publish to them.
Failure semantics
Two failure classes matter most in practice:
- Token issuance can return:
402when the next connection would exceed quota or a spending cap429when the live connection gate is already at its configured ceiling
- Publish can return:
429withpublish_rate_limitedwhen the runtime throttle is active on that channel
In practice this means:
- fetch a new token shortly before connecting
- handle token
402and429separately from general auth failures - back off on publish
429instead of retrying immediately