This workflow follows the HTTP Request → Telegram 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": "GEMINI 3.1 GENERATE v2",
"nodes": [
{
"parameters": {},
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
28992,
20176
],
"id": "9181a364-0ab8-4fff-9b66-3cd6e9adba90",
"name": "When clicking \u2018Execute workflow\u2019"
},
{
"parameters": {
"httpMethod": "POST",
"path": "porto-tools-image",
"authentication": "headerAuth",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
28992,
20416
],
"id": "74055aca-c88a-47bf-9bb1-e5a5d79b7863",
"name": "Webhook (PORTO)",
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "projectId",
"name": "projectId",
"value": "={{ $env.GCP_PROJECT_ID || 'project-8fa51551-4f6c-4ff6-8b7' }}",
"type": "string"
},
{
"id": "accessToken",
"name": "accessToken",
"value": "={{ $env.GCP_ACCESS_TOKEN }}",
"type": "string"
},
{
"id": "prompt",
"name": "prompt",
"value": "Generate a premium product image on a clean studio background. High detail, realistic lighting, sharp focus.",
"type": "string"
},
{
"id": "aspectRatio",
"name": "aspectRatio",
"value": "1:1",
"type": "string"
},
{
"id": "source",
"name": "source",
"value": "manual",
"type": "string"
},
{
"id": "requestId",
"name": "requestId",
"value": "={{ $now.toISO() }}",
"type": "string"
},
{
"id": "references",
"name": "references",
"value": "={{ [] }}",
"type": "array"
},
{
"id": "referenceMapping",
"name": "referenceMapping",
"value": "={{ ({}) }}",
"type": "object"
}
]
},
"options": {}
},
"id": "5138585b-c3ae-4387-aa82-db02b30067b7",
"name": "Manual Config",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
29312,
20176
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "projectId",
"name": "projectId",
"value": "={{ $env.GCP_PROJECT_ID || 'project-8fa51551-4f6c-4ff6-8b7' }}",
"type": "string"
},
{
"id": "accessToken",
"name": "accessToken",
"value": "={{ $env.GCP_ACCESS_TOKEN }}",
"type": "string"
},
{
"id": "prompt",
"name": "prompt",
"value": "={{ $json.body.prompt }}",
"type": "string"
},
{
"id": "aspectRatio",
"name": "aspectRatio",
"value": "={{ $json.body.aspectRatio || '1:1' }}",
"type": "string"
},
{
"id": "source",
"name": "source",
"value": "porto-web",
"type": "string"
},
{
"id": "requestId",
"name": "requestId",
"value": "={{ $json.body.requestId }}",
"type": "string"
},
{
"id": "userEmail",
"name": "userEmail",
"value": "={{ $json.body.userEmail }}",
"type": "string"
},
{
"id": "references",
"name": "references",
"value": "={{ $json.body.references || [] }}",
"type": "array"
},
{
"id": "referenceMapping",
"name": "referenceMapping",
"value": "={{ $json.body.referenceMapping || {} }}",
"type": "object"
}
]
},
"options": {}
},
"id": "99f16994-e25c-4d6e-9041-f7adeefddabc",
"name": "Webhook Config",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
29312,
20416
]
},
{
"parameters": {
"jsCode": "const refs = Array.isArray($json.references) ? $json.references : [];\nconst downloaded = [];\nfor (const r of refs) {\n if (!r || typeof r.url !== 'string') continue;\n const buf = await this.helpers.httpRequest({\n method: 'GET',\n url: r.url,\n encoding: 'arraybuffer',\n returnFullResponse: false,\n });\n const base64 = Buffer.from(buf).toString('base64');\n downloaded.push({ mimeType: r.mimeType || 'image/png', data: base64 });\n}\nreturn [{ json: { ...$json, refsBase64: downloaded } }];"
},
"id": "c3a91e2f-6b7d-4e8a-9c1f-2d4e5f6a7b8c",
"name": "Download References",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
29504,
20304
]
},
{
"parameters": {
"method": "POST",
"url": "=https://aiplatform.googleapis.com/v1/projects/{{$json.projectId}}/locations/global/publishers/google/models/gemini-3.1-flash-image-preview:generateContent",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{$json.accessToken}}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ (() => { const refs = Array.isArray($json.refsBase64) ? $json.refsBase64 : []; let p = $json.prompt || ''; const map = $json.referenceMapping || {}; for (const [token, idx] of Object.entries(map)) { const num = Number(idx) + 1; p = p.split(token).join('image #' + num); } const parts = refs.map(r => ({ inlineData: { mimeType: r.mimeType, data: r.data } })); parts.push({ text: p }); return { contents: [{ role: 'user', parts }], generationConfig: { responseModalities: ['TEXT', 'IMAGE'], imageConfig: { aspectRatio: $json.aspectRatio } } }; })() }}",
"options": {}
},
"id": "a184784f-f933-4b71-979e-5a95e7d88d76",
"name": "Vertex Gemini 3.1 Image",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
29632,
20304
],
"credentials": {
"googleApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const upstream = $('Vertex Gemini 3.1 Image').first().json;\n\nfunction readConfigNode(nodeName) {\n try {\n const item = $(nodeName).first().json;\n return item?.source ? item : null;\n } catch (error) {\n return null;\n }\n}\n\nfunction asObject(value) {\n return value && typeof value === 'object' && !Array.isArray(value) ? value : null;\n}\n\nfunction asArray(value) {\n return Array.isArray(value) ? value : [];\n}\n\nfunction firstString(...values) {\n for (const value of values) {\n if (typeof value === 'string' && value.trim()) return value.trim();\n }\n return '';\n}\n\nfunction collectParts(response) {\n const candidates = asArray(response?.candidates);\n const parts = [];\n\n for (const candidate of candidates) {\n parts.push(...asArray(candidate?.content?.parts));\n parts.push(...asArray(candidate?.content?.role?.parts));\n }\n\n parts.push(...asArray(response?.content?.parts));\n parts.push(...asArray(response?.parts));\n\n return parts.filter(Boolean);\n}\n\nfunction readInlineData(part) {\n const inlineData = asObject(part?.inlineData) || asObject(part?.inline_data);\n if (!inlineData) return null;\n\n const rawData = firstString(inlineData.data, inlineData.imageBase64, inlineData.base64);\n if (!rawData) return null;\n\n const dataUrlMatch = rawData.match(/^data:([^;]+);base64,(.+)$/);\n const mimeType = firstString(\n inlineData.mimeType,\n inlineData.mime_type,\n dataUrlMatch?.[1],\n 'image/png',\n );\n const imageBase64 = (dataUrlMatch?.[2] || rawData).replace(/\\s+/g, '');\n\n return { mimeType, imageBase64 };\n}\n\nfunction isProbablyBase64(value) {\n return /^[A-Za-z0-9+/]+={0,2}$/.test(value) && value.length % 4 === 0;\n}\n\nfunction extensionFromMime(mimeType) {\n if (mimeType.includes('jpeg') || mimeType.includes('jpg')) return 'jpg';\n if (mimeType.includes('webp')) return 'webp';\n return 'png';\n}\n\nconst sourceItem = readConfigNode('Webhook Config') || readConfigNode('Manual Config') || {};\nconst parts = collectParts(upstream);\nconst imagePart = parts.find((part) => readInlineData(part));\nconst textPart = parts.find((part) => typeof part?.text === 'string' && part.text.trim());\n\nif (!imagePart) {\n const candidateCount = asArray(upstream?.candidates).length;\n const partKeys = parts.map((part) => Object.keys(asObject(part) || {}).join('|')).filter(Boolean);\n throw new Error(\n 'Tidak ada inline image dari Gemini response. candidates=' + candidateCount +\n '; parts=' + parts.length +\n '; partKeys=' + (partKeys.join(', ') || 'none') +\n '. Pastikan responseModalities berisi IMAGE dan model mendukung image output.'\n );\n}\n\nconst inline = readInlineData(imagePart);\nif (!inline?.imageBase64) {\n throw new Error('Inline image ditemukan, tetapi field data/base64 kosong.');\n}\n\nif (!isProbablyBase64(inline.imageBase64)) {\n throw new Error('Inline image data bukan base64 valid. Cek output Gemini/HTTP node.');\n}\n\nconst imageBytes = Buffer.from(inline.imageBase64, 'base64');\nif (!imageBytes.length) {\n throw new Error('Inline image base64 valid tetapi menghasilkan file kosong.');\n}\n\nconst mimeType = inline.mimeType;\nconst extension = extensionFromMime(mimeType);\nconst requestId = firstString(sourceItem.requestId, $execution.id, Date.now().toString());\n\nreturn [{\n json: {\n ok: true,\n model: 'gemini-3.1-flash-image-preview',\n mimeType,\n imageBase64: inline.imageBase64,\n imageSizeBytes: imageBytes.length,\n text: textPart?.text ?? '',\n source: sourceItem.source || 'manual',\n requestId,\n aspectRatio: sourceItem.aspectRatio || '1:1',\n userEmail: sourceItem.userEmail || null,\n },\n binary: {\n image: {\n data: inline.imageBase64,\n mimeType,\n fileName: 'gemini-3-1-output-' + requestId + '.' + extension,\n fileExtension: extension,\n fileSize: imageBytes.length,\n },\n },\n}];"
},
"id": "b7a545d7-1ae8-49af-a4f8-67bf068f0e08",
"name": "Convert Image to Binary",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
29888,
20304
]
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "source-porto-web",
"leftValue": "={{ $json.source }}",
"rightValue": "porto-web",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "porto-web"
}
]
},
"options": {
"fallbackOutput": "extra",
"renameFallbackOutput": "manual"
}
},
"id": "8811a762-1864-4cb7-aeba-181387af6499",
"name": "Route by source",
"type": "n8n-nodes-base.switch",
"typeVersion": 3.2,
"position": [
30144,
20304
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ { ok: true, source: $json.source, requestId: $json.requestId, model: $json.model, aspectRatio: $json.aspectRatio, mimeType: $json.mimeType, imageBase64: $json.imageBase64, imageSizeBytes: $json.imageSizeBytes, text: $json.text || '' } }}",
"options": {
"responseCode": 200
}
},
"id": "ce409c00-0980-4c50-bafd-65119501b3cc",
"name": "Respond to Webhook (PORTO)",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
30432,
20176
]
},
{
"parameters": {
"operation": "sendPhoto",
"chatId": "-1003986112540",
"binaryData": true,
"binaryPropertyName": "image",
"additionalFields": {
"caption": "={{ $json.text || 'Generated with Gemini 3.1 Flash Image Preview' }}"
}
},
"id": "91654968-dfe5-4f36-bdfb-dc233d37f5f0",
"name": "Send Image to Telegram",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
30432,
20416
],
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
}
],
"connections": {
"When clicking \u2018Execute workflow\u2019": {
"main": [
[
{
"node": "Manual Config",
"type": "main",
"index": 0
}
]
]
},
"Webhook (PORTO)": {
"main": [
[
{
"node": "Webhook Config",
"type": "main",
"index": 0
}
]
]
},
"Manual Config": {
"main": [
[
{
"node": "Download References",
"type": "main",
"index": 0
}
]
]
},
"Webhook Config": {
"main": [
[
{
"node": "Download References",
"type": "main",
"index": 0
}
]
]
},
"Download References": {
"main": [
[
{
"node": "Vertex Gemini 3.1 Image",
"type": "main",
"index": 0
}
]
]
},
"Vertex Gemini 3.1 Image": {
"main": [
[
{
"node": "Convert Image to Binary",
"type": "main",
"index": 0
}
]
]
},
"Convert Image to Binary": {
"main": [
[
{
"node": "Route by source",
"type": "main",
"index": 0
}
]
]
},
"Route by source": {
"main": [
[
{
"node": "Respond to Webhook (PORTO)",
"type": "main",
"index": 0
}
],
[
{
"node": "Send Image to Telegram",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1"
},
"versionId": "a70e7b37-37e8-4c67-baaa-8cfdcd53073f",
"meta": {
"templateCredsSetupCompleted": true
},
"id": "I7lkeFnnbHg0nPGP",
"tags": []
}
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.
googleApihttpHeaderAuthtelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
GEMINI 3.1 GENERATE v2. Uses httpRequest, telegram. Event-driven trigger; 10 nodes.
Source: https://github.com/ID-Adi/PORTO/blob/cdf2a32e25bb55475a0d21c66d53e5a14723be20/n8n/Generate_Image_tools.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.
Creators, designers, and developers exploring AI-powered image generation. Automation enthusiasts who want to integrate image creation into n8n workflows. Telegram bot builders looking to add visual A
Monitors multiple technology websites 24/7 Uses AI to read and understand each article Filters out low-quality content automatically. Saves the best articles to Notion with summaries. Sends you Telegr
[](https://www.linkedin.com/in/mosaab-yassir-lafrimi/)[](https://t.me/joevenner)
Transcribe audio messages from Telegram using Google Gemini for free.
A sophisticated Telegram bot that provides AI-powered responses with conversation memory. This template demonstrates how to integrate any AI API service with Telegram, making it easy to swap between d