Guild Embeds
Put a guild-owned Jukie library inside an authorized website or application. Jukie keeps media access scoped to one guild, measures active playback, and can send signed listening events back to your server.
How it works
- Your server authenticates the listener.Never place the shared signing secret in browser code.
- Your server creates a short-lived player token.The token identifies the external user, guild library, scopes, and session.
- Your page embeds the Jukie player.Jukie validates the token before listing or streaming any track.
- Jukie reports measured listening.Optional signed webhooks are idempotent and safe to retry.
Jukie configuration
Configure approved guilds, parent origins, and the optional listening webhook in Jukie's protected environment.
JUKIE_INTEGRATION_SHARED_SECRET=replace-with-a-long-random-secret
JUKIE_INTEGRATION_GUILD_IDS=123456789012345678
JUKIE_INTEGRATION_EMBED_ORIGINS=https://community.example
JUKIE_LISTENING_WEBHOOK_URL=https://community.example/api/jukie/listening
Create a signed token
Tokens use URL-safe base64 JSON and an HMAC-SHA256 signature. Generate a new jti for every player session and keep expiry short.
import base64, hashlib, hmac, json, secrets, time
now = int(time.time())
claims = {
"aud": "jukie-player",
"iss": "your-app",
"sub": "your-listener-id",
"guild_id": "123456789012345678",
"jti": secrets.token_urlsafe(24),
"iat": now,
"exp": now + 3600,
"scopes": ["player", "listening", "timeline_cues"],
"theme": "dark",
"library_label": "Community Radio",
"parent_origin": "https://community.example",
}
payload = base64.urlsafe_b64encode(
json.dumps(claims, separators=(",", ":"), sort_keys=True).encode()
).decode().rstrip("=")
signature = base64.urlsafe_b64encode(
hmac.new(SHARED_SECRET.encode(), payload.encode(), hashlib.sha256).digest()
).decode().rstrip("=")
token = f"{payload}.{signature}"
subYour stable listener identifier.guild_idThe single Jukie library this session may access.jtiA unique player session identifier.parent_originThe exact origin allowed to embed the player and receive player messages. It must also be listed in JUKIE_INTEGRATION_EMBED_ORIGINS.scopesMust contain player; add listening for progress and timeline_cues for real-time cue messages.themedark, rose, or light.Embed the player
Return the final URL from your authenticated backend, then assign it directly to the iframe. Do not place a protected redirect inside the frame.
<iframe
title="Community audio player"
src="https://jukie.app/integrations/v1/player?token=SIGNED_TOKEN&compact=1&shuffle=1&autoselect=1&embedded=1"
allow="autoplay"
loading="lazy">
</iframe>
The player is responsive and supports native audio controls, track selection, shuffle, byte-range streaming, and the signed theme supplied by your server.
compact=1Renders only the docked audio surface so the host application can provide its own chrome.autoselect=1Selects an initial track when the iframe loads. Browsers still require a user gesture before sound can start.shuffle=1Chooses the initial and variant tracks randomly instead of starting from the first visible item.embedded=1Marks the session as an embedded host integration. It is safe to include with current players and reserved for embed-specific behavior.Parent/player messages
Hosts can control the embedded iframe with postMessage. Always send commands to https://jukie.app, and always validate event.origin on messages received from Jukie.
const frame = document.querySelector("#jukie-player");
const JUKIE_ORIGIN = "https://jukie.app";
frame.contentWindow.postMessage(
{type: "jukie.control", action: "play"},
JUKIE_ORIGIN
);
playSelects a track if needed and attempts playback.pausePauses the current audio element.nextMoves to the next visible track group and plays it.previousMoves to the previous visible track group and plays it.Jukie sends jukie.player-status to the signed parent_origin. Current states include ready, selected, playing, paused, ended, blocked, and empty. The message may include a title.
window.addEventListener("message", event => {
if (event.origin !== "https://jukie.app") return;
if (event.data?.type === "jukie.player-status") {
console.log(event.data.state, event.data.title);
}
});
With timeline_cues, the player sends jukie.timeline-cue messages to the signed parent_origin as its audio clock crosses each neutral cue. Use this channel for time-sensitive integrations such as toy, light, visual, or breathing cues; do not wait for listening-credit webhooks.
{
"type": "jukie.timeline-cue",
"event_id": "session-id:track-id:cue-id",
"track_id": 42,
"position_ms": 15000,
"cue": {"id": "pulse-1", "offset_ms": 15000}
}
Media sources
Normal paid guilds use isolated hosted storage and retain uploads, subscription quotas, playlists, and Discord controls. Trusted self-hosted installations may additionally map a guild to an existing server folder.
JUKIE_GUILD_MEDIA_PATHS=123456789012345678=/srv/audio/community
Supported audio is indexed without copying it. Matching JSON sidecars may provide neutral audio_file and length_ms metadata; provider-specific fields are ignored. Remote services should be implemented as media-source adapters only when their supported API and licensing permit it.
Listening webhooks
When configured, Jukie sends compact JSON events signed over the exact request body. Verify X-Jukie-Signature with HMAC-SHA256 before accepting an event.
{
"provider": "jukie",
"external_user_id": "external-listener-id",
"event_id": "session-id:heartbeat-id",
"seconds": 15,
"track_id": "42"
}
Store event_id uniquely so retries cannot grant credit twice. Jukie measures heartbeat intervals server-side; consuming applications should never trust browser-submitted elapsed time.
Security checklist
- Authenticate listeners before issuing tokens.
- Use an unpredictable secret and rotate it deliberately.
- Allow only the guilds and parent origins your integration needs.
- Keep token lifetimes short and provide a visible disconnect control.
- Verify webhook signatures with a constant-time comparison.
- Reject expired, forged, replayed, or duplicate events.