Developer documentation

Private viewer grants

An existing server-signed access path, separate from owner API keys and browser sign-in.

Configure private access

  1. Select Private under Access in the owner publishing panel.
  2. Save draft to generate the experience signing secret if none exists.
  3. Copy the one-time Viewer signing secret display into your server secret storage.
  4. Publish the experience after configuring its allowed domains.
  5. Authenticate the viewer on your own server.
  6. Authorize that viewer to access the selected experience before issuing a grant.

If the secret is lost, Rotate secret creates and displays a replacement. There is no browser-readable secret lookup. This signing secret is not an account API key.

Grant format

The payload contains experience_id, viewer_id, exp and nonce. Encode its UTF-8 JSON as base64url, sign that encoded string with HMAC-SHA256, and append the base64url signature after a dot.

FieldConstraint
experience_idThe exact experience UUID.
viewer_idStable, nonblank viewer identifier; at most 200 characters. Choose an opaque identifier instead of personal details.
expUnix seconds, future expiry no more than five minutes ahead.
nonceA fresh unique value of 16 to 128 URL-safe letters, digits, underscores or hyphens.
Server-only signing helper, not a shipped SDK
import { createHmac, randomBytes } from 'node:crypto';

export function createViewerGrant(experienceId, viewerId, signingSecret) {
  const payload = {
    experience_id: experienceId,
    viewer_id: viewerId,
    exp: Math.floor(Date.now() / 1000) + 240,
    nonce: randomBytes(16).toString('base64url'),
  };
  const encoded = Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url');
  const signature = createHmac('sha256', signingSecret)
    .update(encoded)
    .digest('base64url');
  return encoded + '.' + signature;
}

The helper does not authenticate a viewer or authorize a video. Your server must do those checks before signing and must never return its signing secret.

Deliver and rotate

Deliver only a fresh grant to the authorized host page, then pass it through the wrapper’s grant option. The wrapper transmits it with validated host:init, never in the iframe URL.

Do not copy a real grant into a reusable snippet. Issue a new nonce for a new redemption. The shared production store claims each nonce once for the grant’s entire lifetime. Expired grants are refused. Do not reuse a grant.

The runtime handles access-token and refresh-token renewal after redemption. The same experience and stable viewer identifier resolve to the same identified viewer session.

Rotating the signing secret invalidates unredeemed grants signed with the previous secret. It does not independently erase existing viewer sessions. Use Revoke to disable the experience.

Grant refusal codes