This workflow corresponds to n8n.io template #17222 — 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 →
{
"id": "uJVtmxWf6UCJLXQF",
"name": "Auto-upload Zoom cloud recordings to YouTube and Google Drive",
"tags": [],
"nodes": [
{
"id": "9b5cbf60-bc19-43fc-8edc-1aedde80c3df",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
0
],
"parameters": {
"width": 500,
"height": 1204,
"content": "## Auto-upload Zoom cloud recordings to YouTube and Google Drive\n\n### Who's it for\nCoaches, educators, and teams who record sessions on Zoom and want every recording archived in Google Drive and published to YouTube (unlisted) without manual downloads.\n\n### How it works\n1. Zoom calls this workflow's webhook when a cloud recording completes.\n2. The workflow answers Zoom's URL-validation challenge automatically (HMAC-SHA256) \u2014 the step where most webhook setups fail.\n3. MP4 files are extracted from the event payload and downloaded using the short-lived download token included in the event.\n4. Each file is uploaded to Google Drive and YouTube in parallel.\n\n### How to set up\n1. Open the **Setup** node and paste your Zoom app's Secret Token.\n2. In the Zoom Marketplace, create a *Webhook Only* (or Server-to-Server OAuth) app, subscribe to the **recording.completed** event, and paste this workflow's production webhook URL.\n3. Activate the workflow, then press **Validate** in Zoom.\n4. Select your Google Drive destination folder and connect your YouTube OAuth2 credentials.\n\n### Requirements\n- Zoom paid plan (cloud recording)\n- YouTube Data API v3 \u2014 uploads cost 1,600 quota units (\u22486 videos/day on the default quota)\n\n### How to customize\nChange the YouTube privacy status (unlisted \u2192 private/public), adjust the file-naming pattern in the *Extract MP4 files* node, or add a Slack/email notification after upload."
},
"typeVersion": 1
},
{
"id": "ab4ca747-0d19-4388-8e60-c0f2c4cb4078",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
560,
224
],
"parameters": {
"color": 7,
"width": 682,
"height": 362,
"content": "### 1. Receive Zoom events\nPaste your Zoom app's **Secret Token** into the Setup node."
},
"typeVersion": 1
},
{
"id": "34e7f355-61b9-44f8-89b9-aaa61fc42477",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1296,
16
],
"parameters": {
"color": 7,
"width": 500,
"height": 330,
"content": "### 2. URL validation\nAnswers Zoom's challenge automatically. Click **Validate** in Zoom after activating this workflow."
},
"typeVersion": 1
},
{
"id": "9d03d2ae-ee43-4eb9-9784-8fb10ac804ac",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1296,
384
],
"parameters": {
"color": 7,
"width": 1200,
"height": 506,
"content": "### 3. Download & upload\nExtract completed MP4s, download with the event's token, then upload to Google Drive and YouTube (unlisted) in parallel."
},
"typeVersion": 1
},
{
"id": "f02505ab-8aeb-463c-9299-efec74ac817b",
"name": "Receive Zoom event",
"type": "n8n-nodes-base.webhook",
"position": [
624,
384
],
"parameters": {
"path": "zoom-recording",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "8ef8867f-5bde-4545-9883-0effecd04f20",
"name": "Setup: Edit this node",
"type": "n8n-nodes-base.set",
"position": [
848,
384
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "secret-token",
"name": "zoomSecretToken",
"type": "string",
"value": "YOUR_ZOOM_SECRET_TOKEN"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "e327b5b9-9dc9-4888-9b06-dbe7edb3805a",
"name": "Is URL validation?",
"type": "n8n-nodes-base.if",
"position": [
1072,
384
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.body.event }}",
"rightValue": "endpoint.url_validation"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "b2b49bce-f8c1-4ede-ac86-123ef6594a7c",
"name": "Generate HMAC response",
"type": "n8n-nodes-base.code",
"position": [
1328,
176
],
"parameters": {
"jsCode": "// Answer Zoom's URL-validation challenge:\n// HMAC-SHA256 of plainToken, keyed with your app's Secret Token.\nconst crypto = require('crypto');\n\nconst secretToken = $json.zoomSecretToken;\nconst plainToken = $json.body.payload.plainToken;\n\nconst encryptedToken = crypto\n .createHmac('sha256', secretToken)\n .update(plainToken)\n .digest('hex');\n\nreturn [{ json: { plainToken, encryptedToken } }];"
},
"typeVersion": 2
},
{
"id": "df0e0ede-0b84-46fd-94bf-62b524bd55e6",
"name": "Respond to validation",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1552,
176
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={{ { plainToken: $json.plainToken, encryptedToken: $json.encryptedToken } }}"
},
"typeVersion": 1.1
},
{
"id": "be903419-a52c-4fe9-84ad-eb817862f6f9",
"name": "Respond 200 OK",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1328,
592
],
"parameters": {
"options": {},
"respondWith": "text",
"responseBody": "OK"
},
"typeVersion": 1.1
},
{
"id": "e02a6e21-209c-4113-a413-f7e636fe16d5",
"name": "Extract MP4 files",
"type": "n8n-nodes-base.code",
"position": [
1552,
592
],
"parameters": {
"jsCode": "// Extract completed MP4 files from the recording.completed payload.\nconst body = $input.first().json.body;\nconst obj = body.payload.object;\nconst downloadToken = body.download_token;\n\nconst topic = obj.topic || 'Zoom recording';\nconst startTime = obj.start_time || '';\n// e.g. 2026-07-18_1400 (UTC)\nconst dt = startTime ? startTime.replace(/[:]/g, '').replace('T', '_').slice(0, 15) : 'unknown';\n\nconst files = (obj.recording_files || [])\n .filter(f => f.file_type === 'MP4' && f.status === 'completed')\n .map(f => ({\n json: {\n fileName: `${dt}_${topic}.mp4`.replace(/[\\\\/:*?\"<>|]/g, '_'),\n videoTitle: `${topic} (${startTime.slice(0, 10)})`,\n downloadUrl: f.download_url,\n downloadToken: downloadToken,\n fileSize: f.file_size,\n topic: topic,\n startTime: startTime\n }\n }));\n\nif (files.length === 0) {\n return [{ json: { skip: true, reason: 'No MP4 files in this event', topic } }];\n}\n\nreturn files;"
},
"typeVersion": 2
},
{
"id": "24a4ddbd-3497-4a65-aae6-caa12d91d089",
"name": "Has MP4?",
"type": "n8n-nodes-base.if",
"position": [
1776,
592
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "boolean",
"operation": "notEquals"
},
"leftValue": "={{ $json.skip }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.2
},
{
"id": "41f5a4f3-5d27-487f-9b59-3e2d83d0de63",
"name": "Download recording",
"type": "n8n-nodes-base.httpRequest",
"position": [
2000,
592
],
"parameters": {
"url": "={{ $json.downloadUrl }}",
"options": {
"timeout": 300000,
"response": {
"response": {
"responseFormat": "file"
}
}
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $json.downloadToken }}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "a7624fa8-01fb-4eff-b91a-06c71aa82c9b",
"name": "Upload to Google Drive",
"type": "n8n-nodes-base.googleDrive",
"position": [
2240,
528
],
"parameters": {
"name": "={{ $('Extract MP4 files').item.json.fileName }}",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"options": {},
"folderId": {
"__rl": true,
"mode": "list",
"value": "root",
"cachedResultName": "Select your destination folder"
}
},
"typeVersion": 3
},
{
"id": "1b27281e-160c-478e-abb5-3e454449bb49",
"name": "Upload to YouTube",
"type": "n8n-nodes-base.youTube",
"position": [
2240,
736
],
"parameters": {
"title": "={{ $('Extract MP4 files').item.json.videoTitle }}",
"options": {
"description": "=Zoom session recording\nMeeting: {{ $('Extract MP4 files').item.json.topic }}\nDate: {{ $('Extract MP4 files').item.json.startTime }}",
"privacyStatus": "unlisted"
},
"resource": "video",
"operation": "upload",
"categoryId": "27",
"regionCode": "US"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "e27af28b-9bf8-4c19-a329-c29f15d96379",
"nodeGroups": [],
"connections": {
"Has MP4?": {
"main": [
[
{
"node": "Download recording",
"type": "main",
"index": 0
}
]
]
},
"Respond 200 OK": {
"main": [
[
{
"node": "Extract MP4 files",
"type": "main",
"index": 0
}
]
]
},
"Extract MP4 files": {
"main": [
[
{
"node": "Has MP4?",
"type": "main",
"index": 0
}
]
]
},
"Download recording": {
"main": [
[
{
"node": "Upload to Google Drive",
"type": "main",
"index": 0
},
{
"node": "Upload to YouTube",
"type": "main",
"index": 0
}
]
]
},
"Is URL validation?": {
"main": [
[
{
"node": "Generate HMAC response",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond 200 OK",
"type": "main",
"index": 0
}
]
]
},
"Receive Zoom event": {
"main": [
[
{
"node": "Setup: Edit this node",
"type": "main",
"index": 0
}
]
]
},
"Setup: Edit this node": {
"main": [
[
{
"node": "Is URL validation?",
"type": "main",
"index": 0
}
]
]
},
"Generate HMAC response": {
"main": [
[
{
"node": "Respond to validation",
"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 receives Zoom cloud recording webhooks, handles Zoom’s endpoint URL validation, downloads completed MP4 recording files using the event download token, and uploads each recording to both Google Drive and YouTube as an unlisted video. Receives a POST webhook from…
Source: https://n8n.io/workflows/17222/ — 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.
Automated n8n workflow: Receives videos via form, dubs/translates them to the selected languages, and—upon completion—uploads them to multiple social media channels and cloud drives, including Box, Dr
My workflow 3. Uses httpRequest, youTube. Webhook trigger; 17 nodes.
This workflow automates the process of creating an AI-generated avatar video using HeyGen and directly uploading it to YouTube. By sending text input via a webhook, the workflow generates a video with
[아세아치과] 유튜브 영상 업로드. Uses notion, googleDrive, youTube, httpRequest. Event-driven trigger; 11 nodes.
Automated Tiktok Videos. Uses executeCommand, httpRequest, lmChatGroq, chainLlm. Webhook trigger; 45 nodes.