This workflow follows the HTTP Request → Postgres 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": "Escalation Trigger \u2014 /approve /disapprove /requeue",
"nodes": [
{
"id": "etrig2-0001-0001-0001-0001-000000000001",
"name": "Schedule (every 60s)",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
0,
300
],
"parameters": {
"rule": {
"interval": [
{
"field": "seconds",
"secondsInterval": 60
}
]
}
}
},
{
"id": "etrig2-0002-0002-0002-0002-000000000002",
"name": "Get Last Offset",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
220,
300
],
"parameters": {
"operation": "executeQuery",
"query": "SELECT COALESCE((SELECT value FROM telegram_poll_state WHERE key='last_update_id'), 0) AS last_update_id, (SELECT value FROM n8n_config WHERE key='ops_bot_base_url') AS bot_base_url",
"options": {}
},
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"id": "etrig2-0003-0003-0003-0003-000000000003",
"name": "Fetch Updates",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
440,
300
],
"continueOnFail": true,
"parameters": {
"method": "GET",
"url": "={{ $('Get Last Offset').item.json.bot_base_url }}getUpdates",
"authentication": "none",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "offset",
"value": "={{ Number($json.last_update_id) + 1 }}"
},
{
"name": "limit",
"value": "=100"
},
{
"name": "timeout",
"value": "=0"
}
]
},
"options": {
"timeout": 10000
}
}
},
{
"id": "etrig2-0004-0004-0004-0004-000000000004",
"name": "Parse Commands",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
660,
300
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "const items = $input.all();\nif (!items || items.length === 0) return [];\n\nconst resp = items[0].json;\nif (resp.error || !resp.ok || !Array.isArray(resp.result)) return [];\nif (resp.result.length === 0) return [];\n\nconst MAX_UPDATE_ID = Math.max(...resp.result.map(u => u.update_id));\nconst AUTH_CHAT_ID = 'YOUR_TELEGRAM_CHAT_ID';\n\nconst commands = resp.result\n .filter(u =>\n u.message &&\n String(u.message.chat && u.message.chat.id) === AUTH_CHAT_ID &&\n typeof u.message.text === 'string' &&\n u.message.text.trim().startsWith('/')\n )\n .map(u => {\n const parts = u.message.text.trim().split(/\\s+/);\n return {\n json: {\n is_command: true,\n max_update_id: MAX_UPDATE_ID,\n update_id: u.update_id,\n command: (parts[0] || '').toLowerCase(),\n ticketId: parseInt(parts[1]) || 0,\n chatId: String(u.message.chat.id),\n text: u.message.text\n }\n };\n });\n\nif (commands.length === 0) {\n // Updates seen but no commands from authorized chat \u2014 must still advance offset.\n return [{ json: { is_command: false, max_update_id: MAX_UPDATE_ID, command: null, ticketId: 0, chatId: AUTH_CHAT_ID } }];\n}\n\nreturn commands;"
}
},
{
"id": "etrig2-0005-0005-0005-0005-000000000005",
"name": "Update Offset",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
880,
300
],
"parameters": {
"operation": "executeQuery",
"query": "UPDATE telegram_poll_state SET value = GREATEST(value, {{ $json.max_update_id }}::bigint), updated_at = now() WHERE key = 'last_update_id'",
"options": {}
},
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"id": "etrig2-0006-0006-0006-0006-000000000006",
"name": "Has Command?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1100,
300
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "cond-has-command",
"leftValue": "={{ $json.is_command }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
}
}
},
{
"id": "etrig2-0007-0007-0007-0007-000000000007",
"name": "Is /approve?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1320,
300
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "cond-is-approve",
"leftValue": "={{ $json.command }}",
"rightValue": "/approve",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
}
}
},
{
"id": "etrig2-0008-0008-0008-0008-000000000008",
"name": "Approve: set open",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
1540,
180
],
"parameters": {
"operation": "executeQuery",
"query": "WITH approve AS (UPDATE tickets SET status='open', updated_at=now() WHERE id={{ $json.ticketId }} AND status='escalation_pending' RETURNING workspace_id) INSERT INTO logs (workspace_id, component, level, message, ticket_id) SELECT workspace_id, 'escalation_approved', 'info', 'escalation approved via Telegram /approve', {{ $json.ticketId }} FROM approve",
"options": {}
},
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"id": "etrig2-0009-0009-0009-0009-000000000009",
"name": "Confirm: approved",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
1760,
180
],
"parameters": {
"chatId": "={{ $json.chatId }}",
"text": "=\u2705 Ticket #{{ $json.ticketId }} approved for Sonnet escalation. It will be claimed on the next processor cycle.",
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"continueOnFail": true
},
{
"id": "etrig2-0010-0010-0010-0010-000000000010",
"name": "Is /disapprove?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1540,
360
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "cond-is-disapprove",
"leftValue": "={{ $json.command }}",
"rightValue": "/disapprove",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
}
}
},
{
"id": "etrig2-0011-0011-0011-0011-000000000011",
"name": "Disapprove: set failed",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
1760,
300
],
"parameters": {
"operation": "executeQuery",
"query": "UPDATE tickets SET status='failed', last_error='escalation disapproved \u2014 re-scope this ticket for Qwen or raise model_hint manually', updated_at=now() WHERE id={{ $json.ticketId }} AND status='escalation_pending'",
"options": {}
},
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"id": "etrig2-0012-0012-0012-0012-000000000012",
"name": "Confirm: disapproved",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
1980,
300
],
"parameters": {
"chatId": "={{ $json.chatId }}",
"text": "=\u274c Ticket #{{ $json.ticketId }} marked failed. Re-scope for Qwen, or /requeue {{ $json.ticketId }} after editing the spec.",
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"continueOnFail": true
},
{
"id": "etrig2-0013-0013-0013-0013-000000000013",
"name": "Is /requeue?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1760,
480
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "cond-is-requeue",
"leftValue": "={{ $json.command }}",
"rightValue": "/requeue",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
}
}
},
{
"id": "etrig2-0014-0014-0014-0014-000000000014",
"name": "Requeue: reset to Qwen",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
1980,
420
],
"parameters": {
"operation": "executeQuery",
"query": "WITH reset AS (UPDATE tickets SET status='open', attempts=0, updated_at=now() WHERE id={{ $json.ticketId }} AND status='escalation_pending') DELETE FROM logs WHERE component='escalation_pending' AND ticket_id={{ $json.ticketId }}",
"options": {}
},
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"id": "etrig2-0015-0015-0015-0015-000000000015",
"name": "Confirm: requeued",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
2200,
420
],
"parameters": {
"chatId": "={{ $json.chatId }}",
"text": "=\ud83d\udd04 Ticket #{{ $json.ticketId }} returned to Qwen queue (attempts=0, reviewer feedback preserved). Escalation gate re-armed \u2014 it will fire again at attempts=3.",
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"continueOnFail": true
},
{
"id": "etrig2-0016-0016-0016-0016-000000000016",
"name": "Unknown command",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
1980,
600
],
"parameters": {
"chatId": "={{ $json.chatId }}",
"text": "=Unknown command: {{ $json.command }}\n\nValid commands:\n /approve <ticket_id>\n /disapprove <ticket_id>\n /requeue <ticket_id>",
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"continueOnFail": true
}
],
"connections": {
"Schedule (every 60s)": {
"main": [
[
{
"node": "Get Last Offset",
"type": "main",
"index": 0
}
]
]
},
"Get Last Offset": {
"main": [
[
{
"node": "Fetch Updates",
"type": "main",
"index": 0
}
]
]
},
"Fetch Updates": {
"main": [
[
{
"node": "Parse Commands",
"type": "main",
"index": 0
}
]
]
},
"Parse Commands": {
"main": [
[
{
"node": "Update Offset",
"type": "main",
"index": 0
},
{
"node": "Has Command?",
"type": "main",
"index": 0
}
]
]
},
"Update Offset": {
"main": [
[]
]
},
"Has Command?": {
"main": [
[
{
"node": "Is /approve?",
"type": "main",
"index": 0
}
],
[]
]
},
"Is /approve?": {
"main": [
[
{
"node": "Approve: set open",
"type": "main",
"index": 0
}
],
[
{
"node": "Is /disapprove?",
"type": "main",
"index": 0
}
]
]
},
"Approve: set open": {
"main": [
[
{
"node": "Confirm: approved",
"type": "main",
"index": 0
}
]
]
},
"Is /disapprove?": {
"main": [
[
{
"node": "Disapprove: set failed",
"type": "main",
"index": 0
}
],
[
{
"node": "Is /requeue?",
"type": "main",
"index": 0
}
]
]
},
"Disapprove: set failed": {
"main": [
[
{
"node": "Confirm: disapproved",
"type": "main",
"index": 0
}
]
]
},
"Is /requeue?": {
"main": [
[
{
"node": "Requeue: reset to Qwen",
"type": "main",
"index": 0
}
],
[
{
"node": "Unknown command",
"type": "main",
"index": 0
}
]
]
},
"Requeue: reset to Qwen": {
"main": [
[
{
"node": "Confirm: requeued",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"saveManualExecutions": true,
"errorWorkflow": "agentstack-error-handler-v1"
},
"staticData": null,
"id": "agentstack-escalation-trigger-v1",
"active": 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.
postgrestelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Escalation Trigger — /approve /disapprove /requeue. Uses postgres, httpRequest, telegram. Scheduled trigger; 16 nodes.
Source: https://github.com/kevinluaus/aftos-agent-stack/blob/main/n8n/workflow-escalation-trigger.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.
Processor — EXECUTE NEXT (5min, dep-aware) ✅. Uses postgres, httpRequest, telegram. Scheduled trigger; 32 nodes.
Chanchito_PROD. Uses googleGemini, postgres, telegram, httpRequest. Scheduled trigger; 94 nodes.
System Architecture Two integrated N8N workflows providing automated US stock portfolio management through Telegram:
This workflow automatically generates stock market insights for selected tickers (e.g. GAZP, SBER, LKOH) using historical data, technical indicators, and an AI model. The results are then sent to Tele
WF-00 agent_router. Uses postgres, httpRequest, agent, lmChatOpenRouter. Scheduled trigger; 23 nodes.