Skip to content

Webhook API

yayuyokitano edited this page Sep 27, 2023 · 6 revisions

As of version 3.2.0, Web Scrobbler offers a webhook connection.
If a user adds a URL as a webhook, a request will be sent out on various events occurring within the extension.
IMPORTANT: Remember to return a 200 status code response on all requests, else Web Scrobbler will interpret that there has been an error while scrobbling.

The requests look like this:

{
  "eventName": string,
  "time": number, // timestamp of when the request was sent in milliseconds, only available starting with v3.3.0
  "data": {
    "song": Song, // the song currently playing or the first song in the scrobble request
    "songs"?: Song[], // All scrobbles in scrobble request. Only sent for "scrobble" event starting with v3.3.0
    "isLoved"?: boolean // only sent for `"loved"` event.
    "currentlyPlaying"?: boolean // Whether the song is currently playing, or if it is being scrobbled from cache. Only sent for "scrobble" event starting with v3.3.0.
  }
}

The song object mostly reflects the BaseSong type in the extension code, except it does not have the methods.

Here is an example of a song object
{
  "parsed": {
    "track": "Stand By Me",
    "artist": "ヤユヨ",
    "albumArtist": null,
    "album": null,
    "duration": 283,
    "uniqueID": "1pYg6pWwELg",
    "currentTime": 2,
    "isPlaying": true,
    "trackArt": null,
    "isPodcast": false,
    "originUrl": "https://youtu.be/1pYg6pWwELg",
    "isScrobblingAllowed": true
  },
  "processed": {
    "track": "Stand By Me",
    "artist": "ヤユヨ",
    "albumArtist": null,
    "duration": 283
  },
  "noRegex": {
    "track": "Stand By Me",
    "artist": "ヤユヨ",
    "albumArtist": null,
    "duration": null
  },
  "flags": {
    "isScrobbled": false,
    "isCorrectedByUser": false,
    "isRegexEditedByUser": {
      "track": false,
      "artist": false,
      "album": false,
      "albumArtist": false
    },
    "isAlbumFetched": true,
    "isValid": true,
    "isMarkedAsPlaying": true,
    "isSkipped": false,
    "isReplaying": false
  },
  "metadata": {
    "userloved": true,
    "startTimestamp": 1695212956,
    "label": "YouTube",
    "trackArtUrl": "https://coverartarchive.org/release/da3ec572-fb09-4424-8c9c-858dfde2c3d3/front-500",
    "artistUrl": "https://www.last.fm/music/%E3%83%A4%E3%83%A6%E3%83%A8",
    "trackUrl": "https://www.last.fm/music/%E3%83%A4%E3%83%A6%E3%83%A8/_/Stand+By+Me",
    "userPlayCount": 56
  },
  "connectorLabel": "YouTube",
  "controllerTabId": 1603388632
}

The time timestamp can be used to verify that the most recently received request was also the most recently sent one, as requests are not necessarily guaranteed to arrive in the same order that they were sent.

The types of eventName and when they are invoked are as follows:

  • "nowplaying": invoked when the extension tries to send a now playing request to scrobblers.
  • "scrobble": invoked when the extension tries to send a request to scrobble to scrobblers.
  • "loved": invoked when the extension tries to send a request to toggle the love status to scrobblers.
  • "paused": invoked when the isPlaying state is changed to false.
  • "resumedplaying": invoked when the isPlaying state is changed to true.

Tips for using the song object

In the song object:

  • The parsed object has data as parsed by the extension.
  • The processed object has data after processing, such as applying album matched from lastfm, applying edits, etc.
  • The noRegex object is not intended for use, but contains data prior to regex edits being applied.
  • The flags object contains various booleans. You probably mostly do not need to care about these as they are mostly for internal use.
  • The metadata object contains various useful data, some from the extension itself, some fetched from lastfm.

Sometimes, the same metadata may appear in several objects, and it can be hard to know which to prioritize. Here is a list of some of the functions used in web scrobbler to decide which piece of metadata to use.

/**
 * Get song artist.
 *
 * @returns Song artist
 */
getArtist(): string | null | undefined {
	return this.processed.artist || this.parsed.artist;
}

/**
 * Get song title.
 *
 * @returns Song title
 */
getTrack(): string | null | undefined {
	return this.processed.track || this.parsed.track;
}

/**
 * Get song album.
 *
 * @returns Song album
 */
getAlbum(): string | null | undefined {
	return this.processed.album || this.parsed.album;
}

/**
 * Return song's album artist (Optional)
 * @returns Album artist
 */
getAlbumArtist(): string | null | undefined {
	return this.processed.albumArtist || this.parsed.albumArtist;
}

/**
 * Returns song's processed or parsed duration in seconds.
 * Parsed duration (received from connector) is preferred.
 *
 * @returns Song duration
 */
getDuration(): number | null | undefined {
	return this.parsed.duration || this.processed.duration;
}

/**
 * Return the track art URL associated with the song.
 * Parsed track art (received from connector) is preferred.
 *
 * @returns Track art URL
 */
getTrackArt(): string | null {
	return this.parsed.trackArt || this.metadata.trackArtUrl || null;
}