A Pusher alternative when you only need a REST publish endpoint
If your server-side need is one HTTP POST to fan out an event, most of what you would be buying is client-side. Where that trade works and where it doesn't.
Plenty of teams reach for Pusher and use one feature: a server-side call that pushes an event to connected browsers. Infrai has that as POST /v1/realtime/publish on the same key as your queues, storage and email — so if the server side is all you need, you can skip adding a messaging vendor.
Be clear about what you’d be giving up, though, because Pusher and Ably sell more than the publish endpoint and the rest of it is mostly client-side.
The server side, in full
curl -sS -X POST "https://api.infrai.cc/v1/realtime/publish" \
-H "Authorization: Bearer ${INFRAI_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"channel": "orders:live",
"event": "message.published",
"data": {"type": "order.created", "id": "ord_8821", "total_usd": 42.5}
}'
{
"ok": true,
"data": {
"event_id": "evt_2fVc8nRqLmT4xBzY",
"channel": "orders:live",
"published_at": "2026-09-21T03:07:11Z",
"vendor": "tencent_im"
}
}
Channel creation with POST /v1/realtime/channel/create (types public, private, presence), a client token from POST /v1/realtime/token/issue, presence via GET /v1/realtime/presence/get/{channel}, and a batch publish at POST /v1/realtime/publish/batch. That’s the whole surface — small on purpose.
What you’d actually be comparing
| Capability | Pusher / Ably | Infrai realtime |
|---|---|---|
| Server-side publish over REST | yes | yes |
| Presence channels | yes | yes, type: "presence" |
| Scoped client tokens | yes | yes, closed capability set |
| Mature browser SDK with reconnect + backoff | yes, the main value | no — the vendor client plus your token |
| Message history / replay | yes | not exposed |
| Guaranteed delivery / QoS | Ably, yes | no — connected clients only |
| Webhooks on channel lifecycle | yes | yes, via POST /v1/account/webhooks/register |
| Same credential as queues, email, storage, inference | no | yes |
Rows four to six are the honest reason to buy the specialist, and they are real limitations here rather than framing: there’s no browser SDK, no message history endpoint, and no delivery guarantee. A reconnection strategy that handles a flaky mobile network well is genuinely hard, and Ably’s delivery guarantees are an engineering product rather than a feature bullet — if a dropped message is a bug in your product rather than a stale pixel, that’s what you want and this isn’t a good fit.
Pusher sits in between: less machinery than Ably, a very good client, and a decade of people having already hit your problem.
Where the trade works
Server-driven, ephemeral updates. A progress bar. A live count. An ops dashboard. A “someone else just edited this” banner. Anything where the current value is the only value that matters and a client that missed one update will get the next one.
In those cases the client-side sophistication you’d be paying for is mostly insurance against a problem you don’t have, and the channel-lifecycle events you do want are available as account webhooks:
curl -sS -X POST "https://api.infrai.cc/v1/account/webhooks/register" \
-H "Authorization: Bearer ${INFRAI_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"url": "https://ops.example.com/hooks/realtime",
"events": ["realtime.channel.occupied", "realtime.channel.vacated", "realtime.member.added", "realtime.member.removed"],
"description": "channel lifecycle",
"secret": "a-long-random-string-you-generate"
}'
That’s the “is anyone watching this channel” signal, which is what most teams actually use Pusher’s webhooks for — stop doing expensive work when nobody’s connected.
A publisher you can drop into an existing service
const API = "https://api.infrai.cc";
const KEY = process.env.INFRAI_API_KEY;
if (!KEY) throw new Error("INFRAI_API_KEY is not set");
const headers = { authorization: `Bearer ${KEY}`, "content-type": "application/json" };
/**
* Publish, never throw. A fan-out failure must not fail the business operation
* that triggered it — the durable record is the truth and this is the fast path.
*/
export async function fanout(channel, type, payload) {
try {
const res = await fetch(`${API}/v1/realtime/publish`, {
method: "POST",
headers,
body: JSON.stringify({ channel, event: "message.published", data: { type, ...payload } }),
});
const body = await res.json();
return body.ok ? body.data.event_id : null;
} catch {
return null;
}
}
/** Many updates in one request rather than many requests. */
export async function fanoutBatch(messages) {
const res = await fetch(`${API}/v1/realtime/publish/batch`, {
method: "POST",
headers,
body: JSON.stringify({
messages: messages.map((m) => ({
channel: m.channel,
event: "message.published",
data: { type: m.type, ...m.payload },
})),
}),
});
const body = await res.json();
if (!body.ok) throw new Error(body.error?.code ?? "batch_publish_failed");
return body.data.published;
}
The migration cost, both directions
Coming from Pusher, the server side is a URL and a body change. The client side is the work: you swap their SDK for the fan-out vendor’s client plus a token from your own endpoint, and you write the reconnect handling their SDK gave you.
Going the other way is equally cheap, which is the point worth making about lock-in: the call site is a plain POST with a JSON body, so leaving is a string change rather than a rewrite. There’s no proprietary protocol in your code.
Cost, without a table you’d have to trust
Publishing is billed per call and batch publishing per batch — both live in GET /v1/discovery/realtime.publish and GET /v1/discovery/realtime.publish.batch, read from your own account (verified 2026-09-21). Channel and token management report billing_class: free. What you actually spent is GET /v1/account/usage, and rates on this platform drift downward as vendor contracts improve, so a live read will tend to beat anything published.
The structural difference from a messaging vendor’s pricing is that there’s no connection-count or channel-minute dimension here — you pay for publishes. Whether that’s cheaper depends on your shape: a few thousand connections receiving occasional updates favours this model, while a small number of clients receiving a constant stream favours a connection-priced one. Work it out with your own numbers rather than either vendor’s example.
The other half of the argument is the one a single-purpose vendor can’t make: the queue whose worker publishes, the storage holding the artefact it announces, and the email to whoever was offline are the same key and one invoice. Fan-out is rarely a feature on its own — it’s the last step of something else.