> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hooked.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Scrolling Video Example

> Record a website scrolling as a video

## Overview

A scrolling video records a website as it scrolls: Hooked opens the page, scrolls it the way you set, and turns the capture into a video. There is no avatar or voice (background music only if you pass a `musicId`), which makes it a clean screen recording to use as B-roll, as a product walkthrough, or as the background of another video.

Every request needs `scrollingVideoSettings.websiteUrl`, a public `http(s)` URL.

## Quick Example

```javascript theme={null}
const response = await fetch('https://api.hooked.so/v1/project/create/scrolling-video', {
  method: 'POST',
  headers: {
    'x-api-key': process.env.HOOKED_API_KEY,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    scrollingVideoSettings: {
      websiteUrl: 'https://yoursite.com'
    },
    webhook: 'https://yoursite.com/webhook?token=YOUR_SECRET'
  })
});

const data = await response.json();
console.log('Project ID:', data.data.projectId);
```

With only the URL you get a 30-second vertical (9:16) video that scrolls down every 3 seconds and jumps back to the top when it reaches the bottom. The create call answers with the `projectId`; poll `GET /v1/project/{projectId}` or pass a `webhook` to get the finished video.

## Custom Scrolling

```javascript theme={null}
const createScrollingVideo = async () => {
  const response = await fetch('https://api.hooked.so/v1/project/create/scrolling-video', {
    method: 'POST',
    headers: {
      'x-api-key': process.env.HOOKED_API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: 'Landing Page Walkthrough',
      scrollingVideoSettings: {
        websiteUrl: 'https://yoursite.com/pricing',
        scrollInterval: 2,
        scrollDuration: 1200,
        scrollMode: 'once',
        targetDuration: 20
      },
      videoSettings: {
        aspectRatio: 'ratio_16_9',
        content: {
          brandingLogo: {
            enabled: true,
            url: 'https://yoursite.com/images/logo.png',
            position: 'bottom-right',
            size: 100
          }
        }
      },
      webhook: 'https://yoursite.com/webhook?token=YOUR_SECRET',
      metadata: { page: 'pricing' }
    })
  });

  return await response.json();
};
```

## Parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `scrollingVideoSettings.websiteUrl` | string | Yes | The page to record (max 500 characters) |
| `scrollingVideoSettings.scrollInterval` | number | No | Seconds the page rests between scrolls, 1 to 10. Default 3 |
| `scrollingVideoSettings.scrollDuration` | number | No | Milliseconds each scroll takes, 500 to 5,000. Default 1,500 |
| `scrollingVideoSettings.scrollMode` | string | No | How it scrolls (see below). Default `restart` |
| `scrollingVideoSettings.targetDuration` | number | No | Length of the video in seconds, 5 to 120. Default 30 |
| `videoSettings.aspectRatio` | string | No | `ratio_9_16` (default), `ratio_16_9` or `ratio_1_1` |
| `videoSettings.content.brandingLogo` | object | No | Your logo over the video: `{ enabled, url, position, size }` |
| `name` | string | No | Project name (max 100 characters) |
| `webhook` | string | No | HTTPS URL called when the video is ready or the project fails |
| `metadata` | object | No | Your own data (max 5KB), sent back in the webhook |

## Scroll Modes

| `scrollMode` | What it does |
| - | - |
| `restart` | Scrolls down and jumps back to the top when it reaches the bottom (default) |
| `loop` | Scrolls down, then back up, then down again |
| `once` | Scrolls down once and stays at the bottom |
| `fixed` | Does not scroll: records the first screen of the page |

<Note>
  Managed teams pay 13 credits plus 4 per started 30 seconds of `targetDuration`. Teams in bring-your-own-keys mode are not charged credits for generation, but need their own OpenRouter key in Settings → AI keys. Without it the call answers `402` with `code: "missing_credentials"` and the `missingProviders` list, before anything is created or charged. To retry a create call after a timeout or 5xx, send it with an [`Idempotency-Key`](/guides/idempotency): the same key never creates or charges twice.
</Note>

## Tips for Scrolling Videos

<Tip>**Public pages only**: the page must load without a login</Tip>
<Tip>**Match the length to the page**: a short landing page suits `once` with a short `targetDuration`; a long one suits `restart` or `loop`</Tip>
<Tip>**Slower for reading**: a longer `scrollInterval` gives viewers time to read each section</Tip>
<Tip>**Pick the shape for where it goes**: `ratio_9_16` for TikTok, Reels and Shorts, `ratio_16_9` for YouTube and websites</Tip>

## Use Cases

* **Product walkthroughs**: Show a landing page or pricing page in motion
* **B-roll**: A screen recording to use in UGC Studio app demos and other videos
* **Portfolio showcases**: Present websites you built
* **App demos**: Record a web app's public pages

## Handling the Webhook Response

Webhooks are signed: check the `Hooked-Signature` header as shown in [Webhooks](/guides/webhooks#verify-the-signature).

```javascript theme={null}
// Express.js webhook handler
app.post('/webhook', (req, res) => {
  if (req.query.token !== process.env.HOOKED_WEBHOOK_TOKEN) return res.sendStatus(401);

  const { status, message, data } = req.body;
  const { videoId, projectId, url, metadata } = data;

  if (status === 'COMPLETED') {
    console.log('Scrolling video completed:', projectId, videoId);
    console.log('Download URL:', url);
    console.log('Page:', metadata?.page);
  } else if (status === 'FAILED') {
    // videoId is null when the project failed before it rendered
    console.error(videoId === null ? 'Project failed:' : 'Render failed:', message);
  }

  res.status(200).send('OK');
});
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.