Skip to content

Commit 78a19d1

Browse files
committed
docs(readme): document webhooks v2, on_play_command and now_playing event
Adds a dedicated Webhooks section between HTTP API and Admin UI with the event list, always-available template variables, helper funcs, preset roster, and a Discord example. Also bumps the AutoDJ admin-UI bullet to mention on_play_command and the Admin UI list to mention the new Webhooks page. A new collapsible block at the top of "What's new" summarises the changes (clearly marked as on-main / post-beta.9).
1 parent 5f717b0 commit 78a19d1

1 file changed

Lines changed: 100 additions & 1 deletion

File tree

README.md

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,21 @@ control, and a built-in admin SPA. Deploy anywhere in seconds.
7373

7474
Latency, player UX and an honest viewer count.
7575

76+
<details open>
77+
<summary><strong>Webhooks v2 &amp; track-start hooks</strong> — templated bodies, presets, <code>now_playing</code>, AutoDJ <code>on_play_command</code> <em>(on main, post-beta.9)</em></summary>
78+
79+
- New **`now_playing`** event fires once per track on every AutoDJ mount, alongside the existing `source_connect` / `source_disconnect` / `metadata_update` / `security_lockout` events.
80+
- **Templated webhook bodies** with Go `text/template` syntax. Empty template falls back to the legacy JSON envelope so existing receivers keep working unchanged.
81+
- **Per-webhook HTTP method, Content-Type, custom headers**, and a free-form body template. For `GET` / `HEAD` the rendered body is appended as a query string — that's how the TuneIn AIR `Playing.ashx` preset works from a single template.
82+
- **Always-available variables**: `{{.Event}}`, `{{.Timestamp}}`, `{{.UnixTimestamp}}`, `{{.Date}}`, `{{.Time}}`, `{{.Hostname}}`, `{{.BaseURL}}`, `{{.Version}}`. When the event payload carries a mount, `{{.MountURL}}` and `{{.PlayerURL}}` are derived from `base_url`. Helper funcs: `urlencode`, `json`, `lower`, `upper`.
83+
- **Presets** for Discord, Slack, Mattermost, Microsoft Teams, Telegram, ntfy.sh, Pushover, TuneIn AIR `Playing.ashx`, generic JSON envelope, and webhook.site (for debugging templates).
84+
- **Admin UI**: dedicated `/admin/webhooks` page with add / edit / delete, per-row Test button (fires a sample payload), preset dropdown, click-to-insert placeholder helper.
85+
- **AutoDJ `on_play_command`**: shell hook executed at track-start with `TINYICE_ARTIST` / `TINYICE_TITLE` / `TINYICE_ALBUM` / `TINYICE_FILE` / `TINYICE_MOUNT` env vars, for integrations that prefer a script over an HTTP endpoint.
86+
87+
See [Webhooks](#webhooks) for the full reference.
88+
89+
</details>
90+
7691
<details open>
7792
<summary><strong>Player &amp; UX</strong> — stats overlay, posters, DVR, viewer count, listening time</summary>
7893

@@ -371,16 +386,100 @@ equivalent. See `/api/docs` for the full list.
371386

372387
---
373388

389+
## Webhooks
390+
391+
Outbound HTTP notifications for stream and AutoDJ events. Manage from
392+
**Admin → Webhooks** or via the JSON API at `/api/webhooks` (super-admin
393+
only). URLs are validated against the same SSRF guard as relays —
394+
loopback, private and link-local addresses are rejected.
395+
396+
### Events
397+
398+
| Event | Fired when |
399+
|---|---|
400+
| `now_playing` | A new track starts on an AutoDJ mount. Carries `mount`, `name`, `artist`, `title`, `album`, `file`, `format`, `bitrate`, `duration_seconds`. |
401+
| `source_connect` | An external source (broadcaster) connects to a mount. |
402+
| `source_disconnect` | An external source disconnects from a mount. |
403+
| `metadata_update` | Mount metadata (title / artist / song) changes. |
404+
| `security_lockout` | An IP is locked out for repeated auth failures. |
405+
406+
### Body templates
407+
408+
Webhooks have an optional `body_template` rendered with Go's
409+
`text/template` syntax. Leaving it empty sends the legacy JSON envelope
410+
(`{event, timestamp, hostname, data}`). Filling it in lets you match
411+
whatever shape the receiver wants.
412+
413+
**Always-available variables** (every event, every template):
414+
415+
| Variable | Example |
416+
|---|---|
417+
| `{{.Event}}` | `now_playing` |
418+
| `{{.Timestamp}}` | `2026-05-04T19:30:00Z` |
419+
| `{{.UnixTimestamp}}` | `1809974400` |
420+
| `{{.Date}}` · `{{.Time}}` | `2026-05-04` · `19:30:00` |
421+
| `{{.Hostname}}` · `{{.BaseURL}}` · `{{.Version}}` | from config |
422+
423+
When the payload carries a `mount`, the dispatcher derives `{{.MountURL}}`
424+
(public listen URL) and `{{.PlayerURL}}` (embedded player) from
425+
`base_url`. Use `{{if .MountURL}}…{{end}}` to no-op gracefully when
426+
`base_url` isn't set.
427+
428+
**Helper funcs**: `urlencode`, `json`, `lower`, `upper`. Snake-case payload
429+
keys also expose a CamelCase alias — `{{.DurationSeconds}}` and
430+
`{{.duration_seconds}}` both work.
431+
432+
For `GET` / `HEAD` webhooks the rendered body is appended to the URL as
433+
a query string instead of being sent as a request body — that's how the
434+
TuneIn AIR preset hits `Playing.ashx` from a single template.
435+
436+
### Presets
437+
438+
The editor's **Load preset** dropdown ships templates for: Discord,
439+
Slack, Mattermost, Microsoft Teams (MessageCard), Telegram bot
440+
`sendMessage`, ntfy.sh, Pushover, TuneIn AIR `Playing.ashx`, generic
441+
JSON, and webhook.site (echo for debugging). Loading a preset fills
442+
method, headers and body — the URL field is left alone if you've
443+
already typed one.
444+
445+
### Example: Discord "now playing"
446+
447+
```json
448+
{
449+
"name": "Discord — #now-playing",
450+
"url": "https://discord.com/api/webhooks/<channel-id>/<token>",
451+
"method": "POST",
452+
"events": ["now_playing"],
453+
"body_template": "{\"username\":\"TinyIce\",\"content\":\":musical_note: **{{.Mount}}** — {{.Artist}} – {{.Title}}{{if .MountURL}} — [Listen]({{.MountURL}}){{end}}\"}",
454+
"enabled": true
455+
}
456+
```
457+
458+
### Track-start without HTTP: `on_play_command`
459+
460+
If you'd rather run a local script than an HTTP webhook, AutoDJ exposes
461+
an `on_play_command` per instance. The command runs asynchronously at
462+
each track start with the metadata as env vars (`TINYICE_ARTIST`,
463+
`TINYICE_TITLE`, `TINYICE_ALBUM`, `TINYICE_FILE`, `TINYICE_MOUNT`):
464+
465+
```bash
466+
#!/bin/bash
467+
curl -s "https://air.radiotime.com/Playing.ashx?partnerId=$P&partnerKey=$K&id=$ID&title=${TINYICE_TITLE}&artist=${TINYICE_ARTIST}"
468+
```
469+
470+
---
471+
374472
## Admin UI
375473

376474
- **Dashboard**: live bandwidth (in + out), listeners, streams, health.
377475
- **Streams**: create, edit (password / visibility / enabled / burst), kick source or listeners, remove.
378-
- **AutoDJ**: full CRUD, inline edit, external `song_command` hook.
476+
- **AutoDJ**: full CRUD, inline edit, external `song_command` (next-track selector) and `on_play_command` (track-start notifier) hooks.
379477
- **Studio**: 3-column live control — library browser, playlist editor, transport, visualiser, mount switcher.
380478
- **Go Live**: browser WebRTC broadcasting with device picker, spectrum analyser, level meters + headroom in dB.
381479
- **Transcoders**: MP3 and Opus targets with per-instance Opus application / frame size / complexity / VBR knobs.
382480
- **Relays**: pull streams from upstream Icecast servers with in-stream ICY metadata parsing.
383481
- **Users**: roles (super-admin, admin, DJ), passkey enrolment, OIDC linking, bearer-token management.
482+
- **Webhooks**: outbound HTTP notifications for stream + AutoDJ events; templated bodies, custom headers/method, preset library (Discord, Slack, Teams, Telegram, ntfy, Pushover, TuneIn AIR, …), per-row Test button.
384483
- **Security**: IP ban / whitelist with CIDR, audit log tab with filters.
385484
- **Pending Users**: approve or deny users who signed up via OIDC.
386485
- **Settings**: HTTPS, directory listing, branding, SMTP, auto-update.

0 commit comments

Comments
 (0)