Open source on GitHub

Live audio for Farcaster communities.White-label. Open. Yours.

Zuke is the white-label live audio surface for Farcaster communities. Powered by Juke. Graduated from the ZAO OS lab. Fork, drop in 6 env vars, deploy.

21

Features shipped

0

Spaces hosted

0

With recording

0

Webhook events

What you get

Listen surface

Public /listen page. Live now, scheduled next, recording shelf. No auth required to listen - SIWF only for participation.

Host + admin tools

/live/create UI plus admin API for programmatic space creation, end-space, recording handling, agent join.

Webhook receiver

HMAC-verified inbound webhooks (room.started, finished, participant.*, recording.ready). Idempotent. Auto-cast on recap.

Public status dashboard

/juke-status mirrors what you shipped + recent webhook deliveries + open asks. JSON + markdown + HTML, all in sync.

Why Zuke, not raw Juke

  • Your domain. Listeners land at audio.yourbrand.com, not juke.audio/space/xyz. Cast unfurls show your card.
  • Your database. All space metadata, participant counts, recordings land in your Supabase - queryable from your existing community tooling.
  • Your integrations. Recap casts from your own community account, custom CTAs on the live page, agent participants tied to your accounts.
  • No infra. Juke runs the LiveKit cluster, the iOS app, the iframe. You ship a Vercel project + a Supabase + 6 env vars.

The build - every feature, newest first

Sourced from jukeIntegrationManifest. Every PR linked where one exists. Auto-updates as we ship.

  1. Consumer code for Juke developer reads + rate-limit observability

    2026-05-25

    Wraps Juke's PR #175 ship (2026-05-25): GET /v1/developer/spaces/{id} returns RoomDetailResponse (status + participants + recording in one call), GET /v1/developer/webhooks/{id} returns delivery health, DELETE /v1/developer/webhooks/{id} cleans up orphans (already existed). New helper at src/lib/spaces/juke-api-reads.ts surfaces all three behind one client + extracts X-Juke-Rate-Limit-Limit / Remaining / Reset from every response, logging a warn when remaining drops below 20% of the limit. Stale-room cron at /api/cron/juke-stale-rooms now uses GET /spaces/{id} as the authoritative source - only flips a row to ended when Juke confirms ended (or 404s), trusting Juke over our webhook timeline. Fallback to the older heuristic when JUKE_API_KEY is absent (local/preview). Admin route /api/juke/admin/delete-webhook wraps DELETE with an introspection-before-delete audit log.

  2. Recap cast on room.finished (ended_via host/api only)

    2026-05-25

    When a Juke space ends with ended_via in {host, api}, the webhook handler auto-casts a 'Just wrapped: {title}' message to /zao from @thezao via autoCastToZao. Embeds the /live/{id} URL so Farcaster unfurls the OG card. Skips silent idle-timeouts (ended_via=null) since there's nobody to recap to. The recording.ready handler still fires its own 'Recording up' follow-up cast independently when a recording is on - two-cast pattern is intentional so listeners get a re-engagement ping when the file lands.

  3. Host "End space" button on /live/{id} + admin end-space route

    2026-05-24

    Iframe Leave is a pure LiveKit room.disconnect() with anon: participant identity - no API call, so rooms we create via developer API stay alive until LiveKit's 300s empty-room timeout. EndJukeSpaceButton on /live/{id} (gated to host or admin via SSR session) calls POST /api/juke/admin/end-space which proxies to Juke's POST /v1/developer/spaces/{id}/end (Nicky's PR #174). On a 404 from Juke (endpoint not shipped yet, or cross-app room), the route falls back to flipping our local juke_spaces row to ended so /spaces stops showing dead rooms as Live. The webhook handler remains the source of truth for the canonical room.finished event - we do not pre-flip our DB on the success path. Two-step confirm pattern on the button prevents fat-finger ends.

  4. Webhook payload parser: event_type / data.room_id / event_id

    2026-05-24View PR

    parseWebhookEvent now reads Juke 2026-05-23 shape (event_type + event_id at top level, data.room_id for the space id) instead of the legacy event / type / data.id fields. Defensive aliases keep the older shape working. readParticipant accepts fid / participant_fid / user_fid / host_fid + display_name / displayName / username for human-or-agent identification. Result: webhooks no longer log "no space_id" and lifecycle updates apply.

  5. Register-webhook fix: Juke generates the HMAC secret, not us

    2026-05-24View PR

    Initial admin route POSTed { url, events, secret } and Juke returned 422 extra_forbidden on the secret field - Juke generates the secret server-side and returns it in the response. Route now POSTs { url, events } only, captures juke.secret from the response, returns it with an action_required instructing the admin to copy it into Vercel's JUKE_WEBHOOK_SECRET env. Server logs the registration with the secret redacted.

  6. Richer /juke-status: recent webhooks + recent spaces + code examples

    2026-05-24View PR

    Three new sections on the public dashboard. (1) Recent webhooks - last 15 events with type / space_id / age / processed-vs-failed pill. (2) Recent spaces - last 10 juke_spaces rows with status pill + time marker + participant count + recording link. (3) Code examples - 4 reference snippets matching production (create-space, embed, webhook verify, subscribe). Plus OG + Twitter card meta on the page itself, and recent_spaces + recent_events arrays added to /api/juke/status and /juke-integration.md.

  7. Admin route to register the Juke webhook server-side

    2026-05-24View PR

    POST /api/juke/admin/register-webhook calls Juke /v1/developer/webhooks from a Vercel context that already has JUKE_API_KEY loaded. Juke generates the HMAC secret server-side and returns it in the response; the admin caller copies it into the JUKE_WEBHOOK_SECRET env var (Production + Preview + Development) and redeploys. Admin-only.

  8. Recurring weekly Juke schedule script

    2026-05-23

    scripts/schedule-zao-recurring.ts pre-creates Juke spaces for ZAO's weekly events (fractal call, ZAOstock standups). Idempotent (dedupes against juke_spaces.scheduled_at +/- 30min). Safe to wire into a weekly cron.

  9. Unified /spaces Live tab - Juke spaces alongside Stream/100ms

    2026-05-23

    Browser-side juke_spaces query in parallel with the rooms query, realtime subscription on both tables, JukeLiveSection rendered above ZAO stages when active rows exist. Cards route to /live/{id} with a purple Juke accent.

  10. Juke as 3rd audio provider in the Go-Live modal

    2026-05-23

    HostRoomModal on /spaces now exposes Juke alongside Stream.io + 100ms. When picked, the modal collapses (mode/theme/gate/multistream hidden), POSTs /api/juke/space, redirects to /live/{spaceId}.

  11. Schedule-a-space UI on /live/create

    2026-05-23

    Operator form to pre-create Juke spaces with a real scheduled_at - threads through to Juke. Optional announceCast toggle. Pre-fills "1h from now, rounded up to the next half hour".

  12. Public /live index of ZAO Juke spaces

    2026-05-23

    Anyone can browse Live / Scheduled / Recent ZAO Juke spaces without auth. Each card routes to /live/{id} (keyless iframe). Includes a paste-link form for non-ZAO spaces.

  13. Public build-status surfaces for the Juke team

    2026-05-23

    Three mirrors of this manifest: /juke-status (HTML dashboard with live stats + architecture diagram), /api/juke/status (JSON, CORS open, X-ZAO-Juke-Status: v1 header), /juke-integration.md (llms.txt-style markdown). Single source of truth in jukeIntegrationManifest.ts.

  14. Auto-cast on recording.ready

    2026-05-23

    After persisting recording_url, the webhook handler posts a recap cast to /zao via the @thezao official account, embedding the Juke /live/{id} URL so the Juke OG image renders in the cast preview. Silently no-ops when the @thezao signer env is missing.

  15. Public /live/recordings shelf

    2026-05-23

    Lists ended Juke spaces with recording_url, most-recent first. Server-fetched from juke_spaces. Each card shows the Juke OG image + a "Listen to recording" CTA. Populated by the recording.ready webhook.

  16. "Open in Juke app" CTA

    2026-05-23

    jukeAppDeeplinkUrl(spaceId) returns juke.audio/space/{id}?open=app. Button on /live/{id} routes desktop visitors into the iOS app via universal link.

  17. OG image per space

    2026-05-23View PR

    generateMetadata pulls juke.audio/space/{id}/opengraph-image for Open Graph + Twitter card meta tags. Cast/X shares of /live/{id} render the Juke-branded card without ZAO having to render its own.

  18. ?audio=off second-screen mode

    2026-05-23View PR

    jukeEmbedUrl(spaceId, { audioOff: true }) returns the embed with audio disabled. UI offers a "Mute (second screen)" toggle on /live/{id}. Solves the laptop-alongside-iOS-app double-broadcast case.

  19. Inbound webhook consumer at /api/juke/webhooks

    2026-05-23View PR

    HMAC-SHA256 verifier for X-Juke-Signature: t={ts},v1={hex} over `{ts}.{body}`. 5-minute replay window. Idempotent via signature_hash unique constraint. Handlers cover room.started, room.finished, participant.joined, participant.left, recording.ready.

  20. Path B — server-side space creation via POST /v1/developer/spaces

    2026-05-22View PR

    Key-only auth (X-Juke-Api-Key), room owner derived from app.owner_fid. Admin-or-password gated route at /api/juke/space; /live/create web form. Persists juke_spaces row on success.

  21. Path A — keyless iframe at /live/{spaceId}

    2026-05-20View PR

    Public route that embeds juke.audio/embed/{id} with ZAO chrome. No API keys, anonymous listen by default, SIWF inside the iframe for participation.

Deploy your own

1

Fork the repo

github.com/ZAODEVZ/Zuke. MIT-style license. Next.js 16 + Supabase + Juke developer API.

git clone https://github.com/ZAODEVZ/Zuke.git
2

Provision Supabase + apply migrations

Create a Supabase project. Apply the two migration files in scripts/:

scripts/juke-spaces-migration.sql
scripts/juke-spaces-migration-2.sql
3

Set 6 env vars + register the webhook

Apply for a Juke developer key at juke.audio/developers. Then in Vercel:

NEXT_PUBLIC_SUPABASE_URL
SUPABASE_SERVICE_ROLE_KEY
JUKE_API_KEY
JUKE_WEBHOOK_SECRET   # filled by step-6 webhook register
ZUKE_ADMIN_PASSWORD
CRON_SECRET

Deploy. Hit POST /api/juke/admin/register-webhook with the admin cookie. Copy the returned whsec_ into JUKE_WEBHOOK_SECRET, redeploy.

Resources