This workflow follows the HTTP Request → Stopanderror 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": "MCP Veo Video",
"active": true,
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "veo-video",
"responseMode": "responseNode",
"options": {}
},
"id": "webhook-trigger",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
304
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "condition-input",
"leftValue": "={{ $json.body.prompt || $json.body.sourceVideo }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "exists"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "check-input",
"name": "Has required input?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
464,
304
]
},
{
"parameters": {
"errorMessage": "={{ \"Missing required input. Provide 'prompt' for most operations or 'sourceVideo' for extendVideo\" }}"
},
"id": "error-no-input",
"name": "Error: Missing Input",
"type": "n8n-nodes-base.stopAndError",
"typeVersion": 1,
"position": [
688,
500
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "condition-callback",
"leftValue": "={{ $json.body.callback_url }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "exists"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "check-callback",
"name": "Has Callback URL?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
688,
304
]
},
{
"parameters": {
"jsCode": "// Store callback info for async processing\nconst crypto = require('crypto');\nconst input = $('Webhook').first().json;\nconst body = input.body || input;\n\n// Generate or use provided job_id\nconst job_id = body.job_id || crypto.randomUUID();\nconst callback_url = body.callback_url;\n\nreturn [{\n json: {\n ...input,\n _callback: {\n job_id,\n callback_url\n }\n }\n}];"
},
"id": "store-callback",
"name": "Store Callback Info",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
912,
192
]
},
{
"parameters": {
"jsCode": "// Prepare data for Veo Video node (sync mode)\nconst input = $('Webhook').first().json;\nconst body = input.body || input;\n\n// Get operation from request (default: generateFromText)\nconst operation = body.operation || 'generateFromText';\n\n// Video parameters\nconst model = body.model || 'veo-3.1-generate-001';\nconst durationSeconds = body.durationSeconds || 6;\nconst aspectRatio = body.aspectRatio || '16:9';\nconst resolution = body.resolution || '1080p';\nconst generateAudio = body.generateAudio !== false;\nconst enhancePrompt = body.enhancePrompt !== false;\nconst personGeneration = body.personGeneration || 'allow_adult';\nconst preset = body.preset || 'none';\n\n// Build result\nconst result = {\n operation,\n prompt: body.prompt || '',\n model,\n durationSeconds,\n aspectRatio,\n resolution,\n generateAudio,\n enhancePrompt,\n personGeneration,\n preset,\n originalRequest: { ...body }\n};\n\n// For generateFromImage\nif (operation === 'generateFromImage') {\n result.sourceImage = body.sourceImage || '';\n result.sourceImageMimeType = body.sourceImageMimeType || 'image/png';\n}\n\n// For generateLongVideo\nif (operation === 'generateLongVideo') {\n result.targetDuration = body.targetDuration || 30;\n}\n\n// For extendVideo\nif (operation === 'extendVideo') {\n result.sourceVideo = body.sourceVideo || '';\n result.extensionPrompt = body.extensionPrompt || '';\n}\n\nreturn [{ json: result }];"
},
"id": "prepare-config-sync",
"name": "Prepare Config (Sync)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
912,
416
]
},
{
"parameters": {
"jsCode": "// Prepare data for Veo Video node (async mode)\nconst input = $input.first().json;\nconst body = input.body || input;\nconst _callback = input._callback;\n\n// Get operation from request (default: generateFromText)\nconst operation = body.operation || 'generateFromText';\n\n// Video parameters\nconst model = body.model || 'veo-3.1-generate-001';\nconst durationSeconds = body.durationSeconds || 6;\nconst aspectRatio = body.aspectRatio || '16:9';\nconst resolution = body.resolution || '1080p';\nconst generateAudio = body.generateAudio !== false;\nconst enhancePrompt = body.enhancePrompt !== false;\nconst personGeneration = body.personGeneration || 'allow_adult';\nconst preset = body.preset || 'none';\n\n// Build result\nconst result = {\n operation,\n prompt: body.prompt || '',\n model,\n durationSeconds,\n aspectRatio,\n resolution,\n generateAudio,\n enhancePrompt,\n personGeneration,\n preset,\n originalRequest: { ...body },\n _callback\n};\n\n// For generateFromImage\nif (operation === 'generateFromImage') {\n result.sourceImage = body.sourceImage || '';\n result.sourceImageMimeType = body.sourceImageMimeType || 'image/png';\n}\n\n// For generateLongVideo\nif (operation === 'generateLongVideo') {\n result.targetDuration = body.targetDuration || 30;\n}\n\n// For extendVideo\nif (operation === 'extendVideo') {\n result.sourceVideo = body.sourceVideo || '';\n result.extensionPrompt = body.extensionPrompt || '';\n}\n\nreturn [{ json: result }];"
},
"id": "prepare-config-async",
"name": "Prepare Config (Async)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1136,
192
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "condition-optimize-sync",
"leftValue": "={{ $json.operation }}",
"rightValue": "optimizePrompt",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "route-operation-sync",
"name": "Optimize Prompt? (Sync)",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1136,
416
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "condition-optimize-async",
"leftValue": "={{ $json.operation }}",
"rightValue": "optimizePrompt",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "route-operation-async",
"name": "Optimize Prompt? (Async)",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1360,
192
]
},
{
"parameters": {
"operation": "optimizePrompt",
"prompt": "={{ $json.prompt }}",
"preset": "={{ $json.preset }}"
},
"id": "veo-optimize-sync",
"name": "Optimize Prompt (Sync)",
"type": "n8n-nodes-veo-video.veoVideo",
"typeVersion": 1,
"position": [
1360,
304
],
"credentials": {
"googleVertexAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "={{ $json.operation }}",
"prompt": "={{ $json.prompt }}",
"extensionPrompt": "={{ $json.extensionPrompt }}",
"sourceVideo": "={{ $json.sourceVideo }}",
"sourceImage": "={{ $json.sourceImage }}",
"sourceImageMimeType": "={{ $json.sourceImageMimeType }}",
"preset": "={{ $json.preset }}",
"model": "={{ $json.model }}",
"targetDuration": "={{ $json.targetDuration }}",
"durationSeconds": "={{ $json.durationSeconds }}",
"aspectRatio": "={{ $json.aspectRatio }}",
"resolution": "={{ $json.resolution }}",
"generateAudio": "={{ $json.generateAudio }}",
"enhancePrompt": "={{ $json.enhancePrompt }}",
"options": {
"personGeneration": "={{ $json.personGeneration }}"
}
},
"id": "veo-video-sync",
"name": "Veo Video (Sync)",
"type": "n8n-nodes-veo-video.veoVideo",
"typeVersion": 1,
"position": [
1360,
528
],
"credentials": {
"googleVertexAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Format response for optimizePrompt (sync)\nconst input = $input.first().json;\n\nconst response = {\n success: true,\n execution_mode: 'sync',\n operation: 'optimizePrompt',\n originalPrompt: input.originalPrompt,\n optimizedPrompt: input.optimizedPrompt,\n keywordsAdded: input.keywordsAdded,\n preset: input.preset,\n metadata: input.metadata\n};\n\nreturn [{ json: response }];"
},
"id": "format-optimize-response-sync",
"name": "Format Optimize Response (Sync)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1584,
304
]
},
{
"parameters": {
"jsCode": "// Format response for video operations (sync)\nconst input = $input.first().json;\n\nconst response = {\n success: true,\n execution_mode: 'sync',\n operation: input.operation,\n video: input.video,\n videoBase64: input.videoBase64,\n model: input.model,\n generationTimeSeconds: input.generationTimeSeconds,\n preset: input.preset,\n metadata: input.metadata\n};\n\nreturn [{ json: response }];"
},
"id": "format-response-sync",
"name": "Format Response (Sync)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1584,
528
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"id": "respond-success-sync",
"name": "Respond Success (Sync)",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1808,
416
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ { success: true, job_id: $json._callback.job_id, status: 'processing', message: 'Video generation started' } }}",
"options": {
"responseCode": 202
}
},
"id": "respond-immediate",
"name": "Respond Immediate",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1584,
80
]
},
{
"parameters": {
"operation": "optimizePrompt",
"prompt": "={{ $json.prompt }}",
"preset": "={{ $json.preset }}"
},
"id": "veo-optimize-async",
"name": "Optimize Prompt (Async)",
"type": "n8n-nodes-veo-video.veoVideo",
"typeVersion": 1,
"position": [
1808,
80
],
"credentials": {
"googleVertexAiApi": {
"name": "<your credential>"
}
},
"onError": "continueErrorOutput"
},
{
"parameters": {
"operation": "={{ $json.operation }}",
"prompt": "={{ $json.prompt }}",
"extensionPrompt": "={{ $json.extensionPrompt }}",
"sourceVideo": "={{ $json.sourceVideo }}",
"sourceImage": "={{ $json.sourceImage }}",
"sourceImageMimeType": "={{ $json.sourceImageMimeType }}",
"preset": "={{ $json.preset }}",
"model": "={{ $json.model }}",
"targetDuration": "={{ $json.targetDuration }}",
"durationSeconds": "={{ $json.durationSeconds }}",
"aspectRatio": "={{ $json.aspectRatio }}",
"resolution": "={{ $json.resolution }}",
"generateAudio": "={{ $json.generateAudio }}",
"enhancePrompt": "={{ $json.enhancePrompt }}",
"options": {
"personGeneration": "={{ $json.personGeneration }}"
}
},
"id": "veo-video-async",
"name": "Veo Video (Async)",
"type": "n8n-nodes-veo-video.veoVideo",
"typeVersion": 1,
"position": [
1808,
192
],
"credentials": {
"googleVertexAiApi": {
"name": "<your credential>"
}
},
"onError": "continueErrorOutput"
},
{
"parameters": {
"jsCode": "// Format response for optimizePrompt (async)\nconst input = $input.first().json;\nconst _callback = $('Prepare Config (Async)').first().json._callback;\n\nconst response = {\n success: true,\n execution_mode: 'async',\n job_id: _callback.job_id,\n operation: 'optimizePrompt',\n originalPrompt: input.originalPrompt,\n optimizedPrompt: input.optimizedPrompt,\n keywordsAdded: input.keywordsAdded,\n preset: input.preset,\n metadata: input.metadata,\n _callback\n};\n\nreturn [{ json: response }];"
},
"id": "format-optimize-response-async",
"name": "Format Optimize Response (Async)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2032,
80
]
},
{
"parameters": {
"jsCode": "// Format response for video operations (async)\nconst input = $input.first().json;\nconst _callback = $('Prepare Config (Async)').first().json._callback;\n\nconst response = {\n success: true,\n execution_mode: 'async',\n job_id: _callback.job_id,\n operation: input.operation,\n video: input.video,\n videoBase64: input.videoBase64,\n model: input.model,\n generationTimeSeconds: input.generationTimeSeconds,\n preset: input.preset,\n metadata: input.metadata,\n _callback\n};\n\nreturn [{ json: response }];"
},
"id": "format-response-async",
"name": "Format Response (Async)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2032,
192
]
},
{
"parameters": {
"jsCode": "// Prepare callback payload for success\nconst input = $input.first().json;\nconst _callback = input._callback;\n\n// Remove internal _callback from the payload\nconst { _callback: _, ...data } = input;\n\nconst body = {\n success: true,\n job_id: _callback.job_id,\n status: 'completed',\n data\n};\n\nreturn [{\n json: {\n body,\n callback_url: _callback.callback_url\n }\n}];"
},
"id": "prepare-callback-payload",
"name": "Prepare Callback Payload",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2256,
136
]
},
{
"parameters": {
"jsCode": "// Compute HMAC signature (RFC-040 format)\nconst crypto = require('crypto');\nconst input = $input.first().json;\n\nconst secret = $env.N8N_WEBHOOK_SECRET || 'default-secret';\nconst payload = JSON.stringify(input.body);\n\n// Sign body only, output hex WITHOUT prefix\nconst signature = crypto.createHmac('sha256', secret).update(payload).digest('hex');\n\nreturn [{\n json: {\n ...input,\n signature\n }\n}];"
},
"id": "compute-hmac",
"name": "Compute HMAC Signature",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2480,
136
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $json.callback_url }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "X-N8N-Signature",
"value": "={{ $json.signature }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json.body }}",
"options": {
"timeout": 30000
}
},
"id": "callback-success",
"name": "Callback Success",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2704,
136
]
},
{
"parameters": {
"jsCode": "// Prepare error callback payload\nconst _callback = $('Prepare Config (Async)').first().json._callback;\n\n// Get error information\nlet errorMessage = 'Unknown error occurred';\nlet errorDetails = {};\n\ntry {\n const input = $input.first().json;\n if (input.error) {\n errorMessage = input.error.message || input.error;\n errorDetails = input.error;\n } else if (input.message) {\n errorMessage = input.message;\n }\n} catch (e) {\n errorMessage = 'Video generation failed';\n}\n\nconst body = {\n success: false,\n job_id: _callback.job_id,\n status: 'failed',\n error: {\n message: errorMessage,\n details: errorDetails\n }\n};\n\nreturn [{\n json: {\n body,\n callback_url: _callback.callback_url\n }\n}];"
},
"id": "prepare-error-callback",
"name": "Prepare Error Callback",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2032,
360
]
},
{
"parameters": {
"jsCode": "// Compute HMAC signature for error callback (RFC-040 format)\nconst crypto = require('crypto');\nconst input = $input.first().json;\n\nconst secret = $env.N8N_WEBHOOK_SECRET || 'default-secret';\nconst payload = JSON.stringify(input.body);\n\n// Sign body only, output hex WITHOUT prefix\nconst signature = crypto.createHmac('sha256', secret).update(payload).digest('hex');\n\nreturn [{\n json: {\n ...input,\n signature\n }\n}];"
},
"id": "compute-hmac-error",
"name": "Compute HMAC Error",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2256,
360
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $json.callback_url }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "X-N8N-Signature",
"value": "={{ $json.signature }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json.body }}",
"options": {
"timeout": 30000
}
},
"id": "callback-error",
"name": "Callback Error",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2480,
360
]
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "Has required input?",
"type": "main",
"index": 0
}
]
]
},
"Has required input?": {
"main": [
[
{
"node": "Has Callback URL?",
"type": "main",
"index": 0
}
],
[
{
"node": "Error: Missing Input",
"type": "main",
"index": 0
}
]
]
},
"Has Callback URL?": {
"main": [
[
{
"node": "Store Callback Info",
"type": "main",
"index": 0
}
],
[
{
"node": "Prepare Config (Sync)",
"type": "main",
"index": 0
}
]
]
},
"Store Callback Info": {
"main": [
[
{
"node": "Prepare Config (Async)",
"type": "main",
"index": 0
}
]
]
},
"Prepare Config (Sync)": {
"main": [
[
{
"node": "Optimize Prompt? (Sync)",
"type": "main",
"index": 0
}
]
]
},
"Prepare Config (Async)": {
"main": [
[
{
"node": "Optimize Prompt? (Async)",
"type": "main",
"index": 0
}
]
]
},
"Optimize Prompt? (Sync)": {
"main": [
[
{
"node": "Optimize Prompt (Sync)",
"type": "main",
"index": 0
}
],
[
{
"node": "Veo Video (Sync)",
"type": "main",
"index": 0
}
]
]
},
"Optimize Prompt? (Async)": {
"main": [
[
{
"node": "Respond Immediate",
"type": "main",
"index": 0
},
{
"node": "Optimize Prompt (Async)",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond Immediate",
"type": "main",
"index": 0
},
{
"node": "Veo Video (Async)",
"type": "main",
"index": 0
}
]
]
},
"Optimize Prompt (Sync)": {
"main": [
[
{
"node": "Format Optimize Response (Sync)",
"type": "main",
"index": 0
}
]
]
},
"Veo Video (Sync)": {
"main": [
[
{
"node": "Format Response (Sync)",
"type": "main",
"index": 0
}
]
]
},
"Format Optimize Response (Sync)": {
"main": [
[
{
"node": "Respond Success (Sync)",
"type": "main",
"index": 0
}
]
]
},
"Format Response (Sync)": {
"main": [
[
{
"node": "Respond Success (Sync)",
"type": "main",
"index": 0
}
]
]
},
"Respond Immediate": {
"main": [
[]
]
},
"Optimize Prompt (Async)": {
"main": [
[
{
"node": "Format Optimize Response (Async)",
"type": "main",
"index": 0
}
],
[
{
"node": "Prepare Error Callback",
"type": "main",
"index": 0
}
]
]
},
"Veo Video (Async)": {
"main": [
[
{
"node": "Format Response (Async)",
"type": "main",
"index": 0
}
],
[
{
"node": "Prepare Error Callback",
"type": "main",
"index": 0
}
]
]
},
"Format Optimize Response (Async)": {
"main": [
[
{
"node": "Prepare Callback Payload",
"type": "main",
"index": 0
}
]
]
},
"Format Response (Async)": {
"main": [
[
{
"node": "Prepare Callback Payload",
"type": "main",
"index": 0
}
]
]
},
"Prepare Callback Payload": {
"main": [
[
{
"node": "Compute HMAC Signature",
"type": "main",
"index": 0
}
]
]
},
"Compute HMAC Signature": {
"main": [
[
{
"node": "Callback Success",
"type": "main",
"index": 0
}
]
]
},
"Prepare Error Callback": {
"main": [
[
{
"node": "Compute HMAC Error",
"type": "main",
"index": 0
}
]
]
},
"Compute HMAC Error": {
"main": [
[
{
"node": "Callback Error",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"callerPolicy": "workflowsFromSameOwner",
"availableInMCP": false
},
"tags": [],
"activeVersion": {
"updatedAt": "2026-03-16T10:47:24.351Z",
"createdAt": "2026-03-16T10:47:24.351Z",
"versionId": "a61bb1df-69de-4b92-bdca-ba3d580f5ec5",
"workflowId": "wlWgNRY2QBOzWXUY",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "veo-video",
"responseMode": "responseNode",
"options": {}
},
"id": "webhook-trigger",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
304
],
"webhookId": "veo-video-webhook"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "condition-input",
"leftValue": "={{ $json.body.prompt || $json.body.sourceVideo }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "exists"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "check-input",
"name": "Has required input?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
464,
304
]
},
{
"parameters": {
"errorMessage": "={{ \"Missing required input. Provide 'prompt' for most operations or 'sourceVideo' for extendVideo\" }}"
},
"id": "error-no-input",
"name": "Error: Missing Input",
"type": "n8n-nodes-base.stopAndError",
"typeVersion": 1,
"position": [
688,
500
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "condition-callback",
"leftValue": "={{ $json.body.callback_url }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "exists"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "check-callback",
"name": "Has Callback URL?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
688,
304
]
},
{
"parameters": {
"jsCode": "// Store callback info for async processing\nconst crypto = require('crypto');\nconst input = $('Webhook').first().json;\nconst body = input.body || input;\n\n// Generate or use provided job_id\nconst job_id = body.job_id || crypto.randomUUID();\nconst callback_url = body.callback_url;\n\nreturn [{\n json: {\n ...input,\n _callback: {\n job_id,\n callback_url\n }\n }\n}];"
},
"id": "store-callback",
"name": "Store Callback Info",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
912,
192
]
},
{
"parameters": {
"jsCode": "// Prepare data for Veo Video node (sync mode)\nconst input = $('Webhook').first().json;\nconst body = input.body || input;\n\n// Get operation from request (default: generateFromText)\nconst operation = body.operation || 'generateFromText';\n\n// Video parameters\nconst model = body.model || 'veo-3.1-generate-001';\nconst durationSeconds = body.durationSeconds || 6;\nconst aspectRatio = body.aspectRatio || '16:9';\nconst resolution = body.resolution || '1080p';\nconst generateAudio = body.generateAudio !== false;\nconst enhancePrompt = body.enhancePrompt !== false;\nconst personGeneration = body.personGeneration || 'allow_adult';\nconst preset = body.preset || 'none';\n\n// Build result\nconst result = {\n operation,\n prompt: body.prompt || '',\n model,\n durationSeconds,\n aspectRatio,\n resolution,\n generateAudio,\n enhancePrompt,\n personGeneration,\n preset,\n originalRequest: { ...body }\n};\n\n// For generateFromImage\nif (operation === 'generateFromImage') {\n result.sourceImage = body.sourceImage || '';\n result.sourceImageMimeType = body.sourceImageMimeType || 'image/png';\n}\n\n// For generateLongVideo\nif (operation === 'generateLongVideo') {\n result.targetDuration = body.targetDuration || 30;\n}\n\n// For extendVideo\nif (operation === 'extendVideo') {\n result.sourceVideo = body.sourceVideo || '';\n result.extensionPrompt = body.extensionPrompt || '';\n}\n\nreturn [{ json: result }];"
},
"id": "prepare-config-sync",
"name": "Prepare Config (Sync)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
912,
416
]
},
{
"parameters": {
"jsCode": "// Prepare data for Veo Video node (async mode)\nconst input = $input.first().json;\nconst body = input.body || input;\nconst _callback = input._callback;\n\n// Get operation from request (default: generateFromText)\nconst operation = body.operation || 'generateFromText';\n\n// Video parameters\nconst model = body.model || 'veo-3.1-generate-001';\nconst durationSeconds = body.durationSeconds || 6;\nconst aspectRatio = body.aspectRatio || '16:9';\nconst resolution = body.resolution || '1080p';\nconst generateAudio = body.generateAudio !== false;\nconst enhancePrompt = body.enhancePrompt !== false;\nconst personGeneration = body.personGeneration || 'allow_adult';\nconst preset = body.preset || 'none';\n\n// Build result\nconst result = {\n operation,\n prompt: body.prompt || '',\n model,\n durationSeconds,\n aspectRatio,\n resolution,\n generateAudio,\n enhancePrompt,\n personGeneration,\n preset,\n originalRequest: { ...body },\n _callback\n};\n\n// For generateFromImage\nif (operation === 'generateFromImage') {\n result.sourceImage = body.sourceImage || '';\n result.sourceImageMimeType = body.sourceImageMimeType || 'image/png';\n}\n\n// For generateLongVideo\nif (operation === 'generateLongVideo') {\n result.targetDuration = body.targetDuration || 30;\n}\n\n// For extendVideo\nif (operation === 'extendVideo') {\n result.sourceVideo = body.sourceVideo || '';\n result.extensionPrompt = body.extensionPrompt || '';\n}\n\nreturn [{ json: result }];"
},
"id": "prepare-config-async",
"name": "Prepare Config (Async)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1136,
192
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "condition-optimize-sync",
"leftValue": "={{ $json.operation }}",
"rightValue": "optimizePrompt",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "route-operation-sync",
"name": "Optimize Prompt? (Sync)",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1136,
416
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "condition-optimize-async",
"leftValue": "={{ $json.operation }}",
"rightValue": "optimizePrompt",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "route-operation-async",
"name": "Optimize Prompt? (Async)",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1360,
192
]
},
{
"parameters": {
"operation": "optimizePrompt",
"prompt": "={{ $json.prompt }}",
"preset": "={{ $json.preset }}"
},
"id": "veo-optimize-sync",
"name": "Optimize Prompt (Sync)",
"type": "n8n-nodes-veo-video.veoVideo",
"typeVersion": 1,
"position": [
1360,
304
],
"credentials": {
"googleVertexAiApi": {
"id": "Yis7YQJ8FMrvHGzl",
"name": "Google Vertex AI account"
}
}
},
{
"parameters": {
"operation": "={{ $json.operation }}",
"prompt": "={{ $json.prompt }}",
"extensionPrompt": "={{ $json.extensionPrompt }}",
"sourceVideo": "={{ $json.sourceVideo }}",
"sourceImage": "={{ $json.sourceImage }}",
"sourceImageMimeType": "={{ $json.sourceImageMimeType }}",
"preset": "={{ $json.preset }}",
"model": "={{ $json.model }}",
"targetDuration": "={{ $json.targetDuration }}",
"durationSeconds": "={{ $json.durationSeconds }}",
"aspectRatio": "={{ $json.aspectRatio }}",
"resolution": "={{ $json.resolution }}",
"generateAudio": "={{ $json.generateAudio }}",
"enhancePrompt": "={{ $json.enhancePrompt }}",
"options": {
"personGeneration": "={{ $json.personGeneration }}"
}
},
"id": "veo-video-sync",
"name": "Veo Video (Sync)",
"type": "n8n-nodes-veo-video.veoVideo",
"typeVersion": 1,
"position": [
1360,
528
],
"credentials": {
"googleVertexAiApi": {
"id": "Yis7YQJ8FMrvHGzl",
"name": "Google Vertex AI account"
}
}
},
{
"parameters": {
"jsCode": "// Format response for optimizePrompt (sync)\nconst input = $input.first().json;\n\nconst response = {\n success: true,\n execution_mode: 'sync',\n operation: 'optimizePrompt',\n originalPrompt: input.originalPrompt,\n optimizedPrompt: input.optimizedPrompt,\n keywordsAdded: input.keywordsAdded,\n preset: input.preset,\n metadata: input.metadata\n};\n\nreturn [{ json: response }];"
},
"id": "format-optimize-response-sync",
"name": "Format Optimize Response (Sync)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1584,
304
]
},
{
"parameters": {
"jsCode": "// Format response for video operations (sync)\nconst input = $input.first().json;\n\nconst response = {\n success: true,\n execution_mode: 'sync',\n operation: input.operation,\n video: input.video,\n videoBase64: input.videoBase64,\n model: input.model,\n generationTimeSeconds: input.generationTimeSeconds,\n preset: input.preset,\n metadata: input.metadata\n};\n\nreturn [{ json: response }];"
},
"id": "format-response-sync",
"name": "Format Response (Sync)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1584,
528
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"id": "respond-success-sync",
"name": "Respond Success (Sync)",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1808,
416
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ { success: true, job_id: $json._callback.job_id, status: 'processing', message: 'Video generation started' } }}",
"options": {
"responseCode": 202
}
},
"id": "respond-immediate",
"name": "Respond Immediate",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1584,
80
]
},
{
"parameters": {
"operation": "optimizePrompt",
"prompt": "={{ $json.prompt }}",
"preset": "={{ $json.preset }}"
},
"id": "veo-optimize-async",
"name": "Optimize Prompt (Async)",
"type": "n8n-nodes-veo-video.veoVideo",
"typeVersion": 1,
"position": [
1808,
80
],
"credentials": {
"googleVertexAiApi": {
"id": "Yis7YQJ8FMrvHGzl",
"name": "Google Vertex AI account"
}
},
"onError": "continueErrorOutput"
},
{
"parameters": {
"operation": "={{ $json.operation }}",
"prompt": "={{ $json.prompt }}",
"extensionPrompt": "={{ $json.extensionPrompt }}",
"sourceVideo": "={{ $json.sourceVideo }}",
"sourceImage": "={{ $json.sourceImage }}",
"sourceImageMimeType": "={{ $json.sourceImageMimeType }}",
"preset": "={{ $json.preset }}",
"model": "={{ $json.model }}",
"targetDuration": "={{ $json.targetDuration }}",
"durationSeconds": "={{ $json.durationSeconds }}",
"aspectRatio": "={{ $json.aspectRatio }}",
"resolution": "={{ $json.resolution }}",
"generateAudio": "={{ $json.generateAudio }}",
"enhancePrompt": "={{ $json.enhancePrompt }}",
"options": {
"personGeneration": "={{ $json.personGeneration }}"
}
},
"id": "veo-video-async",
"name": "Veo Video (Async)",
"type": "n8n-nodes-veo-video.veoVideo",
"typeVersion": 1,
"position": [
1808,
192
],
"credentials": {
"googleVertexAiApi": {
"id": "Yis7YQJ8FMrvHGzl",
"name": "Google Vertex AI account"
}
},
"onError": "continueErrorOutput"
},
{
"parameters": {
"jsCode": "// Format response for optimizePrompt (async)\nconst input = $input.first().json;\nconst _callback = $('Prepare Config (Async)').first().json._callback;\n\nconst response = {\n success: true,\n execution_mode: 'async',\n job_id: _callback.job_id,\n operation: 'optimizePrompt',\n originalPrompt: input.originalPrompt,\n optimizedPrompt: input.optimizedPrompt,\n keywordsAdded: input.keywordsAdded,\n preset: input.preset,\n metadata: input.metadata,\n _callback\n};\n\nreturn [{ json: response }];"
},
"id": "format-optimize-response-async",
"name": "Format Optimize Response (Async)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2032,
80
]
},
{
"parameters": {
"jsCode": "// Format response for video operations (async)\nconst input = $input.first().json;\nconst _callback = $('Prepare Config (Async)').first().json._callback;\n\nconst response = {\n success: true,\n execution_mode: 'async',\n job_id: _callback.job_id,\n operation: input.operation,\n video: input.video,\n videoBase64: input.videoBase64,\n model: input.model,\n generationTimeSeconds: input.generationTimeSeconds,\n preset: input.preset,\n metadata: input.metadata,\n _callback\n};\n\nreturn [{ json: response }];"
},
"id": "format-response-async",
"name": "Format Response (Async)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2032,
192
]
},
{
"parameters": {
"jsCode": "// Prepare callback payload for success\nconst input = $input.first().json;\nconst _callback = input._callback;\n\n// Remove internal _callback from the payload\nconst { _callback: _, ...data } = input;\n\nconst body = {\n success: true,\n job_id: _callback.job_id,\n status: 'completed',\n data\n};\n\nreturn [{\n json: {\n body,\n callback_url: _callback.callback_url\n }\n}];"
},
"id": "prepare-callback-payload",
"name": "Prepare Callback Payload",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2256,
136
]
},
{
"parameters": {
"jsCode": "// Compute HMAC signature (RFC-040 format)\nconst crypto = require('crypto');\nconst input = $input.first().json;\n\nconst secret = $env.N8N_WEBHOOK_SECRET || 'default-secret';\nconst payload = JSON.stringify(input.body);\n\n// Sign body only, output hex WITHOUT prefix\nconst signature = crypto.createHmac('sha256', secret).update(payload).digest('hex');\n\nreturn [{\n json: {\n ...input,\n signature\n }\n}];"
},
"id": "compute-hmac",
"name": "Compute HMAC Signature",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2480,
136
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $json.callback_url }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "X-N8N-Signature",
"value": "={{ $json.signature }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json.body }}",
"options": {
"timeout": 30000
}
},
"id": "callback-success",
"name": "Callback Success",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2704,
136
]
},
{
"parameters": {
"jsCode": "// Prepare error callback payload\nconst _callback = $('Prepare Config (Async)').first().json._callback;\n\n// Get error information\nlet errorMessage = 'Unknown error occurred';\nlet errorDetails = {};\n\ntry {\n const input = $input.first().json;\n if (input.error) {\n errorMessage = input.error.message || input.error;\n errorDetails = input.error;\n } else if (input.message) {\n errorMessage = input.message;\n }\n} catch (e) {\n errorMessage = 'Video generation failed';\n}\n\nconst body = {\n success: false,\n job_id: _callback.job_id,\n status: 'failed',\n error: {\n message: errorMessage,\n details: errorDetails\n }\n};\n\nreturn [{\n json: {\n body,\n callback_url: _callback.callback_url\n }\n}];"
},
"id": "prepare-error-callback",
"name": "Prepare Error Callback",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2032,
360
]
},
{
"parameters": {
"jsCode": "// Compute HMAC signature for error callback (RFC-040 format)\nconst crypto = require('crypto');\nconst input = $input.first().json;\n\nconst secret = $env.N8N_WEBHOOK_SECRET || 'default-secret';\nconst payload = JSON.stringify(input.body);\n\n// Sign body only, output hex WITHOUT prefix\nconst signature = crypto.createHmac('sha256', secret).update(payload).digest('hex');\n\nreturn [{\n json: {\n ...input,\n signature\n }\n}];"
},
"id": "compute-hmac-error",
"name": "Compute HMAC Error",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2256,
360
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $json.callback_url }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "X-N8N-Signature",
"value": "={{ $json.signature }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json.body }}",
"options": {
"timeout": 30000
}
},
"id": "callback-error",
"name": "Callback Error",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2480,
360
]
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "Has required input?",
"type": "main",
"index": 0
}
]
]
},
"Has required input?": {
"main": [
[
{
"node": "Has Callback URL?",
"type": "main",
"index": 0
}
],
[
{
"node": "Error: Missing Input",
"type": "main",
"index": 0
}
]
]
},
"Has Callback URL?": {
"main": [
[
{
"node": "Store Callback Info",
"type": "main",
"index": 0
}
],
[
{
"node": "Prepare Config (Sync)",
"type": "main",
"index": 0
}
]
]
},
"Store Callback Info": {
"main": [
[
{
"node": "Prepare Config (Async)",
"type": "main",
"index": 0
}
]
]
},
"Prepare Config (Sync)": {
"main": [
[
{
"node": "Optimize Prompt? (Sync)",
"type": "main",
"index": 0
}
]
]
},
"Prepare Config (Async)": {
"main": [
[
{
"node": "Optimize Prompt? (Async)",
"type": "main",
"index": 0
}
]
]
},
"Optimize Prompt? (Sync)": {
"main": [
[
{
"node": "Optimize Prompt (Sync)",
"type": "main",
"index": 0
}
],
[
{
"node": "Veo Video (Sync)",
"type": "main",
"index": 0
}
]
]
},
"Optimize Prompt? (Async)": {
"main": [
[
{
"node": "Respond Immediate",
"type": "main",
"index": 0
},
{
"node": "Optimize Prompt (Async)",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond Immediate",
"type": "main",
"index": 0
},
{
"node": "Veo Video (Async)",
"type": "main",
"index": 0
}
]
]
},
"Optimize Prompt (Sync)": {
"main": [
[
{
"node": "Format Optimize Response (Sync)",
"type": "main",
"index": 0
}
]
]
},
"Veo Video (Sync)": {
"main": [
[
{
"node": "Format Response (Sync)",
"type": "main",
"index": 0
}
]
]
},
"Format Optimize Response (Sync)": {
"main": [
[
{
"node": "Respond Success (Sync)",
"type": "main",
"index": 0
}
]
]
},
"Format Response (Sync)": {
"main": [
[
{
"node": "Respond Success (Sync)",
"type": "main",
"index": 0
}
]
]
},
"Respond Immediate": {
"main": [
[]
]
},
"Optimize Prompt (Async)": {
"main": [
[
{
"node": "Format Optimize Response (Async)",
"type": "main",
"index": 0
}
],
[
{
"node": "Prepare Error Callback",
"type": "main",
"index": 0
}
]
]
},
"Veo Video (Async)": {
"main": [
[
{
"node": "Format Response (Async)",
"type": "main",
"index": 0
}
],
[
{
"node": "Prepare Error Callback",
"type": "main",
"index": 0
}
]
]
},
"Format Optimize Response (Async)": {
"main": [
[
{
"node": "Prepare Callback Payload",
"type": "main",
"index": 0
}
]
]
},
"Format Response (Async)": {
"main": [
[
{
"node": "Prepare Callback Payload",
"type": "main",
"index": 0
}
]
]
},
"Prepare Callback Payload": {
"main": [
[
{
"node": "Compute HMAC Signature",
"type": "main",
"index": 0
}
]
]
},
"Compute HMAC Signature": {
"main": [
[
{
"node": "Callback Success",
"type": "main",
"index": 0
}
]
]
},
"Prepare Error Callback": {
"main": [
[
{
"node": "Compute HMAC Error",
"type": "main",
"index": 0
}
]
]
},
"Compute HMAC Error": {
"main": [
[
{
"node": "Callback Error",
"type": "main",
"index": 0
}
]
]
}
},
"authors": "Sebbah fsebbah@azy.solutions",
"name": null,
"description": null,
"autosaved": false,
"workflowPublishHistory": []
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleVertexAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
MCP Veo Video. Uses stopAndError, n8n-nodes-veo-video, httpRequest. Webhook trigger; 25 nodes.
Source: https://github.com/fsebbah/n8n-workflows/blob/a70a8c0bb7f7aa573956c52defb94541e9348351/workflows/MCP_Veo_Video.json — 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.
comentarios automaticos. Uses httpRequest, stopAndError. Webhook trigger; 69 nodes.
GiveWP Donations to Beacon. Uses httpRequest, stopAndError. Webhook trigger; 42 nodes.
This workflow receives a webhook with an image-edit prompt, a video-motion prompt, and a caption, generates a stylized image with AtlasCloud (OpenAI GPT Image 2) and a vertical video with AtlasCloud (
Automated Email Verification & Digital Health Card Generator
A robust n8n workflow template that automates the university admission process for colleges, universities, and educational programs. This workflow validates student emails, generates professional PDF