Form-to-HLS streaming with 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
- A Typeform submission (or any webhook) sends the video URL to n8n
- n8n POSTs to Ittybit
/taskswithkind: "adaptive_video"andformat: "hls" - n8n pauses the workflow and waits for the Ittybit webhook callback
- When encoding finishes, n8n resumes and sends an email with the HLS playback link
Prerequisites
- An n8n instance (cloud or self-hosted)
- A Typeform 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.):
{
"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.
{
"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 }}"
}
}
}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"
}
}' 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:
{
"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:
{
"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:
{
"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 }}"
}
}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"
}
}' See also
- Create HLS streams — HLS encoding basics and when to use adaptive streaming
- Slack media bot with n8n — another n8n + Ittybit integration pattern
- Build a user upload pipeline — multi-task processing for uploads
- n8n HTTP Request node docs