Skip to main content

Both access control methods require setting a playbackPolicy on stream or asset creation. Without a playback policy, content is publicly playable by anyone with the playback URL.

JWT vs webhook

JWT access control

Step 1 — Create a signing key

Create a signing key pair in Studio under Settings > Signing Keys, or via API:
const signingKey = await client.signingKey.create();
// signingKey.privateKey -- base64-encoded private key (save securely, not retrievable later)
// signingKey.publicKeyId -- ID to reference in the JWT
Store the private key in your secrets manager. It is shown only once.

Step 2 — Create a gated stream or asset

// Gated livestream
const stream = await client.stream.create({
  name: 'members-only-stream',
  playbackPolicy: { type: 'jwt' },
});

// Gated asset
const asset = await client.asset.create({
  name: 'premium-video.mp4',
  playbackPolicy: { type: 'jwt' },
});

Step 3 — Sign a JWT (server-side API route)

The JWT must be signed server-side. Never sign tokens in browser code.
// Next.js API route: /api/sign-jwt.ts
import { signAccessJwt } from '@livepeer/core/crypto';

export async function POST(req: Request) {
  const { playbackId, userId } = await req.json();

  // Add your own authorisation check here
  const isAuthorised = await checkUserSubscription(userId);
  if (!isAuthorised) {
    return Response.json({ error: 'Forbidden' }, { status: 403 });
  }

  const token = await signAccessJwt({
    privateKey: process.env.ACCESS_CONTROL_PRIVATE_KEY!,
    publicKey: process.env.NEXT_PUBLIC_ACCESS_CONTROL_PUBLIC_KEY!,
    issuer: 'https://yourapp.com',
    playbackId,
    expiration: '1h',      // token expires in 1 hour
    custom: { userId },    // optional custom claims
  });

  return Response.json({ token });
}

Step 4 — Pass the JWT to the player

import * as Player from '@livepeer/react/player';

// Fetch token from your API, then pass to player
export const GatedPlayer = ({ playbackId, jwt }: { playbackId: string; jwt: string }) => (
  <Player.Root src={`https://livepeercdn.studio/hls/${playbackId}/index.m3u8`} jwt={jwt}>
    <Player.Container>
      <Player.Video />
    </Player.Container>
  </Player.Root>
);
The Player sends the JWT in the Livepeer-Jwt header for WebRTC and HLS requests, and as a jwt query parameter for MP4. For custom players, add the header manually:
// HLS.js with JWT header
const hls = new Hls({
  xhrSetup: (xhr) => {
    xhr.setRequestHeader('Livepeer-Jwt', jwt);
  },
});

Webhook access control

Create a gated stream with webhook type and a reference to your webhook:
// First create a webhook
const webhook = await client.webhook.create({
  name: 'access-control',
  url: 'https://your-server.com/livepeer/access',
  events: ['playback.accessControl'],
});

// Then create a gated stream referencing the webhook
const stream = await client.stream.create({
  name: 'webhook-gated-stream',
  playbackPolicy: {
    type: 'webhook',
    webhookId: webhook.webhook.id,
    webhookContext: { plan: 'premium' }, // passed to your webhook
  },
});
Your webhook endpoint receives a POST with the viewer’s request details and must respond within 250 milliseconds:
// Your webhook handler
export async function POST(req: Request) {
  const payload = await req.json();
  const { playbackId, userId } = payload;

  const allowed = await checkAccess(userId, playbackId);

  // Respond 200 to allow, any other status to deny
  return Response.json({ allowed }, { status: allowed ? 200 : 403 });
}

Create a Livestream

Create streams with or without playback policies.

Webhooks

Full webhook setup, signature verification, and event types.
Last modified on April 7, 2026