How to use callbacks with the AudD API for real-time music alerts
Configure AudD stream recognition with callback webhooks for instant song-detection notifications: register a stream, receive callbacks, parse payloads leniently, and build alerts.
Instead of polling, you register a live stream once and AudD pushes each match to your server the moment it identifies a song. This tutorial shows how to configure AudD stream recognition with callback webhooks and build responsive alerting on top.
Why callbacks beat polling
Polling wastes effort: you send requests every few seconds hoping to catch a new identification, and most come back empty. Callbacks invert that. AudD POSTs a result to your endpoint as soon as it recognizes a song in your stream, so your system reacts in real time without polling overhead.
Common uses:
- Radio airplay monitoring — get notified the moment a tracked artist or label airs.
- Copyright protection — receive alerts when your content appears in an unauthorized stream.
- Content analysis — trigger automated workflows on each detection.
- Real-time dashboards — update displays as songs are identified.
AudD stream recognition works against both the public catalog of about 160 million songs and your own custom catalog, so it suits mainstream monitoring and proprietary-catalog tracking alike.
If you’d rather pull than expose a public endpoint, AudD also offers longpoll: your backend holds a request open and AudD returns matches as they happen. Longpoll is the alternative to callbacks for the same stream-recognition feature.
How AudD stream callbacks work
Two methods set this up:
addStreamregisters a live audio stream (a radio or stream URL) and gives it an id (radio_id).setCallbackUrltells AudD where to POST results.
Once registered, AudD recognizes the stream continuously and POSTs JSON to your callback URL. Your endpoint must respond with HTTP 200 OK. If it doesn’t, AudD queues the callback and delivers it later, so a brief outage on your side won’t silently drop matches.
By default AudD sends a callback when a song ends; you can configure it to notify at the start of a song instead.
Setting up your callback endpoint
Your endpoint needs to accept POST requests with a JSON body and return 200 OK. Here’s a minimal receiver in Node:
const express = require("express");
const app = express();
app.use(express.json());
app.post("/webhook/audd", (req, res) => {
const body = req.body || {};
// Recognition result
if (body.status === "success" && body.result) {
const { radio_id, timestamp, results } = body.result;
for (const match of results || []) {
console.log(`[${radio_id}] ${match.artist} - ${match.title} @ ${timestamp}`);
handleSongDetection({ radioId: radio_id, timestamp, match });
}
}
// Stream notification (status changes, errors)
if (body.notification) {
console.log("Stream notification:", body.notification.notification_message);
}
// Always acknowledge with 200 so AudD doesn't re-queue
res.status(200).send("OK");
});
app.listen(3000, () => console.log("Callback server on :3000"));
Requirements for your endpoint:
- Accept POST requests with a JSON body.
- Respond with HTTP 200 on success (anything else queues a retry).
- Use HTTPS in production.
Your URL must be publicly reachable. For local development, tunnel with a tool like ngrok.
Registering a stream
Register the stream and set the callback URL. With the SDK you do this once; with raw HTTP it’s two calls:
# Point AudD at your callback endpoint
curl https://api.audd.io/setCallbackUrl/ \
-F api_token=your-token \
-F url=https://yourapp.example/webhook/audd
# Register a station's live stream
curl https://api.audd.io/addStream/ \
-F api_token=your-token \
-F url=https://radio.example.com/stream.mp3
Each stream gets its own radio_id, so you always know which source a match came from. Request the metadata you need — for example Apple Music and Spotify links — so each result arrives report-ready.
Understanding the callback payload
AudD POSTs two kinds of payload. A recognition result looks like this:
{
"status": "success",
"result": {
"radio_id": 7,
"timestamp": "2026-04-08 14:30:15",
"play_length": 111,
"results": [
{
"artist": "The Beatles",
"title": "Come Together",
"album": "Abbey Road",
"release_date": "1969-09-26",
"label": "Apple Records",
"song_link": "https://lis.tn/ComeTogether",
"score": 100
}
]
}
}
A stream notification (status changes, errors) looks like this:
{
"status": "-",
"notification": {
"radio_id": 3,
"notification_code": 650,
"notification_message": "..."
}
}
Note the shape: result.results is an array, and radio_id, timestamp, and play_length sit on result. The match score requires a Startup plan or higher, as do isrc and upc.
Parsing payloads leniently
Any field can be missing. Degrade to null rather than throwing — only a malformed body or a transport problem is a real error.
function handleSongDetection({ radioId, timestamp, match }) {
if (!match) return; // nothing recognized in this segment
const song = {
artist: match.artist ?? null,
title: match.title ?? null,
album: match.album ?? null,
detectedAt: timestamp ? new Date(timestamp.replace(" ", "T") + "Z") : null,
radioId,
};
saveSongDetection(song);
if (isTargetArtist(song.artist)) {
sendAlert(`Target artist on stream ${radioId}: ${song.artist} - ${song.title}`);
}
updateDashboard(song);
}
function isTargetArtist(artist) {
const targets = ["Taylor Swift", "Drake", "Billie Eilish"];
return artist != null && targets.includes(artist);
}
Building real-time alerts
Callbacks make targeted alerting easy. Match each detection against your rules and route to the right channels.
const alertRules = [
{ type: "artist", value: "Taylor Swift", channels: ["email", "slack"] },
{ type: "label", value: "Universal Music Group", channels: ["email"] },
];
function processAlerts(song) {
for (const rule of alertRules) {
if (matchesRule(song, rule)) {
sendNotification(rule.channels, song);
}
}
}
function matchesRule(song, rule) {
switch (rule.type) {
case "artist": return song.artist === rule.value;
case "label": return song.label === rule.value;
default: return false;
}
}
Two practical refinements:
Deduplicate. A track spans several recognitions and stations repeat songs. Collapse consecutive matches of the same title from the same radio_id into one play instead of alerting on every callback.
function isDuplicate(song, recentWindowMs = 30 * 60 * 1000) {
const recent = getRecentDetections(recentWindowMs);
return recent.some(
(d) => d.artist === song.artist && d.title === song.title && d.radioId === song.radioId
);
}
Fan out to multiple channels.
async function sendNotification(channels, song) {
const message = `Detected: ${song.artist} - ${song.title}`;
for (const channel of channels) {
if (channel === "email") await sendEmail(message, song);
if (channel === "slack") await postToSlack(message, song);
}
}
Handling delivery failures
AudD requires a 200 OK. If your endpoint returns anything else (or is unreachable), the callback enters a queue and AudD retries delivery later — so the safest pattern is to acknowledge fast and do heavy work asynchronously. Acknowledge with 200, enqueue the payload, and let a worker process it; that way a slow database write never causes an unnecessary re-queue.
app.post("/webhook/audd", (req, res) => {
try {
enqueue(req.body); // hand off to a background worker
res.status(200).send("OK");
} catch (err) {
console.error("Failed to enqueue callback:", err);
// Non-200 tells AudD to queue and retry delivery
res.status(500).send("Enqueue failed");
}
});
Process each queued payload inside a transaction so a partial failure rolls back cleanly:
async function processSongDetection(body) {
const tx = await db.beginTransaction();
try {
for (const match of body.result?.results ?? []) {
await saveSongDetection(body.result.radio_id, body.result.timestamp, match, tx);
}
await updateStreamStats(body.result?.radio_id, tx);
await tx.commit();
} catch (error) {
await tx.rollback();
throw error;
}
}
Advanced use cases
Copyright monitoring. Match detections against a protected catalog and flag plays on streams that aren’t authorized.
function checkCopyright(song, radioId) {
const match = getProtectedCatalog().find(
(s) => s.artist === song.artist && s.title === song.title
);
if (match && !isAuthorizedStream(radioId)) {
reportCopyrightViolation({ song, radioId, detectedAt: new Date() });
}
}
Playlist generation. Append detected tracks to a playlist, using the streaming links AudD returns when you request them.
Live analytics. Aggregate plays by artist and label and broadcast updates to connected dashboards over a websocket.
Recognizing your own audio
The public catalog covers commercial releases. To recognize station IDs, jingles, or unreleased tracks, upload them to a custom catalog (special access) and assign each an integer audio_id that comes back on future matches — so callbacks fire for your own audio too.
FAQ
What happens if my callback endpoint is temporarily down? If your server doesn’t return 200 OK, AudD queues the callback and delivers it later rather than dropping it. Acknowledge quickly and process asynchronously so a slow handler doesn’t cause needless re-queuing.
How do I handle duplicate detections?
A track spans multiple recognitions, so deduplicate in your handler: skip a match if the same title from the same radio_id was seen within your chosen window (often a few minutes).
Can I send results to more than one endpoint? Configure one callback URL per setup and fan out from your own handler to email, Slack, or downstream services.
When does AudD send the callback — at the start or end of a song? By default at the end of a song; you can configure it to notify at the start instead.
Can I receive results without exposing a public endpoint? Yes — use longpoll instead of a callback URL. Your backend holds a request open and AudD returns matches as they happen.
Conclusion
Callbacks turn AudD stream recognition into a real-time notification system. Register a stream with addStream, point AudD at your endpoint with setCallbackUrl, return 200 OK, and parse each payload leniently. Start with a single stream and a basic handler, then add deduplication, targeted alerts, and analytics as your monitoring grows.
Get a token at dashboard.audd.io and read the reference at docs.audd.io.
Related
Reading this as an AI agent? The raw Markdown is at articles/audd-api-webhooks-real-time-music-alerts.md, and the full index is /resources/llms.txt.
