This workflow corresponds to n8n.io template #9505 — we link there as the canonical source.
This workflow follows the Google Drive → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "UGC \u2013 Screenshot\u2192Gemini\u2192Sora2\u2192VBR (Vertical)",
"nodes": [
{
"id": "webhook",
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"position": [
-1520,
40
],
"parameters": {
"path": "ugc-screenshot-video",
"options": {
"responseData": "allEntries"
},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 1.1
},
{
"id": "manual-trigger",
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-1520,
280
],
"parameters": {},
"typeVersion": 1
},
{
"id": "sticky-overview",
"name": "\ud83d\udccb Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1980,
-320
],
"parameters": {
"width": 520,
"height": 620,
"content": "## \ud83c\udfac AI UGC Ad Generator\n\n**What it does:**\n- Analyzes app screen recording with Gemini AI\n- Generates UGC ad structure (hook, problem, solution, CTA)\n- Creates AI actor video with Sora 2\n- Removes background from actor\n- Composites actor over screen recording\n- Saves final UGC ad to Google Drive\n\n**Perfect for:**\n- App developers creating UGC-style ads\n- SaaS products with screen demos\n- Mobile app marketing teams\n- Digital product launches\n- Feature announcement videos\n\n**Processing time:** 5-8 minutes total\n- Gemini analysis: ~30s\n- Sora 2 generation: ~2-4 min\n- Background removal + composition: ~2-3 min\n\n**Setup:** ~10 minutes (3 API keys)\n\n[\ud83d\udcd6 Full Documentation](https://docs.videobgremover.com/)"
},
"typeVersion": 1,
"notesTextSize": "large",
"notesBackgroundColor": 4
},
{
"id": "sticky-api-keys",
"name": "\ud83d\udd11 API Keys",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1520,
-320
],
"parameters": {
"width": 420,
"height": 480,
"content": "## \ud83d\udd11 API Keys Setup (Required)\n\n**1. Gemini API Key**\n- Visit: https://aistudio.google.com/apikey\n- Create new API key\n- Add to n8n: Settings \u2192 Variables\n - Name: `GEMINI_KEY`\n - Value: your Gemini API key\n\n**2. FAL AI Key (for Sora 2)**\n- Visit: https://fal.ai/dashboard/keys\n- Sign up and create API key\n- Add to n8n: Settings \u2192 Variables\n - Name: `FAL_KEY`\n - Value: your FAL key\n\n**3. VideoBGRemover API Key**\n- Visit: https://videobgremover.com/api-management\n- Sign up (free tier available)\n- Add to n8n: Settings \u2192 Variables\n - Name: `VIDEOBGREMOVER_KEY`\n - Value: your API key\n\n\u2705 All keys use `$vars.` (secure)"
},
"typeVersion": 1
},
{
"id": "sticky-inputs",
"name": "\ud83d\udce5 Inputs",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1060,
-320
],
"parameters": {
"width": 420,
"height": 400,
"content": "## \ud83d\udce5 Input Required\n\n**screenshot_video_url** (required)\n- URL to your app screen recording\n- Should be vertical (9:16 aspect ratio)\n- Duration: 4-12 seconds recommended\n- Must be publicly accessible\n\n**meta** (optional)\n- `product_name`: Name of app/feature\n- `benefits`: Array of key benefits\n- `tone`: Desired ad tone (friendly, professional, etc.)\n\n**Example:**\n```json\n{\n \"screenshot_video_url\": \"https://...\",\n \"meta\": {\n \"product_name\": \"MyApp Pro\",\n \"benefits\": [\"Fast\", \"Easy\", \"Powerful\"],\n \"tone\": \"friendly\"\n },\n \"store_to_drive\": true\n}\n```"
},
"typeVersion": 1
},
{
"id": "sticky-workflow",
"name": "\ud83d\udd04 Workflow",
"type": "n8n-nodes-base.stickyNote",
"position": [
-600,
-320
],
"parameters": {
"width": 420,
"height": 520,
"content": "## \ud83d\udd04 Workflow Steps\n\n**1. Gemini Analysis** (~30s)\n- Uploads screen recording to Gemini\n- AI analyzes app/feature shown\n- Generates UGC ad structure:\n - `ad_topic`: What's being promoted\n - `hook`: Attention grabber (5-8 words)\n - `problem`: Pain point (12-15 words)\n - `solution`: Fix presented (12-15 words)\n - `cta`: Call-to-action (5-8 words)\n - `visual_details`: Actor description\n - `emotional_journey`: Emotion flow\n\n**2. Sora 2 Generation** (~2-4 min)\n- Creates AI actor video\n- Natural UGC-style delivery\n- Matches emotional journey\n- 4, 8, or 12 second duration\n\n**3. VBR Composition** (~2-3 min)\n- Removes actor background\n- Composites over screen recording\n- Bottom-right corner positioning\n- Audio mixing (30% bg + 100% fg)"
},
"typeVersion": 1
},
{
"id": "sticky-sora-structure",
"name": "\ud83d\udcdd Sora Prompt",
"type": "n8n-nodes-base.stickyNote",
"position": [
-100,
-320
],
"parameters": {
"width": 460,
"height": 340,
"content": "## \ud83c\udfaf Sora Prompt Structure\n\n**Template:**\n[ICP/visual details] filming a UGC ad about [APP CONTEXT].\nThey start by saying: [hook],\nthen explain: [problem],\npresent the solution: [solution],\nand end with: [CTA].\n[Emotional journey]. [Camera style].\nNatural hand gestures and expressions.\n\n**Key Elements:**\n- `ad_topic`: What app/feature is being promoted\n- `visual_details`: Actor description + setting\n- `ad_structure`: Hook, problem, solution, CTA\n- `emotional_journey`: How emotions progress\n- Camera style extracted from visual_details\n\n\u2705 This structure makes Sora understand CONTEXT vs WHAT TO GENERATE"
},
"typeVersion": 1
},
{
"id": "sticky-gdrive",
"name": "\ud83d\udcbe Google Drive",
"type": "n8n-nodes-base.stickyNote",
"position": [
400,
-320
],
"parameters": {
"width": 380,
"height": 340,
"content": "## \ud83d\udcbe Google Drive Setup\n\n**Step 1:** Connect Google Drive\n- Click \"Upload to Google Drive\" node\n- Click \"Connect\"\n- Authorize n8n\n\n**Step 2:** Choose folder (optional)\n- Default: Root of \"My Drive\"\n- Or select specific folder\n\n**Output:**\n- Permanent shareable link\n- Direct download URL\n- File metadata\n- Drive file ID\n\n\u23f1\ufe0f Setup time: ~2 minutes\n\n**Note:** Files are named automatically:\n`ugc_final_[timestamp].mp4`"
},
"typeVersion": 1
},
{
"id": "sticky-usage",
"name": "\ud83d\ude80 Usage",
"type": "n8n-nodes-base.stickyNote",
"position": [
860,
-320
],
"parameters": {
"width": 420,
"height": 480,
"content": "## \ud83d\ude80 How to Use\n\n**Manual testing:**\n1. Update `screenshot_video_url` in \"Sample Input\"\n2. Optionally update `meta` object\n3. Click \"Execute Workflow\"\n4. Wait 5-8 minutes for processing\n5. Check Google Drive for final video\n\n**Webhook automation:**\n1. Activate workflow\n2. Copy webhook URL\n3. POST to webhook:\n```json\n{\n \"screenshot_video_url\": \"https://...\",\n \"meta\": {\n \"product_name\": \"MyApp\",\n \"benefits\": [\"Fast\", \"Easy\"],\n \"tone\": \"friendly\"\n },\n \"store_to_drive\": true\n}\n```\n\n**Tips:**\n- Use 9:16 vertical videos\n- Keep recordings 4-12 seconds\n- Test with sample video first"
},
"typeVersion": 1
},
{
"id": "extract-webhook",
"name": "Extract Webhook Data",
"type": "n8n-nodes-base.set",
"position": [
-1300,
40
],
"parameters": {
"assignments": {
"assignments": [
{
"name": "screenshot_video_url",
"type": "string",
"value": "={{ $json.body?.screenshot_video_url ?? $json.screenshot_video_url }}"
},
{
"name": "meta",
"type": "object",
"value": "={{ $json.body?.meta ?? $json.meta ?? {} }}"
},
{
"name": "store_to_drive",
"type": "boolean",
"value": "={{ $json.body?.store_to_drive ?? $json.store_to_drive ?? false }}"
},
{
"name": "reference_image_url",
"type": "string",
"value": "={{ $json.body?.reference_image_url ?? $json.reference_image_url ?? '' }}"
},
{
"name": "source",
"type": "string",
"value": "webhook"
}
]
}
},
"typeVersion": 3.3
},
{
"id": "sample-input",
"name": "Sample Input (Edit Here)",
"type": "n8n-nodes-base.set",
"position": [
-1300,
280
],
"parameters": {
"assignments": {
"assignments": [
{
"name": "screenshot_video_url",
"type": "string",
"value": "https://videos.videobgremover.com/public-videos/assets/screenrecord_video_affina.mp4"
},
{
"name": "meta",
"type": "object",
"value": "={{ {product_name: 'Sample App', benefits: ['Easy to use', 'Fast', 'Powerful'], tone: 'friendly'} }}"
},
{
"name": "store_to_drive",
"type": "boolean",
"value": true
},
{
"name": "reference_image_url",
"type": "string",
"value": ""
},
{
"name": "source",
"type": "string",
"value": "manual"
}
]
}
},
"typeVersion": 3.3
},
{
"id": "merge-triggers",
"name": "Merge Triggers",
"type": "n8n-nodes-base.merge",
"position": [
-1080,
160
],
"parameters": {
"mode": "append",
"options": {}
},
"typeVersion": 2.1
},
{
"id": "sticky-section-gemini",
"name": "Section: Gemini",
"type": "n8n-nodes-base.stickyNote",
"position": [
-900,
380
],
"parameters": {
"width": 1100,
"height": 100,
"content": "## \ud83e\udd16 GEMINI AI ANALYSIS SECTION\nAnalyzes screen recording \u2192 Generates ad structure (hook, problem, solution, CTA, actor details)"
},
"typeVersion": 1,
"notesBackgroundColor": 5
},
{
"id": "download-shot",
"name": "Download Screenshot Video",
"type": "n8n-nodes-base.httpRequest",
"position": [
-860,
460
],
"parameters": {
"url": "={{ $json.screenshot_video_url }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "gemini-upload",
"name": "Upload to Gemini",
"type": "n8n-nodes-base.httpRequest",
"position": [
-640,
460
],
"parameters": {
"url": "=https://generativelanguage.googleapis.com/upload/v1beta/files?key={{ $vars.GEMINI_KEY }}",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "binaryData",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-Goog-Upload-Command",
"value": "start, upload, finalize"
},
{
"name": "X-Goog-Upload-Header-Content-Length",
"value": "={{ $binary.data.fileSize }}"
},
{
"name": "X-Goog-Upload-Header-Content-Type",
"value": "=video/{{ $binary.data.fileExtension }}"
},
{
"name": "Content-Type",
"value": "video/mp4"
}
]
},
"inputDataFieldName": "=data"
},
"typeVersion": 4.2
},
{
"id": "wait-gemini",
"name": "Wait for Processing",
"type": "n8n-nodes-base.wait",
"position": [
-420,
460
],
"parameters": {
"unit": "seconds",
"amount": 30
},
"typeVersion": 1.1
},
{
"id": "gemini-analyze",
"name": "Gemini \u2013 Analyze & Plan",
"type": "n8n-nodes-base.httpRequest",
"position": [
-180,
460
],
"parameters": {
"url": "=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent?key={{ $vars.GEMINI_KEY }}",
"method": "POST",
"jsonBody": "={\n \"contents\": [{\n \"role\": \"user\",\n \"parts\": [\n {\n \"fileData\": {\n \"fileUri\": \"{{ $('Upload to Gemini').item.json.file.uri }}\",\n \"mimeType\": \"{{ $('Upload to Gemini').item.json.file.mimeType }}\"\n }\n },\n {\n \"text\": \"You are a UGC ad planner. Analyze this vertical app-screen recording (<=12s) and create a facecam UGC ad structure.\\n\\nObserve the recording and infer: who uses this, what they want, where frustration occurs, why it matters.\\n\\nReturn strict JSON:\\n{\\n ad_topic: \\\"Short description of what app/feature is being promoted (e.g. 'Hinge unlimited likes upgrade')\\\",\\n icp_summary: \\\"One sentence describing the target user and their core frustration\\\",\\n ad_structure: {\\n hook_0_2s: \\\"Opening line that stops scroll (5-8 words max)\\\",\\n problem_2_6s: \\\"One brief sentence about the problem (12-15 words max)\\\",\\n solution_6_10s: \\\"Present the fix with product name and benefit (12-15 words max)\\\",\\n cta_10_12s: \\\"Clear call-to-action (5-8 words max)\\\"\\n },\\n visual_details: \\\"Describe actor (age, gender, wardrobe), setting (location, lighting, background), and camera style (framing, movement)\\\",\\n emotional_journey: \\\"Brief description of how emotions change: starting state (0-2s), middle escalation (2-6s), resolution (6-12s)\\\",\\n duration: 4 or 8 or 12\\n}\\n\\nUse meta if provided: product_name, benefits[], tone.\\nDo NOT add extra fields. Keep all fields concise.\"\n }\n ]\n }],\n \"generationConfig\": {\n \"temperature\": 0.7,\n \"topK\": 40,\n \"topP\": 0.95,\n \"maxOutputTokens\": 8192\n }\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "extract-plan",
"name": "Extract & Parse",
"type": "n8n-nodes-base.code",
"position": [
60,
460
],
"parameters": {
"jsCode": "// Extract raw text from Gemini response\nconst rawText = $input.item.json.candidates[0].content.parts[0].text;\n\n// Remove markdown code blocks\nconst cleanJson = rawText.replace(/```json\\n?/g, '').replace(/\\n?```/g, '').trim();\n\n// Parse the JSON\nconst data = JSON.parse(cleanJson);\n\n// Return the structured data\nreturn {\n json: {\n ad_topic: data.ad_topic,\n icp_summary: data.icp_summary,\n ad_structure: data.ad_structure,\n visual_details: data.visual_details,\n emotional_journey: data.emotional_journey,\n duration: data.duration || 8\n }\n};"
},
"typeVersion": 2
},
{
"id": "sticky-section-sora",
"name": "Section: Sora 2",
"type": "n8n-nodes-base.stickyNote",
"position": [
280,
680
],
"parameters": {
"width": 940,
"height": 100,
"content": "## \ud83c\udfa8 SORA 2 AI VIDEO GENERATION SECTION\nGenerates AI actor video based on Gemini's plan \u2192 UGC-style talking head video"
},
"typeVersion": 1,
"notesBackgroundColor": 6
},
{
"id": "build-sora-prompt",
"name": "Build Sora Prompt",
"type": "n8n-nodes-base.code",
"position": [
300,
760
],
"parameters": {
"jsCode": "// Build Sora prompt following the structure:\n// [ICP/visual] filming a UGC ad about [APP CONTEXT]. They start by saying [hook], then explain [problem], present the solution: [solution], and end with [CTA]. [Emotional journey]. [Camera style]. Natural hand gestures and expressions.\n\nconst visual = $input.item.json.visual_details;\nconst emotions = $input.item.json.emotional_journey;\nconst ad = $input.item.json.ad_structure;\nconst topic = $input.item.json.ad_topic;\n\n// Extract camera style from visual_details (usually at the end)\nconst cameraStyle = visual.includes('camera') ? visual.split('.').find(s => s.toLowerCase().includes('camera'))?.trim() || 'Handheld selfie-style close-up' : 'Handheld selfie-style close-up';\n\n// Build the person description (without camera details)\nconst personDescription = visual.split('.').filter(s => !s.toLowerCase().includes('camera')).join('.').trim();\n\nconst soraPrompt = `${personDescription} filming a UGC ad about ${topic}. They start by saying: ${ad.hook_0_2s}. Then explain: ${ad.problem_2_6s}. Present the solution: ${ad.solution_6_10s}. And end with: ${ad.cta_10_12s}. ${emotions}. ${cameraStyle}. Natural hand gestures and authentic expressions throughout. Raw selfie video of ONLY the person talking - no text overlays, no app UI, no graphics, no split-screen. Just the speaker.`;\n\nreturn {\n json: {\n sora_prompt: soraPrompt,\n duration: $input.item.json.duration\n }\n};"
},
"typeVersion": 2
},
{
"id": "sora-submit",
"name": "Sora 2 \u2013 Submit (fal.ai)",
"type": "n8n-nodes-base.httpRequest",
"position": [
520,
760
],
"parameters": {
"url": "https://queue.fal.run/fal-ai/sora-2/text-to-video",
"method": "POST",
"jsonBody": "={\n \"prompt\": \"{{ $json.sora_prompt }}\",\n \"aspect_ratio\": \"9:16\",\n \"resolution\": \"720p\",\n \"duration\": {{ $json.duration }}\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Key {{ $vars.FAL_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "sora-status",
"name": "Sora \u2013 Check Status",
"type": "n8n-nodes-base.httpRequest",
"position": [
740,
760
],
"parameters": {
"url": "=https://queue.fal.run/fal-ai/sora-2/requests/{{ $json.request_id }}/status",
"options": {
"response": {
"response": {
"neverError": true,
"responseFormat": "json"
}
}
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Key {{ $vars.FAL_KEY }}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "wait-20",
"name": "Wait 20s",
"type": "n8n-nodes-base.wait",
"position": [
960,
860
],
"parameters": {
"unit": "seconds",
"amount": 20
},
"typeVersion": 1.1
},
{
"id": "if-sora-done",
"name": "Sora Completed?",
"type": "n8n-nodes-base.if",
"position": [
960,
760
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "ok",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.status }}",
"rightValue": "COMPLETED"
}
]
}
},
"typeVersion": 2
},
{
"id": "sora-result",
"name": "Sora \u2013 Get Result",
"type": "n8n-nodes-base.httpRequest",
"position": [
1180,
760
],
"parameters": {
"url": "=https://queue.fal.run/fal-ai/sora-2/requests/{{ $('Sora 2 \u2013 Submit (fal.ai)').item.json.request_id }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Key {{ $vars.FAL_KEY }}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "sticky-section-vbr",
"name": "Section: VideoBGRemover",
"type": "n8n-nodes-base.stickyNote",
"position": [
1400,
980
],
"parameters": {
"width": 1240,
"height": 100,
"content": "## \ud83c\udfac VIDEOBGREMOVER COMPOSITION SECTION\nRemoves AI actor background \u2192 Composites over screen recording \u2192 Mixes audio"
},
"typeVersion": 1,
"notesBackgroundColor": 2
},
{
"id": "vbr-create",
"name": "VBR \u2013 Create Job (Foreground=Sora)",
"type": "n8n-nodes-base.httpRequest",
"position": [
1400,
1060
],
"parameters": {
"url": "https://api.videobgremover.com/api/v1/jobs",
"method": "POST",
"jsonBody": "={ \n \"video_url\": \"{{ $json.video.url }}\" \n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "X-API-Key",
"value": "={{ $vars.VIDEOBGREMOVER_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.1
},
{
"id": "vbr-start",
"name": "VBR \u2013 Start Composition",
"type": "n8n-nodes-base.httpRequest",
"position": [
1620,
1060
],
"parameters": {
"url": "=https://api.videobgremover.com/api/v1/jobs/{{ $json.id }}/start",
"method": "POST",
"jsonBody": "={\n \"background\": {\n \"type\": \"composition\",\n \"composition\": {\n \"template\": \"ai_ugc_ad\",\n \"background_type\": \"video\",\n \"background_url\": \"{{ $('Merge Triggers').item.json.screenshot_video_url }}\",\n \"background_audio_enabled\": true,\n \"background_audio_volume\": 0.3,\n \"export_format\": \"h264\",\n \"export_preset\": \"medium\"\n }\n }\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "X-API-Key",
"value": "={{ $vars.VIDEOBGREMOVER_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.1
},
{
"id": "vbr-status",
"name": "VBR \u2013 Check Status",
"type": "n8n-nodes-base.httpRequest",
"position": [
1840,
1060
],
"parameters": {
"url": "=https://api.videobgremover.com/api/v1/jobs/{{ $('VBR \u2013 Create Job (Foreground=Sora)').item.json.id }}/status",
"method": "GET",
"options": {
"response": {
"response": {
"neverError": true,
"responseFormat": "json"
}
}
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-API-Key",
"value": "={{ $vars.VIDEOBGREMOVER_KEY }}"
}
]
}
},
"typeVersion": 4.1
},
{
"id": "wait-20b",
"name": "Wait 20s (VBR)",
"type": "n8n-nodes-base.wait",
"position": [
2060,
1160
],
"parameters": {
"unit": "seconds",
"amount": 20
},
"typeVersion": 1.1
},
{
"id": "if-vbr-done",
"name": "VBR Completed?",
"type": "n8n-nodes-base.if",
"position": [
2060,
1060
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "done",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.status }}",
"rightValue": "completed"
}
]
}
},
"typeVersion": 2
},
{
"id": "prepare-final",
"name": "Prepare Final URL",
"type": "n8n-nodes-base.set",
"position": [
2280,
1060
],
"parameters": {
"assignments": {
"assignments": [
{
"name": "final_url",
"type": "string",
"value": "={{ $json.processed_video_url }}"
},
{
"name": "length_seconds",
"type": "number",
"value": "={{ $json.length_seconds ?? $json.meta?.duration }}"
}
]
}
},
"typeVersion": 3.3
},
{
"id": "sticky-section-output",
"name": "Section: Output",
"type": "n8n-nodes-base.stickyNote",
"position": [
2480,
1280
],
"parameters": {
"width": 900,
"height": 100,
"content": "## \ud83d\udcbe OUTPUT SECTION\nDownload final video \u2192 Upload to Google Drive \u2192 Return response"
},
"typeVersion": 1,
"notesBackgroundColor": 3
},
{
"id": "download-final",
"name": "Download Final Video",
"type": "n8n-nodes-base.httpRequest",
"position": [
2480,
1360
],
"parameters": {
"url": "={{ $('Prepare Final URL').item.json.final_url }}",
"options": {
"response": {
"response": {
"responseFormat": "file"
}
}
}
},
"typeVersion": 4.2
},
{
"id": "gdrive-upload",
"name": "Upload to Google Drive",
"type": "n8n-nodes-base.googleDrive",
"position": [
2700,
1360
],
"parameters": {
"name": "=ugc_final_{{ new Date().getTime() }}.mp4",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"options": {
"simplifyOutput": true
},
"folderId": {
"__rl": true,
"mode": "list",
"value": "root"
},
"operation": "upload",
"binaryPropertyName": "data"
},
"typeVersion": 3
},
{
"id": "build-drive-resp",
"name": "Build Drive Response",
"type": "n8n-nodes-base.set",
"position": [
2920,
1360
],
"parameters": {
"assignments": {
"assignments": [
{
"name": "response",
"type": "object",
"value": "={{ { url: $json.webViewLink, drive_file_id: $json.id, source: 'google_drive' } }}"
}
]
}
},
"typeVersion": 3.3
},
{
"id": "if-webhook",
"name": "From Webhook?",
"type": "n8n-nodes-base.if",
"position": [
3140,
1460
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "is-webhook",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Merge Triggers').item.json.source }}",
"rightValue": "webhook"
}
]
}
},
"typeVersion": 2
},
{
"id": "respond",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
3360,
1360
],
"parameters": {
"responseBody": "={{ $json.response ?? $json }}",
"responseCode": 200
},
"typeVersion": 1.1
},
{
"id": "manual-complete",
"name": "Manual Test Complete",
"type": "n8n-nodes-base.set",
"position": [
3360,
1560
],
"parameters": {
"assignments": {
"assignments": [
{
"name": "final_result",
"type": "object",
"value": "={{ $json.response ?? $json }}"
}
]
}
},
"typeVersion": 3.3
}
],
"settings": {
"executionOrder": "v1"
},
"connections": {
"Wait 20s": {
"main": [
[
{
"node": "Sora \u2013 Check Status",
"type": "main",
"index": 0
}
]
]
},
"From Webhook?": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
],
[
{
"node": "Manual Test Complete",
"type": "main",
"index": 0
}
]
]
},
"Manual Trigger": {
"main": [
[
{
"node": "Sample Input (Edit Here)",
"type": "main",
"index": 0
}
]
]
},
"Merge Triggers": {
"main": [
[
{
"node": "Download Screenshot Video",
"type": "main",
"index": 0
}
]
]
},
"VBR Completed?": {
"main": [
[
{
"node": "Prepare Final URL",
"type": "main",
"index": 0
}
],
[
{
"node": "Wait 20s (VBR)",
"type": "main",
"index": 0
}
]
]
},
"Wait 20s (VBR)": {
"main": [
[
{
"node": "VBR \u2013 Check Status",
"type": "main",
"index": 0
}
]
]
},
"Extract & Parse": {
"main": [
[
{
"node": "Build Sora Prompt",
"type": "main",
"index": 0
}
]
]
},
"Sora Completed?": {
"main": [
[
{
"node": "Sora \u2013 Get Result",
"type": "main",
"index": 0
}
],
[
{
"node": "Wait 20s",
"type": "main",
"index": 0
}
]
]
},
"Webhook Trigger": {
"main": [
[
{
"node": "Extract Webhook Data",
"type": "main",
"index": 0
}
]
]
},
"Upload to Gemini": {
"main": [
[
{
"node": "Wait for Processing",
"type": "main",
"index": 0
}
]
]
},
"Build Sora Prompt": {
"main": [
[
{
"node": "Sora 2 \u2013 Submit (fal.ai)",
"type": "main",
"index": 0
}
]
]
},
"Prepare Final URL": {
"main": [
[
{
"node": "Download Final Video",
"type": "main",
"index": 0
}
]
]
},
"Sora \u2013 Get Result": {
"main": [
[
{
"node": "VBR \u2013 Create Job (Foreground=Sora)",
"type": "main",
"index": 0
}
]
]
},
"Wait for Processing": {
"main": [
[
{
"node": "Gemini \u2013 Analyze & Plan",
"type": "main",
"index": 0
}
]
]
},
"Build Drive Response": {
"main": [
[
{
"node": "From Webhook?",
"type": "main",
"index": 0
}
]
]
},
"Download Final Video": {
"main": [
[
{
"node": "Upload to Google Drive",
"type": "main",
"index": 0
}
]
]
},
"Extract Webhook Data": {
"main": [
[
{
"node": "Merge Triggers",
"type": "main",
"index": 0
}
]
]
},
"VBR \u2013 Check Status": {
"main": [
[
{
"node": "VBR Completed?",
"type": "main",
"index": 0
}
]
]
},
"Sora \u2013 Check Status": {
"main": [
[
{
"node": "Sora Completed?",
"type": "main",
"index": 0
}
]
]
},
"Upload to Google Drive": {
"main": [
[
{
"node": "Build Drive Response",
"type": "main",
"index": 0
}
]
]
},
"Sample Input (Edit Here)": {
"main": [
[
{
"node": "Merge Triggers",
"type": "main",
"index": 1
}
]
]
},
"Download Screenshot Video": {
"main": [
[
{
"node": "Upload to Gemini",
"type": "main",
"index": 0
}
]
]
},
"Gemini \u2013 Analyze & Plan": {
"main": [
[
{
"node": "Extract & Parse",
"type": "main",
"index": 0
}
]
]
},
"VBR \u2013 Start Composition": {
"main": [
[
{
"node": "VBR \u2013 Check Status",
"type": "main",
"index": 0
}
]
]
},
"Sora 2 \u2013 Submit (fal.ai)": {
"main": [
[
{
"node": "Sora \u2013 Check Status",
"type": "main",
"index": 0
}
]
]
},
"VBR \u2013 Create Job (Foreground=Sora)": {
"main": [
[
{
"node": "VBR \u2013 Start Composition",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow is perfect for app developers, SaaS founders, and mobile growth teams who need constant UGC-style video ads without hiring creators or agencies. If you're spending $500+ per creator and waiting weeks for videos, this automates the entire process for $2-5 per video…
Source: https://n8n.io/workflows/9505/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
AI Background Generation with Nano Banana (Gemini Image). Uses httpRequest, googleDrive. Webhook trigger; 35 nodes.
This template is for developers, teams, and automation enthusiasts who want a private, PIN-protected Telegram chatbot that answers questions from their own documents — without relying on external AI A
Storage Sync - Google Drive. Uses supabase, googleDrive, httpRequest. Webhook trigger; 22 nodes.
This workflow automates the creation of user-generated-content-style product videos by combining Gemini's image generation with OpenAI's SORA 2 video generation. It accepts webhook requests with produ
For content creators, agencies, and SaaS developers who need automated AI image generation and editing with professional delivery workflows.