All guides
Grow your audience8 min read

Connect your station to everything else: the API, webhooks and realtime events

Show now playing and recent tracks on your website, post to Discord or socials when a track starts or you go live, sync a music folder, and build a status page — with the Nobex API, webhooks and Server-Sent Events.

Your station already knows what is playing, who is listening and when it went on air. Now that knowledge can leave the dashboard: a REST API for anything your account can see or do, webhooks that call your URL when something happens, and a public realtime feed for your website. All on every plan, and most of it takes an afternoon.

Three ways in, and when to use each

You want to…UseNeeds a key?
Show now playing, recent tracks or the schedule on a websitePublic feeds or the realtime streamNo
React the moment a track starts, the station goes on or off air, or a DJ goes liveWebhooksNo key — a signing secret per endpoint
Read analytics, manage the library, start or stop the station from your own toolsREST API (/v1)Yes — created in Dashboard → Developers

The rule of thumb: anything that runs in a listener’s browser uses the public feeds, because a key in a web page is a key anyone can read. Anything that runs on your own server or in an automation tool can hold a key.

Now playing and recent tracks on your website

The simplest build, and the one most stations want first. Polling the public now-playing feed every 20 seconds is covered step by step in Show what your station is playing, anywhere. Two things are new: a history feed for the last plays, and a realtime stream so the page updates the instant a track changes instead of on the next poll.

Recent tracks — no key
https://streaming-api.nobexpartners.com/public/stations/YOUR-SLUG/history?limit=10

{
  "station": { "id": "moonlightfm", "slug": "moonlightfm", "name": "Moonlight FM" },
  "data": [
    { "title": "Backseat Star", "artist": "The Hold Steady", "album": null, "played_at": "2026-09-08T14:03:10.980Z" },
    { "title": "Cherry Wine", "artist": "Hozier", "album": null, "played_at": "2026-09-08T13:59:02.114Z" }
  ]
}
Live updates — no key, no polling
<p>Now playing: <span id="track">…</span></p>
<script>
  var es = new EventSource(
    "https://streaming-api.nobexpartners.com/public/stations/YOUR-SLUG/events"
  );
  es.addEventListener("now_playing", function (e) {
    var np = JSON.parse(e.data).now_playing;
    document.getElementById("track").textContent =
      np ? (np.artist ? np.artist + " — " : "") + np.title : "Off air";
  });
</script>

The browser reconnects on its own if the connection drops, and every connection starts with the current track, so there is nothing else to wire up.

Keep both

Use the realtime stream for the live page and the history feed for a “recently played” list underneath it. Both are cached and cheap; neither touches your audio stream.

“Now playing” in Discord or Slack

Both chat tools accept incoming webhooks, but each wants its own message format, so a small receiver sits in between: Nobex posts a track.started event to your URL, your code verifies the signature and forwards a formatted line to Discord or Slack. That receiver can be a few lines on any host that runs Node or Python — or a no-code scenario in Zapier, Make or n8n, whose webhook triggers accept the event as-is.

What arrives at your URL
POST /hooks/nobex
Content-Type: application/json
X-Nobex-Event: track.started
X-Nobex-Delivery: 7c1d…
X-Nobex-Signature: t=1757340191,v1=9a4c…

{
  "id": "4f1c1a2e-…",
  "type": "track.started",
  "created_at": "2026-09-08T14:03:11.204Z",
  "api_version": "2026-09-08",
  "data": {
    "station": { "id": "moonlightfm", "slug": "moonlightfm", "name": "Moonlight FM" },
    "track": { "title": "Backseat Star", "artist": "The Hold Steady", "album": null,
               "isrc": null, "source": "autodj", "started_at": "2026-09-08T14:03:10.980Z" },
    "listeners": 42
  }
}
  1. 1In Discord, open the channel → Integrations → Webhooks → New webhook and copy its URL. (Slack: create an app with an Incoming Webhook.)
  2. 2Stand up a receiver that accepts the Nobex POST, checks X-Nobex-Signature — the developer docs have copy-paste Node and Python — and forwards { "content": "▶ Artist — Title" } to the Discord URL.
  3. 3In Dashboard → Developers → Webhooks, add your receiver’s URL, tick track.started, and copy the signing secret it shows you once.
  4. 4Press Send test event. The dashboard shows the response code right there; the first real track change does the rest.

Reply first, work after

We wait ten seconds for a 2xx. Send the response, then talk to Discord — a slow third party should never make your delivery look failed. If it does fail, it is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours, and every attempt is listed under the endpoint in the dashboard.

“We’re live” on your socials, with no code

The live.started event fires when a real person takes over from AutoDJ, and station.online when the station comes on air. Point either at a Zapier, Make, n8n or IFTTT webhook trigger and the rest is their catalogue: post to a Facebook page, send a Telegram message, add a row to a sheet, email a list. The event body carries the station name and the time, which is usually all the message needs.

  • Zapier — trigger *Webhooks by Zapier → Catch Hook*; use the URL it gives you as the endpoint.
  • Make — *Custom webhook* module; run once to let it learn the payload shape.
  • n8n — the *Webhook* node, production URL, method POST.
  • IFTTT — the *Webhooks* service; note it wants values named value1..3, so a small step in between may be needed.

These tools cannot check the signature

No-code triggers accept any POST to their URL. That is fine for “post that we are live”; keep the URL private and do not use one to drive anything that costs money. For those, put a verifying receiver in front.

Sync a music folder to your library

Stations that produce shows elsewhere often have a folder that fills up — new jingles, a weekly programme, a batch from a label. With a write-scope key, a script can watch that folder and upload what is new. Uploads go straight to storage in two calls: ask for an upload URL, PUT the file to it, then confirm.

Python — upload one file
import os, mimetypes, requests

API = "https://streaming-api.nobexpartners.com"
H = {"Authorization": "Bearer " + os.environ["NOBEX_API_KEY"]}
STATION = "moonlightfm"

def upload(path):
    size = os.path.getsize(path)
    ctype = mimetypes.guess_type(path)[0] or "audio/mpeg"
    grant = requests.post(f"{API}/v1/stations/{STATION}/tracks/upload-url", headers=H,
                          json={"filename": os.path.basename(path),
                                "content_type": ctype, "size_bytes": size}).json()["data"]
    # grant = { track_id, upload_url, content_type, max_bytes, expires_in_seconds }
    with open(path, "rb") as f:
        requests.put(grant["upload_url"], data=f,
                     headers={"Content-Type": grant["content_type"]}).raise_for_status()
    done = requests.post(f"{API}/v1/stations/{STATION}/tracks/upload-complete", headers=H,
                         json={"track_id": grant["track_id"],
                               "filename": os.path.basename(path)}).json()
    print("added", done)

The upload-url answer carries track_id, upload_url, content_type, max_bytes and expires_in_seconds. PUT the bytes to upload_url, then send track_id and filename (plus optional title, artist, album, isrc, duration_seconds) to upload-complete. A file already in the library comes back with duplicate: true instead of being added twice.

Pair it with GET /v1/stations/{station}/tracks?q= to skip files already in the library, and DELETE …/tracks/{id} to retire old ones. Files up to 500 MB; the same formats the dashboard accepts. Tag your files first — title, artist and ISRC come from the tags, and they are what royalty reporting reads.

A status page for your team

A single HTML page that opens the realtime stream and shows on-air state, the current track and the listener count for each of your stations — the thing a volunteer checks before messaging the group chat. Nothing to host beyond a static file, because the stream is public and the page needs no key.

  • On connect every stream sends now_playing and listeners, so the page is complete immediately.
  • station.online / station.offline and live.started / live.ended arrive as they happen — flip a badge, play a sound, whatever helps.
  • For history and analytics on the same page, add a tiny server that holds a read key and proxies /v1/stations/{station}/stats?range=24h — never put the key in the page itself.

Start, stop and push titles from your own tools

With a write key, POST /v1/stations/{station}/start and /stop do exactly what the dashboard buttons do, with the same checks. POST …/now-playing with { "title", "artist" } sets what listeners see during a live show driven by playout software that does not send metadata itself; the next track change or the next push replaces it. POST …/play-now with a track_id plays a library track next, ahead of the rotation — handy for a “request line” form on your site that a small server relays.

The compact reference

ResourceEndpoints (under `https://streaming-api.nobexpartners.com`)
Account & stationsGET /v1/me · GET /v1/stations · GET /v1/stations/{station}
Now playing & historyGET|POST …/now-playing · GET …/history?limit&before
ListenersGET …/listeners · GET …/listeners/live
Stats & analyticsGET …/stats?range=1h|24h|7d|30d · GET …/analytics?start&end · GET …/analytics/export.csv
TracksGET …/tracks · GET|DELETE …/tracks/{id} · POST …/tracks/upload-url · POST …/tracks/upload-complete
Playlists & scheduleGET …/playlists · GET …/playlists/{id} · GET …/schedule
ControlPOST …/start · POST …/stop · POST …/play-now
Webhook eventstrack.started · station.online · station.offline · live.started · live.ended · test.ping
Public, no key/public/stations/{slug}/now-playing · …/history · …/events (SSE) · …/schedule
  • KeysAuthorization: Bearer nbx_live_… or X-API-Key; read or read+write scope; account-wide; revoke any time in Dashboard → Developers.
  • Limits — 600 requests a minute per key, with X-RateLimit-* headers on every response.
  • Errors — always { "error": { "code", "message" } }.
  • Spec`/v1/openapi.json` imports into Postman, Insomnia and any code generator.

Is the API an extra cost?

No. The REST API, webhooks and the realtime feed are included on every plan. Create a key in Dashboard → Developers and you are set.

Can I use webhooks without writing code?

Yes. Zapier, Make, n8n and IFTTT all provide a webhook trigger URL; add it as your endpoint in the dashboard and pick the events. Anything with a verified signature or a purchase behind it should go through a small receiver of your own instead.

How quickly does a webhook arrive after a track starts?

Deliveries are picked up within seconds of the event. If your endpoint is down, the same delivery is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours, and every attempt is recorded under the endpoint in the dashboard.

Is there a listener-count alert?

Not yet — a listeners.threshold event is planned. Today you can read the current count from the realtime stream or from GET /v1/stations/{station}/listeners on whatever schedule you like.

Which stations can the key see?

Every station on the account that created it. Ownership is checked on each call; a station you do not own answers 404.

Keep reading

Ready to put this into practice?

Start free and be on air in under five minutes — no software to install.

Start streaming free