This workflow follows the Datatable → 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 →
{
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "koc-approval",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
0,
64
],
"id": "98ccf50f-e6bb-4909-bd38-0524be579201",
"name": "Webhook"
},
{
"parameters": {
"jsCode": "const body =\n ($json && typeof $json === 'object' && $json.body && typeof $json.body === 'object')\n ? $json.body\n : $json;\n\nconst action = String(body.action || '').trim();\n\nconst headers = ($json && $json.headers && typeof $json.headers === 'object') ? $json.headers : {};\nconst query = ($json && $json.query && typeof $json.query === 'object') ? $json.query : {};\n\nreturn [{ json: { ...body, action, headers, query } }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
224,
64
],
"id": "9bd293aa-04d6-46e3-82b5-c8841bfb650a",
"name": "Normalize input"
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"leftValue": "={{ $json.action }}",
"rightValue": "request",
"operator": {
"type": "string",
"operation": "equals"
},
"id": "d75d8239-7fc4-4f97-af2d-82da37640154"
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Request"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "a9abf42c-c8b6-4f53-8227-d953c57d0fb8",
"leftValue": "={{ $json.action }}",
"rightValue": "status",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Status"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "4aef9034-0e00-4cad-9501-5fbc4589efef",
"leftValue": "={{ $json.action }}",
"rightValue": "notify",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Notify"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.4,
"position": [
448,
48
],
"id": "079e64c4-1dd2-4dbb-8ac8-636f925f050c",
"name": "Switch"
},
{
"parameters": {
"jsCode": "const b = $json;\n\nconst approvalId = String(b.approvalId || '').trim();\nif (!approvalId) throw new Error('approvalId is required');\n\nconst summary = String(b.summary || '').trim();\nconst operation = String(b.operation || '').trim();\nconst target = String(b.target || '').trim();\nconst planPath = String(b.planPath || '').trim();\n\nlet details = String(b.details || '').trim();\nif (details.length > 500) details = details.slice(0, 500) + '\u2026';\n\nconst publicBase = String($env.KOC_PUBLIC_BASE_URL || '').replace(/\\/+$/, '');\nif (!publicBase) throw new Error('KOC_PUBLIC_BASE_URL is required');\n\nconst decisionSecret = String($env.KOC_DECISION_SECRET || '').trim();\nif (!decisionSecret) throw new Error('KOC_DECISION_SECRET is required');\n\nconst approveUrl =\n `${publicBase}/webhook/koc-approval-decision?approvalId=${encodeURIComponent(approvalId)}&decision=approved&token=${encodeURIComponent(decisionSecret)}`;\nconst denyUrl =\n `${publicBase}/webhook/koc-approval-decision?approvalId=${encodeURIComponent(approvalId)}&decision=denied&token=${encodeURIComponent(decisionSecret)}`;\n\nreturn [{\n json: {\n approvalId,\n decision: 'pending',\n approver: '',\n reason: 'awaiting decision',\n createdAt: new Date().toISOString(),\n summary,\n operation,\n target,\n planPath,\n details,\n approveUrl,\n denyUrl,\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
672,
-128
],
"id": "b7f88a90-1834-4a1f-bb7b-563bda621634",
"name": "Store pending + build decision URLs"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "{\n \"approvalId\": \"{{$json.approvalId}}\",\n \"decision\": \"pending\",\n \"approver\": \"\",\n \"reason\": \"awaiting decision\"\n}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
1344,
-128
],
"id": "2a81b5cc-ed1a-46f7-8a7c-dcf52e813798",
"name": "Respond to Webhook"
},
{
"parameters": {
"jsCode": "const approvalId = String($json.approvalId || '').trim();\n\n// \u0412 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u043d\u043e\u0434\u044b Data Tables, \u0437\u0430\u043f\u0438\u0441\u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0432 items[] \u0438\u043b\u0438 \u0432 $json.data.\n// \u0421\u0430\u043c\u044b\u0439 \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431: \u0431\u0435\u0440\u0451\u043c \u043f\u0435\u0440\u0432\u0443\u044e \u0432\u0445\u043e\u0434\u044f\u0449\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c \u043a\u0430\u043a row, \u0438\u043d\u0430\u0447\u0435 pending.\nconst row = items[0]?.json || {};\n\nif (!row || Object.keys(row).length === 0 || !row.approvalId) {\n return [{ json: { approvalId, decision: 'pending', approver: '', reason: 'unknown approval id' } }];\n}\n\nreturn [{\n json: {\n approvalId: String(row.approvalId || approvalId),\n decision: String(row.decision || 'pending'),\n approver: String(row.approver || ''),\n reason: String(row.reason || ''),\n raw: row,\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
896,
64
],
"id": "071c78e8-7a8f-490b-94e3-52a99fc6e263",
"name": "Read status"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
1120,
64
],
"id": "d6a318d2-ff34-47eb-bb8e-ec5cf9b758e2",
"name": "Respond to Webhook1"
},
{
"parameters": {
"jsCode": "const root = items[0].json;\nconst action = root.body && root.body.action || root.action;\n\n// 1. \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0442\u043e\u043a\u0435\u043d\u0430 (\u043e\u0431\u0449\u0430\u044f \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0432\u0435\u0442\u043e\u043a)\nconst got = root.headers && root.headers.authorization;\nconst expected = `Bearer ${$env.KOC_DECISION_SECRET}`;\nif (!got || !expected || got !== expected) {\n return [{ json: { ok: false, message: 'unauthorized', got, expected } }];\n}\n\n// 2. \u0412\u0435\u0442\u043a\u0430 notify \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u043e\u043a\u0435\u043d, \u043d\u0438\u0447\u0435\u0433\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e\nif (action === 'notify') {\n return [{ json: { ok: true, message: 'notify accepted' } }];\n}\n\n// 3. \u0412\u0435\u0442\u043a\u0430 approval \u2014 \u0442\u0443\u0442 \u043d\u0443\u0436\u043d\u044b approvalId \u0438 decision\nconst q = (root && typeof root === 'object' && root.query && typeof root.query === 'object') ? root.query : root;\nconst approvalId = String(q.approvalId || '').trim();\nconst decision = String(q.decision || '').trim().toLowerCase();\n\nif (!approvalId) return [{ json: { ok: false, message: 'missing approvalId' } }];\nif (!['approved', 'denied'].includes(decision)) return [{ json: { ok: false, message: 'invalid decision' } }];\n\n// ...\u043e\u0441\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u043b\u043e\u0433\u0438\u043a\u0430 \u0434\u043b\u044f approval...\nreturn [{ json: { ok: true, approvalId, decision } }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
672,
256
],
"id": "33d10e21-1f5b-4142-a241-29f27b91e40c",
"name": "Normalize notify"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "{\"ok\": true}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
1120,
256
],
"id": "f6655d56-92eb-48fb-be23-206722cc97ad",
"name": "Respond: \u201cnotify response\""
},
{
"parameters": {
"chatId": "={{$env.KOC_TELEGRAM_CHAT_ID}}",
"text": "={{\"<b>K8S AI Agent</b>\\n\" + \"Approval requested: <code>\" + $json.approvalId + \"</code>\\n\\n\" + \"<b>Change</b>\\n\" + $json.operation + \" \u2192 <code>\" + $json.target + \"</code>\\n\\n\" + \"<b>Why</b>\\n\" + ($json.details || \"n/a\") + \"\\n\"}}",
"replyMarkup": "inlineKeyboard",
"inlineKeyboard": {
"rows": [
{
"row": {
"buttons": [
{
"text": "Approve",
"additionalFields": {
"callback_data": "=approve:{{$json.approvalId}}"
}
},
{
"text": "Deny",
"additionalFields": {
"callback_data": "=deny:{{$json.approvalId}}"
}
}
]
}
}
]
},
"additionalFields": {
"appendAttribution": false,
"disable_web_page_preview": true,
"parse_mode": "HTML"
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
1120,
-128
],
"id": "b8421055-fd25-4904-8b13-50a15c06993b",
"name": "Send a text message",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"updates": [
"callback_query"
],
"additionalFields": {}
},
"type": "n8n-nodes-base.telegramTrigger",
"typeVersion": 1.2,
"position": [
0,
480
],
"id": "893486e5-6d12-4714-8776-531c77e9fd98",
"name": "Telegram Trigger",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const u = $json;\nconst cq = u.callback_query;\nif (!cq) return [{ json: { ok: true, kind: \"ignore\" } }];\n\nconst data = String(cq.data || \"\").trim();\nconst callbackQueryId = String(cq.id || \"\").trim();\n\nconst msg = cq.message || {};\nconst chatId = msg.chat?.id;\nconst messageId = msg.message_id;\n\nlet decision = \"\";\nlet approvalId = \"\";\n\nif (data.startsWith(\"approve:\")) {\n decision = \"approved\";\n approvalId = data.slice(\"approve:\".length).trim();\n} else if (data.startsWith(\"deny:\")) {\n decision = \"denied\";\n approvalId = data.slice(\"deny:\".length).trim();\n}\n\nif (!approvalId || !decision) return [{ json: { ok: true, kind: \"ignore\" } }];\n\nreturn [{\n json: { approvalId, decision, callbackQueryId, chatId, messageId }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
224,
480
],
"id": "130affbf-7a25-460c-a4c1-b61b764d315b",
"name": "Parse Callback"
},
{
"parameters": {
"method": "POST",
"url": "=https://api.telegram.org/bot{{$env.KOC_TELEGRAM_BOT_TOKEN}}/answerCallbackQuery",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "callback_query_id",
"value": "={{ $('Parse Callback').item.json.callbackQueryId }}"
},
{
"name": "text",
"value": "={{$json.decision === 'approved' ? 'Approved' : 'Denied'}}"
},
{
"name": "show_alert",
"value": "false"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
672,
480
],
"id": "6e915dfb-25a0-43ee-860a-0287f604a707",
"name": "HTTP Request"
},
{
"parameters": {
"method": "POST",
"url": "=https://api.telegram.org/bot{{$env.KOC_TELEGRAM_BOT_TOKEN}}/editMessageReplyMarkup",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "chat_id",
"value": "={{ $('Parse Callback').item.json.chatId }}"
},
{
"name": "message_id",
"value": "={{ $('Parse Callback').item.json.messageId }}"
},
{
"name": "reply_markup",
"value": "={{ JSON.stringify({ inline_keyboard: [] }) }}"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
896,
480
],
"id": "26eb2fb6-bbee-48cb-a9e3-a0a4b79c46de",
"name": "HTTP Request1"
},
{
"parameters": {
"operation": "upsert",
"dataTableId": {
"__rl": true,
"value": "9VmKMQaUAL5FeUAE",
"mode": "list",
"cachedResultName": "k8s-bot",
"cachedResultUrl": "/projects/oae4C2E1F9eaRsa6/datatables/9VmKMQaUAL5FeUAE"
},
"filters": {
"conditions": [
{
"keyName": "approvalId",
"keyValue": "={{$json.approvalId}}"
}
]
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"approvalId": "={{ $json.approvalId }}",
"decision": "={{ $json.decision }}",
"approver": "={{ $json.approver }}",
"reason": "={{ $json.reason }}",
"summary": "={{ $json.summary }}",
"operation": "={{ $json.operation }}",
"target": "={{ $json.target }}",
"planPath": "={{ $json.planPath }}",
"details": "={{ $json.details }}"
},
"matchingColumns": [],
"schema": [
{
"id": "approvalId",
"displayName": "approvalId",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "decision",
"displayName": "decision",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "approver",
"displayName": "approver",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "reason",
"displayName": "reason",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "summary",
"displayName": "summary",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "operation",
"displayName": "operation",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "target",
"displayName": "target",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "planPath",
"displayName": "planPath",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "details",
"displayName": "details",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
896,
-128
],
"id": "d400366d-b1ad-4b7a-8163-81286170744c",
"name": "Upsert row(s)"
},
{
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"value": "9VmKMQaUAL5FeUAE",
"mode": "list",
"cachedResultName": "k8s-bot",
"cachedResultUrl": "/projects/oae4C2E1F9eaRsa6/datatables/9VmKMQaUAL5FeUAE"
},
"filters": {
"conditions": [
{
"keyName": "approvalId",
"keyValue": "={{$json.approvalId}}"
}
]
},
"limit": 1
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
672,
64
],
"id": "8f6af6f5-ee26-42c0-9ca9-2591c0eee1c4",
"name": "Data Tables(Get)"
},
{
"parameters": {
"operation": "update",
"dataTableId": {
"__rl": true,
"value": "9VmKMQaUAL5FeUAE",
"mode": "list",
"cachedResultName": "k8s-bot",
"cachedResultUrl": "/projects/oae4C2E1F9eaRsa6/datatables/9VmKMQaUAL5FeUAE"
},
"filters": {
"conditions": [
{
"keyName": "approvalId",
"keyValue": "={{ $json.approvalId }}"
}
]
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"reason": "={{\"clicked \" + $json.decision}}",
"approver": "={{$json.callbackQueryId}}",
"decision": "={{$json.decision}}"
},
"matchingColumns": [],
"schema": [
{
"id": "approvalId",
"displayName": "approvalId",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": true
},
{
"id": "decision",
"displayName": "decision",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "approver",
"displayName": "approver",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "reason",
"displayName": "reason",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": false
},
{
"id": "summary",
"displayName": "summary",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": true
},
{
"id": "operation",
"displayName": "operation",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": true
},
{
"id": "target",
"displayName": "target",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": true
},
{
"id": "planPath",
"displayName": "planPath",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": true
},
{
"id": "details",
"displayName": "details",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"readOnly": false,
"removed": true
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
448,
480
],
"id": "3eaa550a-78a1-463f-b71c-f02e6c7d07e9",
"name": "Data Tables: Update row"
},
{
"parameters": {
"chatId": "={{$env.KOC_TELEGRAM_CHAT_ID}}",
"text": "={{$('Switch').item.json.title + \"\\n\" + $('Switch').item.json.body}}",
"additionalFields": {
"appendAttribution": false,
"disable_web_page_preview": true,
"parse_mode": "HTML"
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
896,
256
],
"id": "91031394-8250-40fa-a71e-814883eafd01",
"name": "Send a text message1",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "Normalize input",
"type": "main",
"index": 0
}
]
]
},
"Normalize input": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "Store pending + build decision URLs",
"type": "main",
"index": 0
}
],
[
{
"node": "Data Tables(Get)",
"type": "main",
"index": 0
}
],
[
{
"node": "Normalize notify",
"type": "main",
"index": 0
}
]
]
},
"Store pending + build decision URLs": {
"main": [
[
{
"node": "Upsert row(s)",
"type": "main",
"index": 0
}
]
]
},
"Read status": {
"main": [
[
{
"node": "Respond to Webhook1",
"type": "main",
"index": 0
}
]
]
},
"Normalize notify": {
"main": [
[
{
"node": "Send a text message1",
"type": "main",
"index": 0
}
]
]
},
"Send a text message": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"Telegram Trigger": {
"main": [
[
{
"node": "Parse Callback",
"type": "main",
"index": 0
}
]
]
},
"Parse Callback": {
"main": [
[
{
"node": "Data Tables: Update row",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request": {
"main": [
[
{
"node": "HTTP Request1",
"type": "main",
"index": 0
}
]
]
},
"Upsert row(s)": {
"main": [
[
{
"node": "Send a text message",
"type": "main",
"index": 0
}
]
]
},
"Data Tables(Get)": {
"main": [
[
{
"node": "Read status",
"type": "main",
"index": 0
}
]
]
},
"Data Tables: Update row": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"Send a text message1": {
"main": [
[
{
"node": "Respond: \u201cnotify response\"",
"type": "main",
"index": 0
}
]
]
}
},
"meta": {
"templateCredsSetupCompleted": true
}
}
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.
telegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
N8N-Approval-Workflow. Uses telegram, telegramTrigger, httpRequest, dataTable. Webhook trigger; 18 nodes.
Source: https://github.com/gurgenyegoryan/kube-ops-copilot/blob/9a51b727a5fd893e9692d7225d75364d9caf5551/examples/n8n-approval-workflow.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.
N8N Complete Final. Uses telegramTrigger, dataTable, telegram, mqtt. Event-driven trigger; 58 nodes.
TextMain. Uses telegramTrigger, stopAndError, telegram, httpRequest. Event-driven trigger; 56 nodes.
WF_UNIFIED_LEGAL_AUTOMATION. Uses googleSheets, httpRequest, telegram, telegramTrigger. Webhook trigger; 53 nodes.
Telegram B-1 (Variant A): submit -> finalize in n8n. Uses telegramTrigger, telegram, googleDrive, dataTable. Event-driven trigger; 43 nodes.
AIPA - BMF Work Logging. Uses telegram, telegramTrigger, supabase, httpRequest. Webhook trigger; 19 nodes.