# Form-to-HLS streaming with n8n and Ittybit

Convert form-submitted videos to HLS adaptive streams using n8n and Ittybit

A user submits a video through a Typeform. n8n picks up the submission, sends the file to Ittybit for adaptive HLS encoding, waits for the result, and emails the playback link. No backend code, no transcoding infrastructure -- just a webhook trigger, one HTTP request, and a wait node.

## How it works

1. A Typeform submission (or any webhook) sends the video URL to n8n
2. n8n POSTs to Ittybit `/tasks` with `kind: "adaptive_video"` and `format: "hls"`
3. n8n pauses the workflow and waits for the Ittybit webhook callback
4. When encoding finishes, n8n resumes and sends an email with the HLS playback link

## Prerequisites

- An [n8n](https://n8n.io) instance (cloud or self-hosted)
- A [Typeform](https://www.typeform.com) with a file upload question (or any form that produces a video URL)
- An Ittybit API key
- An email account configured in n8n (Gmail, SMTP, SendGrid, etc.)

## Build the n8n workflow

### 1. Typeform Trigger node

Create a new workflow. Add a **Typeform Trigger** node and connect your Typeform account. Select the form that collects video uploads.

The trigger fires on each submission. The incoming data includes the file upload URL and any other fields you defined (name, email, etc.):

```json
{
  "answers": [
    {
      "field": { "ref": "video_upload" },
      "file_url": "https://api.typeform.com/forms/.../files/abc123.mov",
      "type": "file_url"
    },
    {
      "field": { "ref": "email" },
      "email": "user@example.com",
      "type": "email"
    }
  ]
}
```

If you don't use Typeform, substitute a **Webhook** node. Any source that delivers a video URL works.

### 2. HTTP Request node: create the Ittybit task

This is the core of the workflow. Add an **HTTP Request** node that POSTs to Ittybit's Task API with the adaptive video config and a webhook callback URL pointing to the n8n Wait node.

<CodeGroup labels={["n8n node config", "curl"]}>
```json
{
  "method": "POST",
  "url": "https://api.ittybit.com/jobs",
  "authentication": "genericCredentialType",
  "genericAuthType": "httpHeaderAuth",
  "sendHeaders": true,
  "headerParameters": {
    "parameters": [
      { "name": "Authorization", "value": "Bearer {{ $credentials.ittybitApiKey }}" },
      { "name": "Content-Type", "value": "application/json" }
    ]
  },
  "sendBody": true,
  "specifyBody": "json",
  "jsonBody": {
    "input": "={{ $json.answers[0].file_url }}",
    "kind": "adaptive_video",
    "options": {
      "format": "hls"
    },
    "webhook_url": "={{ $node['Wait for Ittybit'].webhookUrl }}",
    "metadata": {
      "email": "={{ $json.answers[1].email }}"
    }
  }
}
```

```bash
curl -X POST https://api.ittybit.com/jobs \
  -H "Authorization: Bearer $ITTYBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "https://api.typeform.com/forms/.../files/abc123.mov",
    "kind": "adaptive_video",
    "options": {
      "format": "hls"
    },
    "webhook_url": "https://your-n8n.app/webhook-waiting/abc123",
    "metadata": {
      "email": "user@example.com"
    }
  }'
```

</CodeGroup>

The `webhook_url` points to the Wait node so Ittybit can resume the workflow when encoding completes. The `metadata` field passes through unchanged -- here it carries the submitter's email for the notification step.

### 3. Wait node: pause for webhook callback

Add a **Wait** node set to **Resume on webhook call**. This pauses the workflow until Ittybit POSTs back with the completed job.

When encoding finishes, Ittybit sends:

```json
{
  "id": "task_abc123",
  "status": "completed",
  "kind": "adaptive_video",
  "output": {
    "url": "https://cdn.ittybit.com/o/output/stream.m3u8"
  },
  "metadata": {
    "email": "user@example.com"
  }
}
```

### 4. IF node: check task status

Add an **IF** node to branch on `{{ $json.status }}`:

- **completed** -- continue to the email node
- **failed** -- send an error notification instead

### 5. Send Email node: deliver the playback link

Add a **Send Email** node (or Gmail / SendGrid node) with:

- **To:** `{{ $json.metadata.email }}`
- **Subject:** `Your video is ready to stream`
- **Body:**

```
Hi,

Your video has been converted to HLS adaptive streaming.

Playback URL: {{ $json.output.url }}

Paste this URL into any HLS-compatible player (hls.js, Video.js, Safari, VLC) to start streaming.
```

For the failure branch, send:

- **Subject:** `Video processing failed`
- **Body:** `Your video (task {{ $json.id }}) could not be processed. Please try uploading again or contact support.`

## Full workflow JSON

Import this directly into n8n via **Settings > Import from JSON**:

```json
{
  "name": "Form to HLS Streaming",
  "nodes": [
    {
      "parameters": { "formId": "your_typeform_id" },
      "name": "Typeform Trigger",
      "type": "n8n-nodes-base.typeformTrigger",
      "position": [240, 300]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.ittybit.com/jobs",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "Authorization", "value": "Bearer {{ $credentials.ittybitApiKey }}" },
            { "name": "Content-Type", "value": "application/json" }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={ \"input\": \"{{ $json.answers[0].file_url }}\", \"kind\": \"adaptive_video\", \"options\": { \"format\": \"hls\" }, \"webhook_url\": \"{{ $node['Wait for Ittybit'].webhookUrl }}\", \"metadata\": { \"email\": \"{{ $json.answers[1].email }}\" } }"
      },
      "name": "Create HLS Task",
      "type": "n8n-nodes-base.httpRequest",
      "position": [460, 300]
    },
    {
      "parameters": { "resume": "webhook" },
      "name": "Wait for Ittybit",
      "type": "n8n-nodes-base.wait",
      "position": [680, 300]
    },
    {
      "parameters": {
        "conditions": { "string": [{ "value1": "={{ $json.status }}", "value2": "completed" }] }
      },
      "name": "Task Completed?",
      "type": "n8n-nodes-base.if",
      "position": [900, 300]
    },
    {
      "parameters": {
        "fromEmail": "noreply@yourapp.com",
        "toEmail": "={{ $json.metadata.email }}",
        "subject": "Your video is ready to stream",
        "text": "=Your video has been converted to HLS adaptive streaming.\n\nPlayback URL: {{ $json.output.url }}\n\nPaste this URL into any HLS-compatible player to start streaming."
      },
      "name": "Send Playback Link",
      "type": "n8n-nodes-base.emailSend",
      "position": [1120, 260]
    },
    {
      "parameters": {
        "fromEmail": "noreply@yourapp.com",
        "toEmail": "={{ $json.metadata.email }}",
        "subject": "Video processing failed",
        "text": "=Your video (task {{ $json.id }}) could not be processed. Please try uploading again or contact support."
      },
      "name": "Send Error Email",
      "type": "n8n-nodes-base.emailSend",
      "position": [1120, 400]
    }
  ],
  "connections": {
    "Typeform Trigger": { "main": [[{ "node": "Create HLS Task", "type": "main", "index": 0 }]] },
    "Create HLS Task": { "main": [[{ "node": "Wait for Ittybit", "type": "main", "index": 0 }]] },
    "Wait for Ittybit": { "main": [[{ "node": "Task Completed?", "type": "main", "index": 0 }]] },
    "Task Completed?": {
      "main": [
        [{ "node": "Send Playback Link", "type": "main", "index": 0 }],
        [{ "node": "Send Error Email", "type": "main", "index": 0 }]
      ]
    }
  }
}
```

## Customizing the HLS output

You can pass additional options to control the adaptive encoding. For example, to limit the maximum resolution or set a specific codec:

<CodeGroup labels={["n8n node config", "curl"]}>
```json
{
  "input": "={{ $json.answers[0].file_url }}",
  "kind": "adaptive_video",
  "options": {
    "format": "hls",
    "width": 1920,
    "codec": "h264",
    "quality": "high"
  },
  "webhook_url": "={{ $node['Wait for Ittybit'].webhookUrl }}",
  "metadata": {
    "email": "={{ $json.answers[1].email }}"
  }
}
```

```bash
curl -X POST https://api.ittybit.com/jobs \
  -H "Authorization: Bearer $ITTYBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "https://api.typeform.com/forms/.../files/abc123.mov",
    "kind": "adaptive_video",
    "options": {
      "format": "hls",
      "width": 1920,
      "codec": "h264",
      "quality": "high"
    },
    "webhook_url": "https://your-n8n.app/webhook-waiting/abc123",
    "metadata": {
      "email": "user@example.com"
    }
  }'
```

</CodeGroup>

## See also

- [Create HLS streams](/guides/create-hls-streams) -- HLS encoding basics and when to use adaptive streaming
- [Slack media bot with n8n](/guides/slack-media-bot-with-n8n) -- another n8n + Ittybit integration pattern
- [Build a user upload pipeline](/guides/build-a-user-upload-pipeline) -- multi-task processing for uploads
- [n8n HTTP Request node docs](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest/)