# Video processing pipeline with Trigger.dev and Ittybit

Use Trigger.dev background jobs to orchestrate async video transcoding with Ittybit

When a user uploads a video, you don't want to block the request while transcoding runs. Trigger.dev handles the background orchestration -- creating an Ittybit task, waiting for it to finish, generating thumbnails, and notifying the user. The whole pipeline runs reliably in the background with built-in retries.

## Install dependencies

```bash
npm install @trigger.dev/sdk
```

Set your environment variables:

```bash
TRIGGER_SECRET_KEY=tr_dev_...
ITTYBIT_API_KEY=your_ittybit_api_key
```

## Define the pipeline task

This Trigger.dev task accepts a video URL and runs the full processing pipeline as a series of steps.

```typescript

export const processVideo = task({
  id: 'process-video',
  retry: { maxAttempts: 3 },
  run: async (payload: { videoUrl: string; userId: string }) => {
    const { videoUrl, userId } = payload;

    // Step 1: Create transcoding task
    const transcodeTask = await createIttybitTask({
      input: videoUrl,
      kind: 'video',
      options: {
        width: 1920,
        format: 'mp4',
        codec: 'h264',
        quality: 'high',
      },
    });

    // Step 2: Poll until transcoding completes
    const completedTask = await pollForCompletion(transcodeTask.id);

    // Step 3: Generate thumbnail from the transcoded video
    const thumbnailTask = await createIttybitTask({
      input: completedTask.output_url,
      kind: 'image',
      options: {
        start: 2,
        width: 640,
        format: 'webp',
      },
    });

    const completedThumbnail = await pollForCompletion(thumbnailTask.id);

    // Step 4: Update your database and notify the user
    await updateVideoRecord(userId, {
      videoUrl: completedTask.output_url,
      thumbnailUrl: completedThumbnail.output_url,
      status: 'ready',
    });

    await notifyUser(userId, 'Your video is ready.');

    return {
      videoUrl: completedTask.output_url,
      thumbnailUrl: completedThumbnail.output_url,
    };
  },
});
```

## Ittybit helper functions

These wrap the Ittybit Task API. `createIttybitTask` kicks off a processing job, and `pollForCompletion` waits for it to finish.

```typescript
async function createIttybitTask(body: {
  input: string;
  kind: string;
  options: Record<string, unknown>;
}) {
  const res = await fetch('https://api.ittybit.com/jobs', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.ITTYBIT_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  });

  if (!res.ok) {
    throw new Error(`Ittybit API error: ${res.status}`);
  }

  return res.json() as Promise<{
    id: string;
    status: string;
    output_url?: string;
  }>;
}

async function pollForCompletion(taskId: string, intervalMs = 5000, maxAttempts = 60) {
  for (let i = 0; i < maxAttempts; i++) {
    const res = await fetch(`https://api.ittybit.com/jobs/${taskId}`, {
      headers: {
        Authorization: `Bearer ${process.env.ITTYBIT_API_KEY}`,
      },
    });

    const task = (await res.json()) as {
      id: string;
      status: string;
      output_url?: string;
    };

    if (task.status === 'completed') {
      return task;
    }

    if (task.status === 'failed') {
      throw new Error(`Task ${taskId} failed`);
    }

    await new Promise((r) => setTimeout(r, intervalMs));
  }

  throw new Error(`Task ${taskId} timed out after polling`);
}
```

## Trigger the task from your API

When a user uploads a video, trigger the background task and return immediately.

<CodeGroup labels={["Next.js", "Express"]}>
```typescript

// Next.js API route
export async function POST(req: Request) {
const { videoUrl, userId } = await req.json();

const handle = await tasks.trigger<typeof processVideo>(
"process-video",
{ videoUrl, userId },
);

return Response.json({
message: "Processing started",
runId: handle.id,
});
}

````

```typescript

app.post("/upload", async (req, res) => {
  const { videoUrl, userId } = req.body;

  const handle = await tasks.trigger<typeof processVideo>(
    "process-video",
    { videoUrl, userId },
  );

  res.json({
    message: "Processing started",
    runId: handle.id,
  });
});
````

</CodeGroup>

## Use webhooks instead of polling

Polling works but wastes compute. For production, configure an Ittybit webhook and use Trigger.dev's `wait.for` to pause the task until the webhook fires.

```typescript

export const processVideoWithWebhooks = task({
  id: 'process-video-webhooks',
  retry: { maxAttempts: 3 },
  run: async (payload: { videoUrl: string; userId: string }) => {
    const { videoUrl, userId } = payload;

    // Create the transcode task
    const transcodeTask = await createIttybitTask({
      input: videoUrl,
      kind: 'video',
      options: {
        width: 1920,
        format: 'mp4',
        codec: 'h264',
        quality: 'high',
      },
    });

    // Wait for the webhook instead of polling
    const transcodeResult = await wait.for<{
      output_url: string;
    }>({
      id: `transcode-${transcodeTask.id}`,
      timeout: '30m',
    });

    // Generate thumbnail
    const thumbnailTask = await createIttybitTask({
      input: transcodeResult.output_url,
      kind: 'image',
      options: {
        start: 2,
        width: 640,
        format: 'webp',
      },
    });

    const thumbnailResult = await wait.for<{
      output_url: string;
    }>({
      id: `thumbnail-${thumbnailTask.id}`,
      timeout: '5m',
    });

    await updateVideoRecord(userId, {
      videoUrl: transcodeResult.output_url,
      thumbnailUrl: thumbnailResult.output_url,
      status: 'ready',
    });

    await notifyUser(userId, 'Your video is ready.');
  },
});
```

Then add a webhook endpoint that completes the wait token:

```typescript

// POST /api/webhooks/ittybit
export async function POST(req: Request) {
  const event = await req.json();

  if (event.type === 'job.succeeded') {
    await runs.completeWaitToken(`transcode-${event.task_id}`, { output_url: event.output_url });
  }

  return new Response('ok');
}
```

## See also

- [Trigger.dev docs](https://trigger.dev/docs)
- [Ittybit Task API reference](/reference/tasks)
- [Build a user upload pipeline](/guides/build-a-user-upload-pipeline)