Once a stream is added, recognition results from AudD are delivered to your code in the form of events. There are two ways to receive events from AudD. You can receive them through webhook callbacks on your server, or through longpoll from your client. Which one you use depends on whether you can host a public HTTPS endpoint. This page explains the two methods, their delivery guarantees, and how to choose between them.
TL;DR
- Can you host a public HTTPS endpoint that returns 200 OK? Use callbacks. Set the URL for callbacks once using the setCallbackUrl API method. Callbacks are the POST requests with recognition results AudD will send to that URL. This is the recommended option. It has the lowest latency and doesn’t require polling. If your server is briefly down or AudD can’t connect to it, AudD will put the callbacks in a retry queue.
- Can’t expose a public endpoint (local dev, a browser widget, a mobile client, no static IP)? Use longpoll. Your client doesn’t have to be exposed to the internet. The longpoll address is GET https://api.audd.io/longpoll/.
- Key fact, easy to miss: Please set the callback URL to an actual URL that can receive POST requests even if you want to use longpoll only. If you don’t have any such URLs, set https://audd.tech/empty/ as the callback URL. This no-op URL simply returns 200 OK. Otherwise, longpoll returns nothing.
You can use longpoll instead of or together with receiving callbacks. For example, a backend can use callbacks to log the results, while a widget can use longpoll to show the results of the same stream.
Why this matters
AudD recognizes the songs on a stream as they’re being played. The result for the song that’s on the air right now comes seconds or minutes after you added the stream. Then a result comes for the next song, and so on, indefinitely. This is an asynchronous process, so you can’t block on a request to “get the answer”. Something then has to deliver an event each time a new result is available.
With callbacks, control is inverted: AudD calls you. You don’t have to ask for these results yourself; instead, just handle the callback. In order for AudD to send callbacks, your server needs to be accessible from the Internet over HTTPS. Make sure that your server is up when AudD sends the callbacks. With longpoll, the client sends its request, and waits for the answer. The connection remains open until an event is ready or the timeout elapses. This only requires the client to make outbound HTTPS connections to AudD. The client can be a browser, a phone, or a laptop behind NAT. There’s no inbound endpoint to host.
Choosing the wrong method is rarely fatal, but it’s always annoying: you’ll either build a webhook receiver you can’t expose from a browser, or you’ll poll from a server that could have just received a push. So decide where your result-handling code actually runs, and choose the method around it.
Callbacks: AudD POSTs to you
Set the URL for callbacks using the setCallbackUrl API method. Only a single callback URL can be set per account, and you can change it at any time using the same method. From then on, callbacks with every recognition result and notification will be sent to this URL as a JSON structure in the POST payload.
You’ll receive callbacks with results and with notifications.
- Results contain the recognized songs. Each result also contains the radio_id you chose for the stream, a timestamp, and (by default) the play_length.
- Notifications are information about the health of the streams. If something happens to an audio stream, AudD will let you know by sending a notification, so you can set up an alert and fix the stream or change its URL. There are 3 codes: 0 (which means that everything’s fine), 650 (which means that AudD can’t connect to the stream) and 651 (which means that AudD receives only white noise from the stream, with no music).
Please ensure that your handler responds with the 200 OK HTTP status when it receives the callbacks. If your server responds with anything else, or AudD can’t connect to it, the delivery is treated as failed and the callbacks are put in a queue for retry (see the delivery semantics below). If you can host a receiver, callbacks are the recommended method.
Longpoll: you pull from AudD
If you can’t host an inbound endpoint, longpoll lets any number of clients fetch the new results over plain outbound HTTPS, without having to spin up a web server or have a static IP.
Each longpoll request completes either because a new event is ready or because timeout seconds have passed. When you receive a response containing the timestamp field, use it as the updated since_time for the new request, so that you don’t miss or repeat events. As the subscription category, use the longpoll_category field from the getStreams response, or calculate the first nine characters of MD5(MD5("your api_token") + "radio_id"). You can share the categories with your clients or users, but never include your API token in the client-side software.
Longpoll still needs a callback URL configured. Please set the callback URL even if you want to use longpoll only and never run a webhook receiver; otherwise, longpoll returns nothing. If you don’t have any such URL, set the no-op receiver https://audd.tech/empty/ as the callback URL. It responds with 200 OK and discards the payload. Start polling only after you’ve set it once with the setCallbackUrl method (or in the web interface).
Longpoll still needs a callback URL configured. Please set the callback URL even if you want to use longpoll only and never run a webhook receiver; otherwise, longpoll returns nothing. If you don’t have any such URL, set the no-op receiver https://audd.tech/empty/ as the callback URL. It responds with 200 OK and discards the payload. Start polling only after you’ve set it once with the setCallbackUrl method (or in the web interface).
Because longpoll runs on the client side, it allows you to show the results in a browser or a mobile app. You can also use AudD’s ready-made HTML widget, https://widget.audd.tech/?ch=-92b1cc7f0&background&history&shadow. It’s driven by longpoll. Use the longpoll category with ”-” before it as the ch URL parameter. You’ll be able to show what’s now playing without having any backend at all.
Delivery semantics: retries, ordering, and missed events
The two methods behave differently when something goes wrong, and this difference is often the deciding factor.
Callbacks have a retry queue. If your server doesn’t respond to the callbacks with the 200 OK status or AudD can’t connect to your server, AudD puts the callbacks in a queue. When your server is back online, AudD sends the backlog gradually instead of dropping it. AudD has a rate limit for the requests it sends: with fewer than 500 streams, you won’t receive more than 3 callbacks per second (with a burst size of 15). The backlog includes everything that wasn’t delivered, so a brief outage costs you a delay, not the data, as long as your endpoint eventually returns 200 OK.
Longpoll catches up by cursor. Unlike callbacks, there’s no server-pushed retry. Instead, you control continuity with since_time: carry each response’s timestamp forward, and you’ll receive everything since that point. If you skip this step, or if too much time passes between requests, you risk a gap. The cursor is what prevents missed and duplicated events, so storing and reusing it is mandatory, not optional.
Ordering. Both deliver events as the songs are recognized over time. With callbacks, the order of the events is the order in which AudD sends them. With longpoll, it’s the order of the since_time progression you drive. In both cases, you should never assume a strict global ordering across many streams; key results by radio_id (and timestamp) instead.
You can disable longpoll for your streams by adding disable-lastsong=true as a parameter to your callback URL. This can be useful if you only use callbacks and don’t want the results to be kept for polling.
The callbacks="before" option
By default, a result is sent after the song finishes playing, and it contains play_length, the total time the song was on air. This is too late for a now-playing display, because the song would be announced only when it’s over.
Set callbacks="before" when adding the stream if you want to be notified as soon as a song starts playing (instead of when it ends):
The downside is that with before, you won’t receive the total played time, because the song hasn’t finished yet. However, the two timings have different benefits, depending on your use case:
- before is for live “now playing” UIs.
- The default (after) is for airplay logs and reports, where you want accurate durations.
This setting controls when an event is generated, and it applies whether you then receive the event through callbacks or through longpoll.
Worked example
Two consumers of the same recognition data. Each one uses the method that fits it:
A server backend logging airplay (callbacks). The backend has a public HTTPS endpoint. For it, callbacks are the recommended option. Set the URL for callbacks once using setCallbackUrl("https://yourapp.example/audd-callback"). Add each station using the addStream method. By default, AudD will send new results after songs end playing, so each result carries play_length. The backend receives the callbacks from the AudD API, writes every result row keyed by radio_id, and returns 200 OK to AudD. For a deploy, the backend will restart. However, the retry queue is robust enough that you can restart without losing plays. When the backend is back online, AudD will send all the backlog. If something happens to a stream, AudD will let you know by sending a 650 or 651 notification. Set up alerts for 650/651 notifications, so a dead stream surfaces immediately.
A browser now-playing widget (longpoll). Web pages can’t host inbound webhooks, so callbacks are out. First, set a callback URL; if you don’t have one, use the no-op https://audd.tech/empty/ (a callback URL is required for longpoll to return anything). Then add the stream with callbacks="before", so that the widget updates as soon as a song starts playing. You can then embed AudD’s hosted widget. Use the longpoll category with ”-” before it as the ch URL parameter, like ch=-<category>. If you’re making the longpoll requests from your own front end, poll https://api.audd.io/longpoll/ with the category and carry each timestamp into the next since_time. This way, the widget gets the new results without having to spin up a web server or have a static IP. It can run together with the callback-based backend on the same stream.
Common mistakes
- Starting longpoll without a callback URL set. Longpoll needs a configured callback URL to retain results. Otherwise, the poll returns nothing. If you don’t have any such URLs, set https://audd.tech/empty/ as the callback URL.
- Returning a non-200 from your callback handler. If your server doesn’t respond to the callbacks with the 200 OK status, AudD marks the delivery as failed and puts the callbacks in a queue. Return 200 immediately, then process asynchronously, so slow processing doesn’t back up the queue.
- Ignoring since_time in longpoll. If you don’t carry each response’s timestamp into the next since_time, or miss one of the timestamps, you’ll re-fetch old events or skip new ones. This cursor is how longpoll stays gapless.
- Putting your API token in client-side longpoll code. You can share the longpoll_category with browsers and apps, but don’t include your api_token in the client-side software. To get the category, calculate the first nine characters of MD5(MD5("your api_token") + "radio_id").
- Expecting play_length with callbacks="before". If you send "before", you won’t receive the total played time in the callbacks. By default, callbacks are sent after the song ends playing and contain the total played time. When you need durations, the default is the recommended option.
- Building a webhook receiver for a browser or mobile UI. Browser and mobile clients can’t accept inbound POST requests. You can use longpoll (or the hosted widget) for anything that runs on the client side.
- Standard, enterprise, or streams: how to choose
- Build a now-playing widget
- Monitor radio airplay
- Streams docs
- API reference







