Skip to content

Webhooks and API

Connecting your own systems to FYN Radio. Written for the developer doing it.

Overview

There are two ways in, both under Integrations on the dashboard, on every plan, and most integrations use both.

  • The API. Your systems ask, we answer: stations, what is playing, listener figures, invoices and payments, as JSON, and with a key made to, starts, stops and restarts stations. Most keys should only read.
  • Webhooks. We tell your systems when something happens: a DJ goes live, a track changes, an invoice is issued. A signed POST to an address of yours, retried if it is down.

The API is at https://app.fynradio.com/api/v1. Everything here is also an OpenAPI 3.1 document, for Postman, Insomnia or generating a client. The account holder can try every request on their own data from API reference on the dashboard, without a key.

Quick start

1. Ask the account holder for an API key, or make one under Integrations, API keys. Leave Can start, stop and restart stations off unless you need it. The key is shown once. Keep it in an environment variable or a secrets store on your server, never in a web page, an app or a repository: anything a browser loads, anyone can read.

2. Ask what is playing:

curl https://app.fynradio.com/api/v1/stations/kayufm/now-playing \
  -H "Authorization: Bearer $FYN_API_KEY"

3. Add a webhook pointing at your system, choose its events, and press Send test. Then check the signature on what arrives, below.

Now playing on a website

With no code at all: every station's overview has an embed code for a player that shows what is playing. Use that if it is all you need.

For your own design, ask from your server, not the browser, since the browser would need the key. Cache the answer for ten seconds and let your page ask your server:

Node.js
// On your server. The station reports every ten seconds, so asking more
// often than that gets the same answer.
let cached = null;
let fetchedAt = 0;

app.get('/now-playing', async (req, res) => {
  if (Date.now() - fetchedAt > 10_000) {
    const response = await fetch('https://app.fynradio.com/api/v1/stations/kayufm/now-playing', {
      headers: { Authorization: `Bearer ${process.env.FYN_API_KEY}` },
    });
    cached = (await response.json()).data;
    fetchedAt = Date.now();
  }

  res.json({ title: cached.now_playing, listeners: cached.listeners });
});

To update the page the moment a track changes, subscribe to track.changed and keep the latest title yourself. It comes about once a song. Read now-playing once when your service starts, so you have a title before the first webhook.

Going live and off air

Choose Going live and off air on the webhook: live.started and live.ended when an encoder connects and disconnects, and default_audio.started and default_audio.ended around the station having nothing to play. Studios use them for an on-air light, for posting that a show has started, and for waking someone when default audio comes on, which means no DJ and an empty playlist.

To know where things stand when your service starts, read state and live from /stations/{slug}.

Keeping accounting in step

Choose Billing: invoice.issued, invoice.paid, invoice.voided, payment.succeeded, payment.failed and balance.low. Key invoices by their number. Money is whole US cents, and a payment also says what was paid in the currency it was paid in, KES for M-Pesa.

Webhooks can be missed: your system may be down for longer than we retry. Once a day, read /invoices and /payments and fill in anything you do not have. Both are newest first, so stop once you reach ones you already know.

Listener figures

/stations/{slug}/stats?days=30 has a row a day: peak and average listeners, listener minutes, minutes on air and bytes sent, and listening by country and by player. They are the figures on the station's Reports page. Days are UTC, and today's row is still filling in, so read yesterday's after midnight UTC.

A day with peak_listeners null is one we were not hearing from the station's server. Treat it as unknown, not as nobody listening.

Starting and stopping

POST /stations/{slug}/stop, /start and /restart do what the buttons on the station's page do, with the same rules, for a key made to control stations. A read-only key gets 403. Studios use them to stop an AutoDJ station overnight, or to restart from their own control screen.

  • Every one disconnects everyone listening. A stopped station stays off air until started.
  • It takes seconds, not milliseconds. The answer is 202 with the state on its way, such as stopping. Read /stations/{slug} or wait for its webhook to see it done.
  • Asking twice is safe. Stopping a stopped station answers 200 with changed false.
  • There are limits. Six stops and starts in ten minutes, and some minutes between restarts. Past them, 429 with Retry-After. A suspended, cancelled or closed station cannot be started, and only one on air can be restarted: 409.
  • It is on the record. The station's history and its webhooks say by api.

Receiving webhooks

Each is a POST with a JSON body and these headers: FYN-Event, the event; FYN-Delivery, this delivery's own id; and FYN-Signature, below. The body:

Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "track.changed",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        },
        "title": "Sauti Sol - Suzanna",
        "previous_title": "Nyashinski - Mungu Pekee",
        "live": false,
        "default_audio": false,
        "listeners": 42
    }
}
  • Answer first, work after. Reply with any 2xx within 10 seconds, as soon as the signature checks out, and do the work afterwards. Anything else, a redirect included, is a failure.
  • Expect repeats. A retry or a resend sends an event again with the same id. Keep the ids you have handled and skip ones you have seen.
  • Expect them out of order. Order by created_at, not by arrival.
  • Tests say so. test is true on anything sent by Send test, which carries sample data. Do not act on one as if it were real.
  • Ignore what you do not know. Fields and events are added over time. Code that refuses an unknown one will break.

A failed delivery is tried again after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours. A track change is tried twice more within about a minute, then dropped, since an old title is no use. If every delivery fails for three days we switch the webhook off and email the account holder. Every delivery, with what we sent and what you answered, is on the webhook's page, where any can be sent again.

The address has to be https and reachable from the internet. To develop against your own machine, use a tunnel such as ngrok or Cloudflare Tunnel and point a second webhook at it.

Checking signatures

Anyone can post to your address, so check every request. The header reads t=1760433124,v1=5257a8…. v1 is the HMAC-SHA256 of t, a full stop and the raw body, keyed with the webhook's signing secret from its page. Work it out yourself, compare in constant time, and refuse a t more than five minutes old. Use the body exactly as it arrived: parsing and re-encoding the JSON changes it.

PHP

$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_FYN_SIGNATURE'] ?? ''), $sig);

$expected = hash_hmac('sha256', ($sig['t'] ?? '').'.'.$body, getenv('FYN_WEBHOOK_SECRET'));

if (! hash_equals($expected, $sig['v1'] ?? '') || abs(time() - (int) ($sig['t'] ?? 0)) > 300) {
    http_response_code(400);
    exit;
}

$event = json_decode($body, true);

Node.js, with Express

import crypto from 'node:crypto';

app.post('/fyn-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = Object.fromEntries((req.get('FYN-Signature') ?? '').split(',').map((part) => part.split('=')));
  const expected = crypto.createHmac('sha256', process.env.FYN_WEBHOOK_SECRET)
    .update(`${sig.t}.${req.body}`)
    .digest('hex');

  const ok = sig.v1?.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(sig.v1), Buffer.from(expected));

  if (!ok || Math.abs(Date.now() / 1000 - Number(sig.t)) > 300) {
    return res.sendStatus(400);
  }

  const event = JSON.parse(req.body);
  res.sendStatus(200);
});

Python, with Flask

import hashlib, hmac, os, time
from flask import abort, request

@app.post("/fyn-webhook")
def fyn_webhook():
    body = request.get_data()
    sig = dict(p.split("=", 1) for p in request.headers.get("FYN-Signature", "").split(",") if "=" in p)
    expected = hmac.new(os.environ["FYN_WEBHOOK_SECRET"].encode(),
                        sig.get("t", "").encode() + b"." + body, hashlib.sha256).hexdigest()

    if not hmac.compare_digest(expected, sig.get("v1", "")) or abs(time.time() - int(sig.get("t", 0))) > 300:
        abort(400)

    event = request.get_json()
    return "", 200

Errors, limits and pages

Errors are JSON with error and message: 401 for a missing or revoked key, 403 for a read-only key asked to control a station, 404 for something not on the account, 409 for a station that cannot do what was asked right now, and 429 past 120 requests a minute for a key, 10 a minute for starting, stopping and restarting, or a station's own limits, with Retry-After saying how many seconds to wait.

Lists take ?page= and ?per_page=, 25 by default and 100 at most, and say where they are in meta: page, per_page, total and last_page.

Versions

This is version 1, in the address. Within it we only add: new fields, new events, new endpoints. Nothing is removed or renamed, and no field changes type. Anything that would break an integration comes as a new version, announced ahead.

API reference

All under https://app.fynradio.com/api/v1, all with the key as a bearer token. Reading is GET; starting, stopping and restarting are POST, with no body.

Your account

GET /account

The account the key belongs to, and its balance.

Response

An object under data. It has:

name string
The account holder's name.
email string
The address we email.
balance_cents integer
Credit, in US cents. Below zero means money is owed.
currency string
Always USD.
stations integer
How many stations the account has, of any status.
Request
curl https://app.fynradio.com/api/v1/account \
  -H "Authorization: Bearer $FYN_API_KEY"
Example response
{
    "data": {
        "name": "Amina Otieno",
        "email": "[email protected]",
        "balance_cents": 2500,
        "currency": "USD",
        "stations": 2
    }
}

List stations

GET /stations

Every station on the account, closed ones included, with what each is doing now.

Response

A list under data. Each has:

slug string
The station's address on our servers, and its id. Never changes.
name string
Its name, as listeners see it.
server string
Which server listeners connect to. icecast, shoutcast
status string
Its subscription. What it is doing right now is state, in the API. pending, provisioning, active, suspended, cancelled, terminated
listen_url string
The stream, for players.
page_url string
Its public page.
plan plan
The plan it is on.
next_billing_at date-time or null
When it is next charged.
cancel_at date-time or null
When it ends, if it has been cancelled.
state string
What it is doing right now. silent means an encoder is connected but sending silence, so the AutoDJ is playing. unknown means we have not heard from its server in the last minute, which does not mean it is off air. on_air, silent, fallback, faulty, starting, restarting, stopping, stopped, pending, provisioning, suspended, cancelled, terminated, unknown
listeners integer or null
Listening now.
now_playing string or null
The title on air now.
live boolean or null
Whether an encoder is connected.
default_audio boolean
Whether it is playing default audio, with nothing else to play.
Request
curl https://app.fynradio.com/api/v1/stations \
  -H "Authorization: Bearer $FYN_API_KEY"
Example response
{
    "data": [
        {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm",
            "plan": {
                "slug": "standard",
                "name": "Standard",
                "max_listeners": 250,
                "bitrate_kbps": 128
            },
            "next_billing_at": "2026-11-03T00:00:00Z",
            "cancel_at": null,
            "state": "on_air",
            "listeners": 42,
            "now_playing": "Sauti Sol - Suzanna",
            "live": false,
            "default_audio": false
        }
    ]
}

One station

GET /stations/{slug}

A station, with its plan and what it is doing now.

Parameters

slug string, in the path
The station's slug, as in its address.

Response

An object under data. It has:

slug string
The station's address on our servers, and its id. Never changes.
name string
Its name, as listeners see it.
server string
Which server listeners connect to. icecast, shoutcast
status string
Its subscription. What it is doing right now is state, in the API. pending, provisioning, active, suspended, cancelled, terminated
listen_url string
The stream, for players.
page_url string
Its public page.
plan plan
The plan it is on.
next_billing_at date-time or null
When it is next charged.
cancel_at date-time or null
When it ends, if it has been cancelled.
state string
What it is doing right now. silent means an encoder is connected but sending silence, so the AutoDJ is playing. unknown means we have not heard from its server in the last minute, which does not mean it is off air. on_air, silent, fallback, faulty, starting, restarting, stopping, stopped, pending, provisioning, suspended, cancelled, terminated, unknown
listeners integer or null
Listening now.
now_playing string or null
The title on air now.
live boolean or null
Whether an encoder is connected.
default_audio boolean
Whether it is playing default audio, with nothing else to play.
Request
curl https://app.fynradio.com/api/v1/stations/kayufm \
  -H "Authorization: Bearer $FYN_API_KEY"
Example response
{
    "data": {
        "slug": "kayufm",
        "name": "Kayu FM",
        "server": "icecast",
        "status": "active",
        "listen_url": "https://kayufm.stream.fynradio.com/",
        "page_url": "https://fynradio.com/kayufm",
        "plan": {
            "slug": "standard",
            "name": "Standard",
            "max_listeners": 250,
            "bitrate_kbps": 128
        },
        "next_billing_at": "2026-11-03T00:00:00Z",
        "cancel_at": null,
        "state": "on_air",
        "listeners": 42,
        "now_playing": "Sauti Sol - Suzanna",
        "live": false,
        "default_audio": false
    }
}

What is playing

GET /stations/{slug}/now-playing

The title on air, how many are listening, whether a DJ is live, and the last titles played. Read from what the station's server reported in the last ten seconds.

Parameters

slug string, in the path
The station's slug, as in its address.

Response

An object under data. It has:

station string
The station's slug.
state string
What it is doing, as for a station.
listeners integer or null
Listening now.
now_playing string or null
The title on air.
live boolean or null
Whether an encoder is on air. False while one is connected but sending silence.
default_audio boolean
Whether it is playing default audio.
recent list of string
The last titles played, newest first, up to 20.
Request
curl https://app.fynradio.com/api/v1/stations/kayufm/now-playing \
  -H "Authorization: Bearer $FYN_API_KEY"
Example response
{
    "data": {
        "station": "kayufm",
        "state": "on_air",
        "listeners": 42,
        "now_playing": "Sauti Sol - Suzanna",
        "live": false,
        "default_audio": false,
        "recent": [
            "Sauti Sol - Suzanna",
            "Nyashinski - Mungu Pekee"
        ]
    }
}

Shows and schedule

GET /stations/{slug}/schedule

The show on now, the next, and the week of shows, as the station's page shows them. Times of day are on the station's clock; the moments are UTC.

Parameters

slug string, in the path
The station's slug, as in its address.

Response

An object under data. It has:

station string
The station's slug.
timezone string
The station's time zone, which the week's times are in.
now object or null
The show on now, with starts_at and ends_at, or null when no show is. Its show has name, presenters, description, image and live.
next object or null
The next show to start, in the same shape.
week list of object
Every show's times, Monday first: day, start and end as HH:MM, and the show.
Request
curl https://app.fynradio.com/api/v1/stations/kayufm/schedule \
  -H "Authorization: Bearer $FYN_API_KEY"
Example response
{
    "data": {
        "station": "kayufm",
        "timezone": "Africa/Nairobi",
        "now": {
            "show": {
                "name": "Morning Drive",
                "presenters": "Wanjiru and Otieno",
                "description": "News, traffic and the best of Kenyan music.",
                "image": "https://cdn.fynradio.com/logos/2b9c41f0-6d3e-4f7a-9a51-8e0c7d2f1a64.png",
                "live": true
            },
            "starts_at": "2026-10-19T03:00:00Z",
            "ends_at": "2026-10-19T07:00:00Z"
        },
        "next": {
            "show": {
                "name": "Midday Mix",
                "presenters": null,
                "description": null,
                "image": null,
                "live": false
            },
            "starts_at": "2026-10-19T07:00:00Z",
            "ends_at": "2026-10-19T10:00:00Z"
        },
        "week": [
            {
                "day": "monday",
                "start": "06:00",
                "end": "10:00",
                "show": {
                    "name": "Morning Drive",
                    "presenters": "Wanjiru and Otieno",
                    "description": "News, traffic and the best of Kenyan music.",
                    "image": "https://cdn.fynradio.com/logos/2b9c41f0-6d3e-4f7a-9a51-8e0c7d2f1a64.png",
                    "live": true
                }
            }
        ]
    }
}

Station history

GET /stations/{slug}/events

What has happened to a station, newest first, in the same names and data as its webhooks.

Parameters

slug string, in the path
The station's slug, as in its address.
page integer, in the query, default 1
Which page.
per_page integer, in the query, default 25
How many to a page, up to 100.

Response

A list under data, and meta with page, per_page, total and last_page. Each has:

type string
The event, as a webhook would name it.
data object
Its data, as in the webhook, without the station.
created_at date-time
When it happened.
Request
curl https://app.fynradio.com/api/v1/stations/kayufm/events \
  -H "Authorization: Bearer $FYN_API_KEY"
Example response
{
    "data": [
        {
            "type": "live.ended",
            "data": {},
            "created_at": "2026-10-14T09:58:21Z"
        },
        {
            "type": "live.started",
            "data": {},
            "created_at": "2026-10-14T07:00:03Z"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 2,
        "last_page": 1
    }
}

Listener figures

GET /stations/{slug}/stats

A row a day, oldest first, and where the listening came from. The same figures as the station's Reports page.

Parameters

slug string, in the path
The station's slug, as in its address.
days integer, in the query, default 30
How far back. One of 7, 30, 90.

Response

An object under data. It has:

station string
The station's slug.
days list of object
A row a day since the station began, up to the days asked for. A day with peak_listeners null is one we were not hearing from its server, which is not the same as nobody listening. Each row has date, peak_listeners, average_listeners, listener_minutes, minutes_on_air and bytes_sent.
audience object
Listening by country and by player over the same days: country and player, each a list of label, hours and share, the share a fraction of 1.
Request
curl https://app.fynradio.com/api/v1/stations/kayufm/stats?days=7 \
  -H "Authorization: Bearer $FYN_API_KEY"
Example response
{
    "data": {
        "station": "kayufm",
        "days": [
            {
                "date": "2026-10-13",
                "peak_listeners": 61,
                "average_listeners": 23,
                "listener_minutes": 33120,
                "minutes_on_air": 1440,
                "bytes_sent": 3179520000
            },
            {
                "date": "2026-10-14",
                "peak_listeners": 48,
                "average_listeners": 19,
                "listener_minutes": 16410,
                "minutes_on_air": 600,
                "bytes_sent": 1575360000
            }
        ],
        "audience": {
            "country": [
                {
                    "label": "KE",
                    "hours": 702.5,
                    "share": 0.851
                },
                {
                    "label": "UG",
                    "hours": 63.2,
                    "share": 0.077
                }
            ],
            "player": [
                {
                    "label": "Browser",
                    "hours": 512,
                    "share": 0.62
                },
                {
                    "label": "VLC",
                    "hours": 101.8,
                    "share": 0.123
                }
            ]
        }
    }
}

Stop a station

POST /stations/{slug}/stop

Takes the station off air. Everyone listening is disconnected, and it stays off until started. A station already stopped answers 200 with changed false.

Needs a key made to control stations. A read-only key gets 403.

202
Done. The station's server acts within seconds.
200
Nothing to do: it already was.
409
Not now: a suspended, cancelled or closed station, or a restart of one that is not on air.
429
Too soon after the last. Retry-After says how many seconds to wait.

Parameters

slug string, in the path
The station's slug, as in its address.

Response

An object under data. It has:

station string
The station's slug.
action string
What was asked. stop
changed boolean
False when there was nothing to do: stopping a station already stopped, say.
state string
What it is doing now, as for a station. Usually on its way: the station's server acts within seconds.
Request
curl -X POST https://app.fynradio.com/api/v1/stations/kayufm/stop \
  -H "Authorization: Bearer $FYN_API_KEY"
Example response
{
    "data": {
        "station": "kayufm",
        "action": "stop",
        "changed": true,
        "state": "stopping"
    }
}

Start a station

POST /stations/{slug}/start

Puts a stopped station back on air, within seconds. A station already on answers 200 with changed false.

Needs a key made to control stations. A read-only key gets 403.

202
Done. The station's server acts within seconds.
200
Nothing to do: it already was.
409
Not now: a suspended, cancelled or closed station, or a restart of one that is not on air.
429
Too soon after the last. Retry-After says how many seconds to wait.

Parameters

slug string, in the path
The station's slug, as in its address.

Response

An object under data. It has:

station string
The station's slug.
action string
What was asked. start
changed boolean
False when there was nothing to do: stopping a station already stopped, say.
state string
What it is doing now, as for a station. Usually on its way: the station's server acts within seconds.
Request
curl -X POST https://app.fynradio.com/api/v1/stations/kayufm/start \
  -H "Authorization: Bearer $FYN_API_KEY"
Example response
{
    "data": {
        "station": "kayufm",
        "action": "start",
        "changed": true,
        "state": "starting"
    }
}

Restart a station

POST /stations/{slug}/restart

Restarts a station that is on air. Listeners are disconnected and reconnect within seconds. Restarts have to be some minutes apart: one too soon answers 429, with Retry-After.

Needs a key made to control stations. A read-only key gets 403.

202
Done. The station's server acts within seconds.
200
Nothing to do: it already was.
409
Not now: a suspended, cancelled or closed station, or a restart of one that is not on air.
429
Too soon after the last. Retry-After says how many seconds to wait.

Parameters

slug string, in the path
The station's slug, as in its address.

Response

An object under data. It has:

station string
The station's slug.
action string
What was asked. restart
changed boolean
False when there was nothing to do: stopping a station already stopped, say.
state string
What it is doing now, as for a station. Usually on its way: the station's server acts within seconds.
Request
curl -X POST https://app.fynradio.com/api/v1/stations/kayufm/restart \
  -H "Authorization: Bearer $FYN_API_KEY"
Example response
{
    "data": {
        "station": "kayufm",
        "action": "restart",
        "changed": true,
        "state": "restarting"
    }
}

List invoices

GET /invoices

Every invoice on the account, newest first.

Parameters

page integer, in the query, default 1
Which page.
per_page integer, in the query, default 25
How many to a page, up to 100.

Response

A list under data, and meta with page, per_page, total and last_page. Each has:

number string
The invoice number, its id in the API.
kind string
A monthly charge, a plan upgrade, or a top-up you started. charge, upgrade, topup
description string
What it is for, as on the PDF.
station string or null
The station's slug, for a charge or an upgrade.
amount_cents integer
The total, in US cents.
due_cents integer
What is still owed. 0 once paid or voided.
currency string
Always USD.
status string
Whether it is settled. unpaid, paid, void
period_start date or null
The first day it pays for.
period_end date or null
The last day it pays for.
issued_at date-time
When it was made.
due_at date-time
When stations stop if it is still unpaid.
paid_at date-time or null
When it was paid in full.
voided_at date-time or null
When it was voided.
url string
Its page on your dashboard.
Request
curl https://app.fynradio.com/api/v1/invoices \
  -H "Authorization: Bearer $FYN_API_KEY"
Example response
{
    "data": [
        {
            "number": "FYN-2026-0417",
            "kind": "charge",
            "description": "Kayu FM, Standard, 3 Oct to 2 Nov 2026",
            "station": "kayufm",
            "amount_cents": 1295,
            "due_cents": 0,
            "currency": "USD",
            "status": "paid",
            "period_start": "2026-10-03",
            "period_end": "2026-11-02",
            "issued_at": "2026-10-03T00:00:04Z",
            "due_at": "2026-10-10T00:00:04Z",
            "paid_at": "2026-10-04T08:12:40Z",
            "voided_at": null,
            "url": "https://app.fynradio.com/billing/invoices/417"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "last_page": 1
    }
}

One invoice

GET /invoices/{number}

An invoice, by its number.

Parameters

number string, in the path
The invoice number.

Response

An object under data. It has:

number string
The invoice number, its id in the API.
kind string
A monthly charge, a plan upgrade, or a top-up you started. charge, upgrade, topup
description string
What it is for, as on the PDF.
station string or null
The station's slug, for a charge or an upgrade.
amount_cents integer
The total, in US cents.
due_cents integer
What is still owed. 0 once paid or voided.
currency string
Always USD.
status string
Whether it is settled. unpaid, paid, void
period_start date or null
The first day it pays for.
period_end date or null
The last day it pays for.
issued_at date-time
When it was made.
due_at date-time
When stations stop if it is still unpaid.
paid_at date-time or null
When it was paid in full.
voided_at date-time or null
When it was voided.
url string
Its page on your dashboard.
Request
curl https://app.fynradio.com/api/v1/invoices/FYN-2026-0417 \
  -H "Authorization: Bearer $FYN_API_KEY"
Example response
{
    "data": {
        "number": "FYN-2026-0417",
        "kind": "charge",
        "description": "Kayu FM, Standard, 3 Oct to 2 Nov 2026",
        "station": "kayufm",
        "amount_cents": 1295,
        "due_cents": 0,
        "currency": "USD",
        "status": "paid",
        "period_start": "2026-10-03",
        "period_end": "2026-11-02",
        "issued_at": "2026-10-03T00:00:04Z",
        "due_at": "2026-10-10T00:00:04Z",
        "paid_at": "2026-10-04T08:12:40Z",
        "voided_at": null,
        "url": "https://app.fynradio.com/billing/invoices/417"
    }
}

List payments

GET /payments

Every payment, newest first, including ones that failed.

Parameters

page integer, in the query, default 1
Which page.
per_page integer, in the query, default 25
How many to a page, up to 100.

Response

A list under data, and meta with page, per_page, total and last_page. Each has:

id integer
The payment's id.
invoice string or null
The number of the invoice it paid.
method string
How it was paid. paypal, mpesa, cash
reference string
The receipt or reference from PayPal or M-Pesa.
amount_cents integer
What it added to your balance, in US cents.
currency string
Always USD.
paid_amount string
What was actually paid, in the currency paid in.
paid_currency string
The currency it was paid in, such as KES.
status string
Where it stands. pending, completed, failed, reversed
created_at date-time
When it was started.
completed_at date-time or null
When the money arrived.
Request
curl https://app.fynradio.com/api/v1/payments \
  -H "Authorization: Bearer $FYN_API_KEY"
Example response
{
    "data": [
        {
            "id": 905,
            "invoice": "FYN-2026-0418",
            "method": "mpesa",
            "reference": "SJK4H7Q2XD",
            "amount_cents": 2000,
            "currency": "USD",
            "paid_amount": "2600.00",
            "paid_currency": "KES",
            "status": "completed",
            "created_at": "2026-10-04T08:11:02Z",
            "completed_at": "2026-10-04T08:12:40Z"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "last_page": 1
    }
}

Webhook events

Station, on air and music events carry the station under data.station, and a webhook's station filter applies to them. Billing and support events are for the whole account.

Station

station.ready

The station is set up and on air for the first time.

With the station under data.station.

Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "station.ready",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        }
    }
}

station.waiting

A station was ordered and is waiting to be set up.

With the station under data.station.

In data

reason string
For payment to arrive, for room on a server, or for us to review the account. payment, capacity, review
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "station.waiting",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        },
        "reason": "payment"
    }
}

station.stopped

The station was stopped. It stays off air until started.

With the station under data.station.

In data

by string
Who did it: you on the dashboard, your systems through the API, our support, or billing on its own. client, api, support, billing
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "station.stopped",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        },
        "by": "client"
    }
}

station.started

The station was started again.

With the station under data.station.

In data

by string
Who did it: you on the dashboard, your systems through the API, our support, or billing on its own. client, api, support, billing
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "station.started",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        },
        "by": "client"
    }
}

station.restarted

The station was restarted. Listeners reconnect within seconds.

With the station under data.station.

In data

by string
Who did it: you on the dashboard, your systems through the API, our support, or billing on its own. client, api, support, billing
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "station.restarted",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        },
        "by": "support"
    }
}

station.suspended

The station stopped because the balance stayed below zero past the grace period.

With the station under data.station.

In data

reason string
Why. balance
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "station.suspended",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        },
        "reason": "balance"
    }
}

station.resumed

A suspended station is back on air because the balance cleared.

With the station under data.station.

Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "station.resumed",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        }
    }
}

station.plan_changed

The station moved to another plan.

With the station under data.station.

In data

from_plan string
The slug of the plan it was on.
to_plan string
The slug of the plan it is on now.
charged_cents integer
What the move cost now, in US cents. 0 for a move at renewal.
by string
Who did it: you on the dashboard, your systems through the API, our support, or billing on its own. client, api, support, billing
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "station.plan_changed",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        },
        "from_plan": "basic",
        "to_plan": "standard",
        "charged_cents": 412,
        "by": "client"
    }
}

station.plan_change_scheduled

A move to a smaller plan was set for the next renewal, or such a move was taken back.

With the station under data.station.

In data

withdrawn boolean
True when a scheduled move was taken back. The fields below are then absent.
to_plan string
The slug of the plan it will move to.
takes_effect date-time
When it moves.
by string
Who did it: you on the dashboard, your systems through the API, our support, or billing on its own. client, api, support, billing
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "station.plan_change_scheduled",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        },
        "withdrawn": false,
        "to_plan": "basic",
        "takes_effect": "2026-11-03T00:00:00Z",
        "by": "client"
    }
}

station.cancellation_scheduled

The station was cancelled. It stays on air until what is paid for runs out.

With the station under data.station.

In data

ends_at date-time
When it goes off air.
by string
Who did it: you on the dashboard, your systems through the API, our support, or billing on its own. client, api, support, billing
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "station.cancellation_scheduled",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        },
        "ends_at": "2026-11-03T00:00:00Z",
        "by": "client"
    }
}

station.cancellation_withdrawn

A cancellation was taken back before the station ended.

With the station under data.station.

In data

by string
Who did it: you on the dashboard, your systems through the API, our support, or billing on its own. client, api, support, billing
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "station.cancellation_withdrawn",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        },
        "by": "client"
    }
}

station.cancelled

A cancelled station reached its end and went off air. It can be resubscribed until it is closed.

With the station under data.station.

Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "station.cancelled",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        }
    }
}

station.resubscribed

A cancelled station was subscribed again.

With the station under data.station.

In data

by string
Who did it: you on the dashboard, your systems through the API, our support, or billing on its own. client, api, support, billing
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "station.resubscribed",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        },
        "by": "client"
    }
}

station.terminated

The station was closed for good. Its music is deleted and its address freed.

With the station under data.station.

Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "station.terminated",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        }
    }
}

On air

live.started

An encoder connected, so a DJ is on air.

With the station under data.station.

Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "live.started",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        }
    }
}

live.ended

The encoder disconnected. The AutoDJ takes over.

With the station under data.station.

Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "live.ended",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        }
    }
}

default_audio.started

Nothing else to play, so the station is on default audio: no DJ, and nothing in its playlist.

With the station under data.station.

Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "default_audio.started",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        }
    }
}

default_audio.ended

The station is playing your audio again.

With the station under data.station.

In data

live boolean
Whether that is because a DJ connected.
title string or null
What it is playing now.
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "default_audio.ended",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        },
        "live": true,
        "title": "Morning Drive with Wanjiru"
    }
}

track.changed

A new title went on air, from a DJ or the AutoDJ. About one a song, so expect many.

With the station under data.station.

In data

title string
What is on air now.
previous_title string or null
What was on before it.
live boolean
Whether it comes from a DJ.
default_audio boolean
Whether it is default audio.
listeners integer
Listening at the change.
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "track.changed",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        },
        "title": "Sauti Sol - Suzanna",
        "previous_title": "Nyashinski - Mungu Pekee",
        "live": false,
        "default_audio": false,
        "listeners": 42
    }
}

show.started

A show on the station's week began, by its schedule. Whether its DJ is on air is live.started's to say.

With the station under data.station.

In data

show object
The show: name, presenters, description, image (a URL, or null) and live, which is whether a DJ is expected rather than the AutoDJ.
starts_at date-time
When this showing began.
ends_at date-time
When it ends.
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "show.started",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        },
        "show": {
            "name": "Morning Drive",
            "presenters": "Wanjiru and Otieno",
            "description": "News, traffic and the best of Kenyan music.",
            "image": "https://cdn.fynradio.com/logos/2b9c41f0-6d3e-4f7a-9a51-8e0c7d2f1a64.png",
            "live": true
        },
        "starts_at": "2026-10-19T03:00:00Z",
        "ends_at": "2026-10-19T07:00:00Z"
    }
}

Music

media.ready

An uploaded track finished processing and can be played.

With the station under data.station.

In data

media media
The track.
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "media.ready",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        },
        "media": {
            "id": 3120,
            "filename": "suzanna.mp3",
            "title": "Suzanna",
            "artist": "Sauti Sol",
            "album": null,
            "duration_seconds": 214,
            "size_bytes": 5146624,
            "status": "ready"
        }
    }
}

media.failed

An uploaded track could not be used.

With the station under data.station.

In data

media media
The track.
reason string
Why, in words for a person.
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "media.failed",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        },
        "media": {
            "id": 3120,
            "filename": "suzanna.mp3",
            "title": "Suzanna",
            "artist": "Sauti Sol",
            "album": null,
            "duration_seconds": 214,
            "size_bytes": 5146624,
            "status": "failed"
        },
        "reason": "That is not an MP3 we can read."
    }
}

media.deleted

A track was deleted from the library.

With the station under data.station.

In data

media media
The track, as it was.
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "media.deleted",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "station": {
            "slug": "kayufm",
            "name": "Kayu FM",
            "server": "icecast",
            "status": "active",
            "listen_url": "https://kayufm.stream.fynradio.com/",
            "page_url": "https://fynradio.com/kayufm"
        },
        "media": {
            "id": 3120,
            "filename": "suzanna.mp3",
            "title": "Suzanna",
            "artist": "Sauti Sol",
            "album": null,
            "duration_seconds": 214,
            "size_bytes": 5146624,
            "status": "ready"
        }
    }
}

Billing

invoice.issued

A monthly charge or a plan upgrade was invoiced.

In data

invoice invoice
The invoice.
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "invoice.issued",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "invoice": {
            "number": "FYN-2026-0417",
            "kind": "charge",
            "description": "Kayu FM, Standard, 3 Oct to 2 Nov 2026",
            "station": "kayufm",
            "amount_cents": 1295,
            "due_cents": 1295,
            "currency": "USD",
            "status": "unpaid",
            "period_start": "2026-10-03",
            "period_end": "2026-11-02",
            "issued_at": "2026-10-03T00:00:04Z",
            "due_at": "2026-10-10T00:00:04Z",
            "paid_at": null,
            "voided_at": null,
            "url": "https://app.fynradio.com/billing/invoices/417"
        }
    }
}

invoice.paid

An invoice was paid in full, by a payment or from credit.

In data

invoice invoice
The invoice.
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "invoice.paid",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "invoice": {
            "number": "FYN-2026-0417",
            "kind": "charge",
            "description": "Kayu FM, Standard, 3 Oct to 2 Nov 2026",
            "station": "kayufm",
            "amount_cents": 1295,
            "due_cents": 0,
            "currency": "USD",
            "status": "paid",
            "period_start": "2026-10-03",
            "period_end": "2026-11-02",
            "issued_at": "2026-10-03T00:00:04Z",
            "due_at": "2026-10-10T00:00:04Z",
            "paid_at": "2026-10-04T08:12:40Z",
            "voided_at": null,
            "url": "https://app.fynradio.com/billing/invoices/417"
        }
    }
}

invoice.voided

An invoice was cancelled. Nothing is owed on it.

In data

invoice invoice
The invoice.
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "invoice.voided",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "invoice": {
            "number": "FYN-2026-0417",
            "kind": "charge",
            "description": "Kayu FM, Standard, 3 Oct to 2 Nov 2026",
            "station": "kayufm",
            "amount_cents": 1295,
            "due_cents": 0,
            "currency": "USD",
            "status": "void",
            "period_start": "2026-10-03",
            "period_end": "2026-11-02",
            "issued_at": "2026-10-03T00:00:04Z",
            "due_at": "2026-10-10T00:00:04Z",
            "paid_at": null,
            "voided_at": "2026-10-05T10:00:00Z",
            "url": "https://app.fynradio.com/billing/invoices/417"
        }
    }
}

payment.succeeded

Money arrived and was added to the balance.

In data

payment payment
The payment.
balance_cents integer
The balance after it, in US cents.
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "payment.succeeded",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "payment": {
            "id": 905,
            "invoice": "FYN-2026-0418",
            "method": "mpesa",
            "reference": "SJK4H7Q2XD",
            "amount_cents": 2000,
            "currency": "USD",
            "paid_amount": "2600.00",
            "paid_currency": "KES",
            "status": "completed",
            "created_at": "2026-10-04T08:11:02Z",
            "completed_at": "2026-10-04T08:12:40Z"
        },
        "balance_cents": 2500
    }
}

payment.failed

A payment did not go through. Nothing was added.

In data

payment payment
The payment.
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "payment.failed",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "payment": {
            "id": 905,
            "invoice": "FYN-2026-0418",
            "method": "mpesa",
            "reference": "SJK4H7Q2XD",
            "amount_cents": 2000,
            "currency": "USD",
            "paid_amount": "2600.00",
            "paid_currency": "KES",
            "status": "failed",
            "created_at": "2026-10-04T08:11:02Z",
            "completed_at": null
        }
    }
}

balance.low

The balance is below zero, and the stations stop on a date unless it is topped up. Sent on the days we email about it.

In data

balance_cents integer
The balance, in US cents.
currency string
Always USD.
stations_stop_at date-time
When the stations stop.
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "balance.low",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "balance_cents": -1295,
        "currency": "USD",
        "stations_stop_at": "2026-10-10T00:00:04Z"
    }
}

Support

ticket.replied

We answered one of your support tickets.

In data

ticket ticket
The ticket.
message string
Our reply, as text.
files integer
How many files came with it. They are on the ticket's page.
Example body
{
    "id": "6f1c2a8e-4d0b-4c7e-9a51-2b8d7f0e3c14",
    "type": "ticket.replied",
    "test": false,
    "created_at": "2026-10-14T09:12:04Z",
    "data": {
        "ticket": {
            "id": 42,
            "reference": "#42",
            "subject": "Encoder will not connect",
            "status": "answered",
            "station": "kayufm",
            "url": "https://app.fynradio.com/support/42"
        },
        "message": "The port is 10006, not 8000. Try that and tell us how it goes.",
        "files": 0
    }
}

Objects

The same in the API and in webhooks.

Station

slug string
The station's address on our servers, and its id. Never changes.
name string
Its name, as listeners see it.
server string
Which server listeners connect to. icecast, shoutcast
status string
Its subscription. What it is doing right now is state, in the API. pending, provisioning, active, suspended, cancelled, terminated
listen_url string
The stream, for players.
page_url string
Its public page.

Plan

slug string
The plan's id.
name string
Its name, as on the pricing page.
max_listeners integer
How many can listen at once.
bitrate_kbps integer
The stream's bitrate.

Invoice

number string
The invoice number, its id in the API.
kind string
A monthly charge, a plan upgrade, or a top-up you started. charge, upgrade, topup
description string
What it is for, as on the PDF.
station string or null
The station's slug, for a charge or an upgrade.
amount_cents integer
The total, in US cents.
due_cents integer
What is still owed. 0 once paid or voided.
currency string
Always USD.
status string
Whether it is settled. unpaid, paid, void
period_start date or null
The first day it pays for.
period_end date or null
The last day it pays for.
issued_at date-time
When it was made.
due_at date-time
When stations stop if it is still unpaid.
paid_at date-time or null
When it was paid in full.
voided_at date-time or null
When it was voided.
url string
Its page on your dashboard.

Payment

id integer
The payment's id.
invoice string or null
The number of the invoice it paid.
method string
How it was paid. paypal, mpesa, cash
reference string
The receipt or reference from PayPal or M-Pesa.
amount_cents integer
What it added to your balance, in US cents.
currency string
Always USD.
paid_amount string
What was actually paid, in the currency paid in.
paid_currency string
The currency it was paid in, such as KES.
status string
Where it stands. pending, completed, failed, reversed
created_at date-time
When it was started.
completed_at date-time or null
When the money arrived.

Track

id integer
The track's id.
filename string
The name of the file you uploaded.
title string or null
From its tags.
artist string or null
From its tags.
album string or null
From its tags.
duration_seconds integer
Its length.
size_bytes integer
Its size.
status string
Whether it can be played yet. uploading, processing, ready, failed

Ticket

id integer
The ticket's id.
reference string
As shown on your dashboard, such as #42.
subject string
What you called it.
status string
Whose turn it is. open, answered, closed
station string or null
The station's slug, if it is about one.
url string
Its page on your dashboard.