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": "Telegram HITL Approval Handler",
"nodes": [
{
"parameters": {
"updates": [
"callback_query"
],
"additionalFields": {}
},
"id": "trigger-callback",
"name": "Telegram Trigger",
"type": "n8n-nodes-base.telegramTrigger",
"typeVersion": 1.2,
"position": [
0,
0
],
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"notes": "One consumer per bot token. Only this trigger should listen on callback_query for this bot."
},
{
"parameters": {
"jsCode": "const cq = $input.first().json.callback_query || $input.first().json;\nconst data = cq.data || '';\nconst [decision, ...rest] = data.split(':');\nconst jobId = rest.join(':');\nconst isApprove = decision === 'APPROVE';\nconst isDeny = decision === 'DENY';\nreturn [{ json: {\n callbackId: cq.id,\n chatId: String(cq.message?.chat?.id || ''),\n messageId: cq.message?.message_id,\n messageText: cq.message?.text || cq.message?.caption || '',\n jobId,\n decision,\n isApprove,\n isDeny,\n recognized: isApprove || isDeny\n} }];"
},
"id": "parse-callback",
"name": "Parse Callback",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
220,
0
],
"notes": "Parses APPROVE:<jobId> / DENY:<jobId>."
},
{
"parameters": {
"resource": "callback",
"operation": "answerQuery",
"queryId": "={{ $json.callbackId }}",
"additionalFields": {
"text": "={{ $json.isApprove ? '\u2705 Approved' : ($json.isDeny ? '\u274c Denied' : '\u26a0\ufe0f Unrecognized') }}",
"show_alert": false
}
},
"id": "answer-callback",
"name": "Answer Callback",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
440,
-140
],
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"notes": "Must answer within ~10s or Telegram shows a spinner."
},
{
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $json.isApprove }}",
"value2": true
}
]
},
"options": {}
},
"id": "if-approve",
"name": "Approved?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
440,
120
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $env.APPROVE_ACTION_URL }}",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { jobId: $json.jobId } }}",
"options": {}
},
"id": "http-approve",
"name": "Run Approve Action",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
680,
40
],
"notes": "POSTs { jobId } to APPROVE_ACTION_URL. Put your real action behind this URL."
},
{
"parameters": {
"method": "POST",
"url": "={{ $env.DENY_ACTION_URL || 'https://example.com/noop' }}",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { jobId: $json.jobId } }}",
"options": {}
},
"id": "http-deny",
"name": "Run Deny Action",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
680,
200
],
"onError": "continueRegularOutput",
"notes": "Optional. Set DENY_ACTION_URL to record/cleanup on deny; otherwise this no-ops."
},
{
"parameters": {
"resource": "message",
"operation": "editMessageText",
"chatId": "={{ $('Parse Callback').first().json.chatId }}",
"messageId": "={{ $('Parse Callback').first().json.messageId }}",
"text": "={{ $('Parse Callback').first().json.messageText + '\\n\\n\u2705 Approved \u2014 ' + $('Parse Callback').first().json.jobId }}",
"replyMarkup": "inlineKeyboard",
"inlineKeyboard": {
"rows": []
}
},
"id": "edit-approved",
"name": "Edit Message (Approved)",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
900,
40
],
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"notes": "Clears the inline keyboard so the action can't be repeated."
},
{
"parameters": {
"resource": "message",
"operation": "editMessageText",
"chatId": "={{ $('Parse Callback').first().json.chatId }}",
"messageId": "={{ $('Parse Callback').first().json.messageId }}",
"text": "={{ $('Parse Callback').first().json.messageText + '\\n\\n\u274c Denied \u2014 ' + $('Parse Callback').first().json.jobId }}",
"replyMarkup": "inlineKeyboard",
"inlineKeyboard": {
"rows": []
}
},
"id": "edit-denied",
"name": "Edit Message (Denied)",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
900,
200
],
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
}
],
"connections": {
"Telegram Trigger": {
"main": [
[
{
"node": "Parse Callback",
"type": "main",
"index": 0
}
]
]
},
"Parse Callback": {
"main": [
[
{
"node": "Answer Callback",
"type": "main",
"index": 0
},
{
"node": "Approved?",
"type": "main",
"index": 0
}
]
]
},
"Approved?": {
"main": [
[
{
"node": "Run Approve Action",
"type": "main",
"index": 0
}
],
[
{
"node": "Run Deny Action",
"type": "main",
"index": 0
}
]
]
},
"Run Approve Action": {
"main": [
[
{
"node": "Edit Message (Approved)",
"type": "main",
"index": 0
}
]
]
},
"Run Deny Action": {
"main": [
[
{
"node": "Edit Message (Denied)",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"active": false,
"meta": {
"templateCredsSetupCompleted": false
}
}
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
Telegram HITL Approval Handler. Uses telegramTrigger, telegram, httpRequest. Event-driven trigger; 8 nodes.
Source: https://github.com/sitewisesoftware/n8n-telegram-hitl-handler/blob/main/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.
Pede Ai. Uses httpRequest, telegram, postgres, telegramTrigger. Event-driven trigger; 57 nodes.
TextMain. Uses telegramTrigger, stopAndError, telegram, httpRequest. Event-driven trigger; 56 nodes.
Pede Ai. Uses httpRequest, telegram, postgres, telegramTrigger. Event-driven trigger; 53 nodes.
📄 Documentation: Notion Guide