Skip to main content

Realtime Public Feed

Category: Backend & APIs

Get this pack →

This page is generated from the Air Pipe marketplace. Browse it live to install into your organization.

The simplest way to add realtime to a website: a public WebSocket feed your visitors subscribe to with no login, no token, no API key. Your backend publishes; every open browser gets the update instantly. It's the Ably/Pusher "public channel" — for live scores, prices, status pages, countdowns, announcements, and activity tickers.

Use public feeds for non-sensitive data only. For per-user or private channels (auth, presence, history), use the Realtime WebSocket Channels pack, which adds the full auth ladder.


Why this is safe to open

public: true lets a browser connect with no credential — but the per-organisation realtime limits (publish rate, subscriptions per connection, distinct topics, retained messages) still apply, so an open feed can't be used to exhaust a shared node. Open access, bounded blast radius.

What's included

FilePurpose
feed.ymlA public feed interface (subscribe + receive, no auth) and a backend publish route.

Endpoints

InterfaceConnect / callAuth
feedwss://<host>/ws/<ORG_UUID>/production/feednone (public)
publishPOST https://<host>/publishyour backend (protect it)

Quick start

Browser — subscribe + receive (no auth):

<script>
const ws = new WebSocket("wss://your-airpipe-host/ws/<ORG_UUID>/production/feed");
ws.onopen = () => ws.send(JSON.stringify({ ap_subscribe: "updates" }));
ws.onmessage = (e) => {
const m = JSON.parse(e.data);
if (m.ap_channel === "updates") console.log("update:", m.data);
};
</script>

Your backend — publish to everyone:

curl -sX POST https://your-airpipe-host/publish -H 'content-type: application/json' -d '{
"channels": ["updates"],
"data": { "message": "Store opens in 5 minutes!" }
}'
# → every browser subscribed to "updates" receives { "ap_channel":"updates", "data":{…} }

That's the whole thing — no keys in the page, no broker to run.

Control frames (client → server)

FrameEffect
{"ap_subscribe": "updates"} / {"ap_subscribe": ["a","b"]}Join channel(s); wildcards room:+, room:# work too
{"ap_unsubscribe": "updates"}Leave channel(s)

Pushes arrive as {"ap_channel":"updates","data":{…}}.

Customisation

  • Protect /publish: add an is_valid_jwt / validate_key assert so only your backend can broadcast (the feed stays public to read).
  • Publish from any pipeline: add a ws_publish action to an existing HTTP/DB/scheduled pipeline so, e.g., a new DB row also fans out to the feed.
  • Go private / add presence + history: move to the Realtime WebSocket Channels pack — same shape, plus open → Air Pipe-issued → shared-secret → JWKS auth, presence and history-rewind.

Notes

  • Public feeds are for non-sensitive data — anyone can subscribe. Put anything private behind the authenticated pack.
  • Cross-node fan-out uses your private node mesh — no Redis/NATS/broker to operate.
  • Each delivered message counts as one request against your plan.
  • Only production and staging environments are accepted in the URL.

Configuration

feed.yml

name: RealtimePublicFeed
description: >
The simplest realtime — a PUBLIC WebSocket feed your website visitors subscribe to with NO auth at
all. Live scores, status, prices, announcements, activity. Your backend publishes; every open
browser gets it instantly. The Ably/Pusher "public channel", in one small config, no keys in the
browser and no message broker to run.

docs: true

# ── How it works ─────────────────────────────────────────────────────────────────
# 1. A visitor's browser opens a WebSocket to:
# wss://your-airpipe-host/ws/<ORG_UUID>/production/feed
# and subscribes with a JSON frame (NO token, NO API key):
# {"ap_subscribe": "updates"}
# then receives every push as:
# {"ap_channel": "updates", "data": { ... }}
#
# 2. Your backend publishes to the channel by POSTing to /publish. Every subscriber (on any node)
# gets it. `public: true` is what allows the credential-less browser connection — use it ONLY for
# NON-SENSITIVE data. Per-org limits (publish rate, subscriptions, topics) still bound it, so an
# open feed can't exhaust the node.

interfaces:

# The public feed. `public: true` = a browser may connect with no credential.
feed:
ws: "on" # realtime — no `output` needed
# output: http # optional: also serve this interface over HTTP (dual interface)
public: true
summary: Public realtime feed (no auth)
description: Connect with no credential, subscribe to a channel, and receive live updates.
tags: [websocket, realtime, public, pubsub]
actions:
- name: Ping
input: a|body|

# Backend publish surface. YOUR server POSTs here to push to every subscriber. The feed is public
# to READ, but you should protect THIS route (add an is_valid_jwt / validate_key assert) so only
# your backend can broadcast. Any pipeline can also push with a `ws_publish` action.
publish:
output: http
method: POST
summary: Publish to the public feed (backend)
description: Fan a payload out to everyone subscribed to the channel(s).
tags: [websocket, realtime, push]
request_example:
channels: ["updates"]
data:
message: "Store opens in 5 minutes!"
actions:
- name: Broadcast
ws_publish:
channels: a|body::channels|
data: a|body::data|