The Stage provides your site with an id_token
. Your backend can use this token to obtain an access_token
that gives you access to Spotify web APIs. This method works on any arbitrary TLD your site is hosted on.
For security and privacy reasons, you must not persist the value of any token in any way:
There are two methods you can use to obtain an id_token
.
stageSDK.requestIDToken() .then(t => {idToken = t}, e => {showError(e)});
This function returns a Promise that may timeout after 5 seconds.
Set Auth Type to IDT
in Mission Control. When The Stage opens your site, it will append an ID token in the fragment portion of the URL, like https://your.site.url#id_token=xxxxx
, which you can extract.
idToken = window.location.hash .match( /#[.*&]?id_token=(?<idToken>.*)/i) ?.groups ?.idToken;
This id_token
has a TTL of about 10 minutes, but since it is generated when The Stage first loads and does not change during a session, it is possible the token will already be expired by the time your page loads.
idToken
Send the id_token
token to your backend during your site's initial load. You must use a POST request to prevent it from showing up in your request logs.
fetch('your/rest/api/id-token/consumer', { method: 'POST', headers: { 'Content-Type': 'application/json'}, body: JSON.stringify({ idToken }), }).then(...);
Your backend can call Spotify's Accounts service to trade the id_token
for an access_token
. Subsequent calls with the same id_token
will return different access_token
s.
fetch('https://accounts.spotify.com/api/token', { method: 'POST', headers: { // The POST body must be form-encoded per OAuth standards 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', }, body: { "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", "assertion": idTokenFromFrontend, "client_id": SECRET_CLIENT_ID, "client_secret": SECRET_CLIENT_SECRET, "scopes": OAUTH_SCOPES } });
This access_token
(along with your various secrets used to call Spotify APIs) must not be logged, and must not be given unencrypted to the client under any circumstances.
As mentioned above, this call will fail if the id_token
is expired. Consider implementing your site in such a way that this will result in your client requesting a new id_token
from the SDK and trying this call again.
Due to the constraints on storing or transmitting access_token
s (i.e. not doing that), you have two options for making subsequent API calls.
access_token
each time you make a call.access_token
using your own keys and return this to your frontend. Include it in subsequent requests to your backend, where you decrypt it and use it to make API calls.No access token
Your backend can now use the access_token
to call Spotify's Web API with the scopes you requested.
webApi = new SpotifyWebApi({ accessToken }); getMe = (await webApi.getMe()).body;
Set Auth Type to STT
in Mission Control to add authentication cookies to the session before loading your page. This feature is only available if your site is hosted on spotify.com
or spotify.net
.
When a user first loads The Stage, it opens accounts.spotify.com
, which applies authentication cookies and redirects to your site.
Call isRunningInTheStage()
to synchronously determine if your site is running within The Stage on a real Spotify client. If it is, you can call getTheStageSDK()
. This returns a Promise
that will resolve to the Stage SDK instance.
import { isRunningInTheStage, getTheStageSDK } from '@spotify-internal/the-stage-js-sdk'; function onTheStageLoaded(stageSDK) { // Your initialization logic goes here } if (isRunningInTheStage()) { getTheStageSDK().then(onTheStageLoaded); } else { // Not running in The Stage }
Some of your site's properties are configured in Mission Control. This admin interface is not yet available to our partners, so you will need to ask TIL to change the settings for you.
These settings apply to all users and can not be changed at runtime. If you are viewing this in The Stage, you can adjust the sliders in this section and tap Reload with these overrides to preview the effect they have on the gallery of demos on this page.
See the sections on Authentication.
The Has Audio setting pauses Spotify content before loading your site, which allows you to play your own audio. See the Audio Player (HTML) section for more info on that. For additional control over Spotify playback, see the section on Playback Control.
Legacy Sharing supports more platforms for direct sharing. The alternative (Story Sharing) only exposes a handful of platforms. should not be used for any new campaigns. See the section on Sharing.
The Hide Player setting causes The Stage to hide the Now Playing Bar before loading your site.
The back button is displayed in a bar above the WebView. With Legacy Back Behavior, this button immediately closes The Stage. Otherwise, it acts as a normal browser back button, closing The Stage only when there's no history left in the WebView.
Note: As of this writing, there is an issue where sites using STT authentication will have the authentication page in their back stack, which will cause a broken user experience. This can be mediated in JavaScript by intercepting the back event and calling stageSDK.requestClose()
.
See the section on Checkout.
The Android system is aggressive about cleaning up unused memory. When a user switches apps, background apps are sometimes killed. When this happens, your site's state will be lost. The same thing happens when the user navigates through your site (i.e. you link out to a playlist) and then returns via the Back button.
This switch controls the behavior of your site when that reload occurrs:
When this flag is false
, The Stage will make no attempt to restore previous state, and will restart from the beginning. Also, when linking out, The Stage will remove itself from the back stack, so users going back will skip your site entirely.
When this flag is true
, The Stage will restore the browsing history and current URL. Your site can take advantage of this to load itself into its previous state. For example, if the final page of your experience links to a playlist and has a share button, you may want to restore this "finished" state as this could result in additional share impressions.
See the section on Media Devices.
Reload this site, overriding the Mission Control settings to the values chosen in the sliders above. Please note that support for this button is hardcoded for this site specifically in client debug code. Your site can't do this.
Your site will be provided a Spotify URI (like spotify:presents:yourcampaign
) and a matching link on open.spotify.com (like https://open.spotify.com/presents/yourcampaign
).
Use the open.spotify.com link on webpages and social media.
Some older client apps do not support the :presents: schema. Use spotify:site:yourcampaign
instead if your campaign needs to support these clients.
Any query parameters appended to the link or Spotify URI like spotify:presents:yourcampaign?can_dance=1&can_jive=1
will be passed through to your site.
The Stage also automatically appends query parameters to your site:
utm_app_version
is the version of the Spotify app The Stage is running under.utm_session_id
is the unique client session ID that the Spotify client uses to log all events on our backend, including custom events you log with The Stage SDK (See the Events section for details). You may also use this to identify a session that has been reloaded.location.search
You can add one extra level of deep link within your site. For example, spotify:presents:yourcampaign:vinyl
will send the user to yourcampaign.example.com/vinyl
.
There is a known issue where links to The Stage that include an extra path are broken when passed through Google Ad Manager - even if the open.spotify.com
link is functioning normally. TIL is aware of this issue, but has not yet identified which system the issue is occuring in. In the mean time, we ask that you avoid using these types of links as the entrypoint for your Stage experience when linking to them from an ad. If you do, perform a full end-to-end test to ensure your ads actually click through to The Stage.
location.pathname
To link to other Spotify content from The Stage such as an artist, track, album, or playlist, see the section on Links.
The Stage supports ordinary <audio>
tags to play audio content hosted on your site.
<audio src="./test-audio.mp3" ... />
For legal reasons, you must not embed content from any third-party source or arbitrary content from the Spotify catalog. To play arbitrary content, see the section on Playback Control.
You may also use the Web Audio API for more advanced control over audio, but be aware that using this method, iOS may not play your audio if the phone is in Silent Mode. Test accordingly.
Be sure to add a hook that pauses your audio on window focus events. Otherwise, your audio may continue playing after the user navigates away from Spotify. Do not use ordinary window blur events here.
function updatePlayState() { if (windowHasFocus && ...) { myAudio.play(); } else { myAudio.pause(); } } stageSDK.onWindowFocusChanged( (hasFocus) => { windowHasFocus = hasFocus; updatePlayState(); });
windowHasFocus
If you are also using the Playback Control API, be sure to pause HTML audio before starting Spotify playback, and ensure Spotify playback is paused before playing your HTML audio. Not doing so could have undesirable effects.
function updatePlayState() { if (!spotifyIsPlayingAudio && ...) { myAudio.play(); } else { myAudio.pause(); } } stageSDK.playback.onPlaybackStatusChanged( playbackStatus => { spotifyIsPlayingAudio = ( playbackStatus === 'playing'); updatePlayState(); });
For all other use cases, the relevant setting in Mission Control will ensure the user's Spotify playback is paused before your site loads.
spotifyIsPlayingAudio
Users can update their Spotify Premium subscription within The Stage under specific circumstances:
spotify.com
If any of these are not true, stageSDK.checkout
will be undefined
.
At this time, the only thing that can be purchased is a Spotify Premium subscription.
Checkout must be "activated" before use. This occurs asynchronously and must be done once per session. This means if your page is reloaded, you may not need to activate a second time.
if (checkout?.isActive) { isActive = true; } else { checkout?.activate().then( () => {isActive = true}); }
isActive
Once activated, you can link (or redirect in JS) to a Premium checkout page. It will open in the native Checkout experience where the user can pay with a variety of methods including credit card, PayPal, and Google Pay.
Please note that the links here are a live demo that will actually bill you if you complete the checkout process.
This demo calls https://api.spotify.com/v1/users/user_id/playlists
, https://api.spotify.com/v1/recommendations
and https://api.spotify.com/v1/playlists/playlist_id/tracks
to create a playlist and add tracks to it, then navigates to the playlist.
When a user first loads The Stage, it opens accounts.spotify.com
, which applies authentication cookies and redirects to your site.
Playlists created this way may not be fully and immediately synced to the client app. This usually takes a few seconds, but could extend to a minute or longer - even if the user has a good network connection.
TODO...
sdkFunction
The Stage SDK exposes information about the client via the clientVersion
property.
Not running in The Stage
clientVersion.platform
will be either ios
, android
, or desktop
.
Avoid using version numbers as a proxy to determine which SDK features are available. The SDK is designed to make these features undefined
if the client does not support them. If you find yourself writing shims around behavior quirks in particular client versions, please ensure TIL is aware of them.
If you must compare versions directly, use the method clientVersion.isAtLeast(...)
. This method accepts a mapping of platforms to a minimum version to compare against. It returns a boolean or undefined
. For example:
isAtLeast({ android: '8.8.74.221', ios: '8.9.3' })
. Desktop was not specified in this example, so on Spotify for Desktop, this code would return undefined
.isAtLeast({ ios: '8.8.21', desktop: '0' })
. For a client with version 2.17.11
, this code would return true
on Desktop, false
on iOS, and undefined
on Android.Other methods to compare client version numbers are not supported and should be avoided.
The Stage has integrated tracking which requires a standardized event format that your site must use. Details on the event format can be found in our private documentation.
Many events, such as page loads, will be logged for you. The Stage uses the same infrastructure that the mobile client uses for analytics, so it is resilient to network issues.
For legal reasons, you must not use any 3rd-party tracking scripts, such as Google Analytics or Tag Manager.
Send custom analytics events via the Stage SDK:
stageSDK.sendEvent(category, action, label, value)
This function is modeled after Google Analytics' ga
function.
category
: Typically the object that was interacted with (e.g. 'Video')action
: The type of interaction (e.g. 'play')label
: Useful for categorizing events (e.g. 'Fall Campaign')value
: A value associated with the event (e.g. '42'). Note: Unlike ga
, this is a string value - not a number.The Stage supports a user uploading files via Spotify's native upload prompt. Your backend is responsible for storing user-uploaded content.
<input id="file-upload" type="file" accept="image/*" />
The Stage opens all links only according to the rules described below. Directives indicating a link should open in a new window (like <a target="_blank" ... >
or window.open(...)
) are ignored, and there is no way to force a link to open within The Stage either.
<a target="_blank" ... >
and window.open(...)
are non-functional on the Desktop client.See the section on Checkout.
Links to open.spotify.com
are treated as Spotify URIs and open within the Spotify app, but, outside The Stage.
Links within the same domain and some links to *.spotify.com/
or *.byspotify.com/
open within The Stage.
If legacy back behavior is disabled, visiting a page will add it to the back stack.
Any other links open in the user's web browser.
Some links may be intercepted by third party apps if they are installed.
Please ensure the URLs on your site, as well as the URLs driving traffic to your site have special characters escaped properly and are generally compliant with RFC 2396. If not, the links may not work properly and their behavior is not defined. This has caused platform-specific bugs in the past.
Please note that the pipe (Â |Â
) is not a valid URL character and "must be escaped in order to be properly represented within a URI." (RFC 2396, section 2.4.3) We have seen these appear in production intentionally from tracking partners and unintentially from advertising partners' email servers mangling URLs or attempting to add their own tracking for some reason.
This demo attempts to use <a download=...>
tags to download images.
This demo calls history.back()
The Spotify mobile clients display in a language according to the standard device settings used by most apps. This setting is exposed to your site within the Stage via the usual HTTP headers and window.navigator
.
Avoid using locale-specific urls like https://example.com/us/
or https://example.com/mx/
if possible.
Do not include a language selector as this will break the illusion of your site being a native app feature.
navigator.languages
Please note there are some subtle differences between the way desktop browsers and The Stage report locales. A desktop browser will typically report a longer list with a generic fallback like ['en-US', 'en']
. Mobile browsers don't do this.
A common issue reported during QA is that your site will inexplicably display in the wrong language, usually when testing Spanish. If a user's language preferences are set to Spanish (Nicaragua) and English (US), The Stage will report that user's languages as ['es-NI', 'en-US']
. If your site has translations for en-US and a different Spanish dialect like es-419, some localization libraries will determine the "best fit" for the user would be English. Note that in this case, the user does at least ostensibly speak English.
What you do about this depends heavily on the specifics of your situation.
First party experiences running in The Stage can use the Navigator.mediaDevices
API to obtain access to the user's camera and microphone data streams.
For legal reasons, and due to the sensitive nature of privacy around this type of access, this must not be used for advertiser / sponsored Stage experiences.
You must explicitly enable media devices in Mission Control for your experience or the permission will be automatically rejected.
Before calling the API and triggering a permission dialog, you must display text to the user that clearly explains what permission(s) will be requested and why, providing a choice to opt-out. If the user opts out, do not attempt to use the API. Consider whether the permission is strictly necessary and, if not, offer a degraded experience so privacy-conscious users aren't completely barred from the experience.
Most experiences won't need to use this. If you only need to pause the user's audio when your site loads, simply enable the relevant setting in Mission Control.
If the client supports playback controls, the Stage SDK will include them at stageSDK.playback
. (It will be undefined if not.)
Avoid playing content if the user doesn't have Spotify Premium. While some functionality will work as expected, Spotify will refuse to play specific songs and unrelated ads may interrupt your content. That is - this API merely reflects the playback controls existing in the app. It does not give your site special privileges to bypass restrictions on the Free experience.
These controls should be self-explanatory:
playback?.requestSkipToPrevious();
playback?.requestResume();
playback?.requestPause();
playback?.requestSkipToNext();
Play a track by passing its full Spotify URI:
uri = 'spotify:track:3B7fVNn9gquWqOeZiv8n1V'; playback?.playTrack(uri);
Playing non-track URIs is not supported at this time and may cause undefined behavior.
Subscribe to playback status changes to receive updates. Events can be triggered by your calls to the SDK, by user interaction (i.e. tapping the Pause button on the Now Playing Bar), or by other means (i.e. a song finishes playing).
playback?.onPlaybackStatusChanged( (status, trackUri) => { playbackStatus = status; nowPlayingTrackUri = trackUri; });
playbackStatus
nowPlayingTrackUri
The Now Playing Bar must be visible whenever Spotify content is playing.
If you just want to show the Now Playing Bar at all times, you can disable the "Hide Now Playing Bar" setting in Mission Control, and you do not need to use this API.
Code examples are forthcoming when this becomes available in the SDK.
These demos subscribe to devicemotion and deviceorientation events on the window
. These APIs function in exactly the same way on The Stage as they do on normal websites.
For legal and privacy reasons, you must not store or transmit any raw position or motion data from the device.
Uses accelerationIncludingGravity
to show a free-floating orb. The red side will appear on top and the white side will appear on the bottom.
Uses rotationRate
to show three interconnected rings indicating the three axes of motion: alpha
(horizontal when the phone is in portrait), beta
(vertical when the phone is in portrait), and gamma
(twisting the screen like a steering wheel). By turning the phone along a particular axis, you should see the corresponding ring move in the opposite direction, as if it has inertia.
Uses orientation events to show a white arrow that will maintain its orientation in physical space. The anchor points perpendicular to the ground at an arbitrary compass heading. On iOS, a second arrow is displayed facing north if that data is available. The outer rings are an approximation of the individual alpha
(compass offset from anchor), beta
, and gamma
angles but mainly exist to help the visual illusion of 3d space.
These caveats are (mostly) not specific to The Stage, but are enumerated here for convenience.
DeviceMotionEvent.requestPermission()
to obtain permission. The Stage will grant you this permission without asking or notifying the user. Older versions of the Spotify app will reject the permission, so you will need to handle this case.z
value will be about +9.8
on Android and -9.8
on iOS. You can normalize these by calling something like [x, y, z] = [-x, -y, -z]
on one of the two platforms.window.localStorage
API.There are technical and non-technical issues with both, making them non-viable for use in The Stage.
The localPrefs
API can be used to store short JSON-serializable data locally on the device.
By default, you do not have permission to use this API for any purpose. Please ensure you have consulted with ODPO and have permission to store the specific data you plan to store in the manner described here. [ Advertising partners: Expect a "no". ] Note the following critera when determining if this API is acceptable:
This API is not suitable for:
# Read data console.log(stageSDK.localPrefs?.value?.timestamp); # Write data stageSDK.localPrefs?.set({timestamp: Date.now().toString()}); # Clear data stageSDK.localPrefs?.set(undefined);
pushed
stageSDK.localPrefs?.value
You may store data in the Storage
object returned by getClientSessionStorage()
. Unlike the previous API, this does not require any privacy approval as the data is ephemeral. On iOS, this will return window.sessionStorage
. On Android, the standard object is broken so this will return a custom shim that implements Storage
.
getClientSessionStorage().getItem('timestamp')
The Stage can play video content that is hosted on your site.
<video id="video-player" src="/test-video.mp4" controls > <track kind="captions" /> </video>
For legal reasons, you must not embed content from any third-party source (like YouTube or Facebook) or arbitrary content from the Spotify catalog.
To show the video in fullscreen mode, call (myVideo.requestFullscreen || myVideo.webkitEnterFullscreen)()
On most Android devices, the standard Vibrate API is available. For iOS devices, the Stage SDK includes a shim that approximates the same functionality. For completeness, we recommend calling both with stageSDK?.vibrate(pattern) ?? window?.navigator.vibrate(pattern)
.
Built with HTML5
Classic falling blocks game. Move and rotate pieces to clear lines and score points.
Built with HTML5
Swap adjacent tiles to match 3 or more of the same color in a row or column.
Built with HTML5
Press the correct piano key as it appears. Beat the timer! Each milestone increases the challenge.
Built with HTML5
Guess the song title from a fun fact. Each wrong guess reveals a letter. You have 5 chances!
Built with HTML5
Jump over music notes and holes, collect upgrades, and see how far you can go! Upgrades include double jump, speed boost, and flying.
The setDebugLogger()
function allows a site to attach a listener on the raw events being passed between the native Spotify client and the SDK. This is generally only useful for debugging the SDK itself.
Please note that message content may change arbitrarily and without warning between client releases. You must not use this function in any production code.
This experiment tests the persistent storage mechanisms technically available to the client.
generated
cookie
device local prefs
local storage
native client session storage
session storage
This experiment tests what circumstances will cause the native clients to kill a WebView in the background.
This experiment tests the Encore `AudioPlayer` component
This experiment tests a custom audio player component