Webhooks offer a versatile and dynamic approach to access control for both assets and livestreams. By setting up a webhook, you can validate access requests on the fly, allowing for real-time, context-sensitive decisions.

When a user requests access to content, the webhook you’ve specified will be called. This webhook can then decide whether to grant or deny access based on custom logic, such as user authentication, subscription status, or any other criteria.

This guide is written for developers using @livepeer/react in a React application.

The example below uses webhook to create and watch a gated content.

Create an Access Control Webhook Handler

Set up an endpoint (e.g., https://yourdomain.com/api/check-access) to handle the logic for granting or denying access to your assets. This endpoint should accept a POST request with a JSON payload containing the access key and webhook context.

This is an example of a payload this endpoint would receive:

POST /api/check-access
{
  "accessKey": "your-access-key",
  "context": {
    "assetId": "abcd1234",
    "userId": "user5678"
  },
  "timestamp": 1680530722502
}

The payload in context is defined by your application.

Register an Access Control Webhook

Use the Livepeer Studio dashboard to create a new webhook with the type playback.accessControl and specify the URL of your access control endpoint.

You can then use the ID of the webhook in the next step.

Create a content with a playback policy webhook

For assets

Create an asset with a playback policy webhook, passing the ID of the webhook you created in the previous step.

accessControl.tsx
import { useCreateAsset } from "@livepeer/react";

import { useCallback, useState, useMemo } from "react";
import { useDropzone } from "react-dropzone";

export const CreateAndViewAsset = () => {
  const [video, setVideo] = useState<File | undefined>();
  const {
    mutate: createAsset,
    data: asset,
    status: createStatus,
    progress,
    error: createError,
  } = useCreateAsset(
    video
      ? {
          sources: [
            {
              name: video.name,
              file: video,
              playbackPolicy: {
                type: "webhook",
                // This is the id of the webhook you created in step 2
                webhookId: "<webhook_id>",
                webhookContext: {
                  // This is the context you want to pass to your webhook
                  // It can be anything you want, and it will be passed back to your webhook
                },
              },
            },
          ] as const,
        }
      : null,
  );

  const onDrop = useCallback(async (acceptedFiles: File[]) => {
    if (acceptedFiles && acceptedFiles.length > 0 && acceptedFiles?.[0]) {
      setVideo(acceptedFiles[0]);
    }
  }, []);

  const { getRootProps, getInputProps } = useDropzone({
    accept: {
      "video/*": ["*.mp4"],
    },
    maxFiles: 1,
    onDrop,
  });

  const progressFormatted = useMemo(
    () =>
      progress?.[0].phase === "failed"
        ? "Failed to process video."
        : progress?.[0].phase === "waiting"
        ? "Waiting"
        : progress?.[0].phase === "uploading"
        ? `Uploading: ${Math.round(progress?.[0]?.progress * 100)}%`
        : progress?.[0].phase === "processing"
        ? `Processing: ${Math.round(progress?.[0].progress * 100)}%`
        : null,
    [progress],
  );

  return (
    <>
      <div {...getRootProps()}>
        <input {...getInputProps()} />
        <p>Drag and drop or browse files</p>
      </div>

      {createError?.message && <p>{createError.message}</p>}

      {video ? <p>{video.name}</p> : <p>Select a video file to upload.</p>}
      {progressFormatted && <p>{progressFormatted}</p>}

      <button
        onClick={() => {
          createAsset?.();
        }}
        disabled={!createAsset || createStatus === "loading"}
      >
        Upload
      </button>
    </>
  );
};

For livestreams

Create a stream with a playback policy webhook, passing the ID of the webhook you created in the previous step.

accessControl.tsx
import { useCreateStream, useStream } from "@livepeer/react";

import { useMemo, useState } from "react";

export const AccessControl = () => {
  const [streamName, setStreamName] = useState<string>("");
  const {
    mutate: createStream,
    data: createdStream,
    status,
  } = useCreateStream(
    streamName
      ? {
          name: streamName,
          playbackPolicy: {
            type: "webhook",
            // This is the id of the webhook you created in step 2
            webhookId: "<webhook_id>",
            webhookContext: {
              // This is the context you want to pass to your webhook
              // It can be anything you want, and it will be passed back to your webhook
            },
          },
        }
      : null,
  );

  const { data: stream } = useStream({
    streamId: createdStream?.id,
    refetchInterval: (stream) => (!stream?.isActive ? 5000 : false),
  });

  const isLoading = useMemo(() => status === "loading", [status]);

  return (
    <div>
      <input
        placeholder="Stream Name"
        onChange={(e) => setStreamName(e.target.value)}
      />

      <button
        onClick={() => {
          createStream?.();
        }}
        disabled={isLoading || !createStream || Boolean(stream)}
      >
        Create Gated Stream
      </button>
    </div>
  );
};

Configure the Player with an Access Key

If you are using the Livepeer React Player, you can use the accessKey prop to provide your custom access key, which is then passed to the webhook you created to verify that it is valid.

import { Player } from "@livepeer/react";

export const CreateAndViewAsset = () => {
  const accessKey = getAccessKeyForYourApplication();

  return <Player playbackId={playbackId} accessKey={accessKey} />;
};

If you are not using the player, you will need to manually append the access key to the m3u8 URL as a query parameter, similar to:

https://playback.livepeer.studio/asset/hls/abcd1234/index.m3u8?accessKey=your-access-key

Receive the Webhook from Livepeer

When a user attempts to access the content, Livepeer will call your access control endpoint with the access key and webhook context. Your endpoint should process the request and return a response indicating whether the stream access should be allowed or disallowed.

Here is an example request sent to your access control endpoint:

{
  "context": {
    // Same value from the `asset.playbackPolicy.webhookContext` field
  },
  "accessKey": "<access_key>" // this is exact value of the prop passed to the Player, or the query parameter
}

In your access control endpoint, implement the logic to verify the access key and decide whether to grant access to the content. If the access is allowed, return a 2XX response. Otherwise, the playback will be disallowed.