{"updatedAt":"2026-04-24T17:43:30.456Z","createdAt":"2026-04-24T17:43:30.456Z","id":"ZK0bkgVFJyOjH4CF","name":"Approvals — Revoke Handler (GAL-109)","description":null,"active":true,"isArchived":false,"nodes":[{"id":"revoke-webhook","name":"Revoke Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[240,300],"parameters":{"httpMethod":"POST","path":"approval-revoke","responseMode":"responseNode","options":{}},"webhookId":"428d405f-860b-4b07-8861-13b9053116a7"},{"id":"normalize-revoke","name":"Normalize","type":"n8n-nodes-base.set","typeVersion":3.4,"position":[440,300],"parameters":{"mode":"manual","assignments":{"assignments":[{"id":"2eb84ce0-8bb5-4307-8175-74036bd9ff22","name":"idempotency_key","value":"={{ $json.body.idempotency_key }}","type":"string"},{"id":"acefae00-42fa-4b65-8b99-39ef359e8e50","name":"reason","value":"={{ $json.body.reason || 'no reason given' }}","type":"string"}]}}},{"id":"fetch-row","name":"Fetch Row","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[640,300],"parameters":{"method":"GET","url":"=https://jiidzeympaalzljyqvjq.supabase.co/rest/v1/approval_requests?idempotency_key=eq.{{ $json.idempotency_key }}&select=*","authentication":"none","sendHeaders":true,"headerParameters":{"parameters":[{"name":"apikey","value":"={{ $env.SB_LSA_PRO_SR }}"},{"name":"Authorization","value":"=Bearer {{ $env.SB_LSA_PRO_SR }}"},{"name":"Accept-Profile","value":"ops"}]},"options":{"response":{"response":{"neverError":true,"responseFormat":"json"}}}},"alwaysOutputData":true},{"id":"wrap-fetch","name":"Wrap Fetch Result","type":"n8n-nodes-base.code","typeVersion":2,"position":[740,300],"parameters":{"mode":"runOnceForAllItems","language":"javaScript","jsCode":"// n8n's HTTP Request splits JSON arrays into items; each item's json is a single row.\n// With alwaysOutputData=true, a truly empty response still emits 1 item (possibly empty json).\n// Filter by idempotency_key presence to distinguish real rows from the phantom item.\nconst items = $input.all();\nconst rows = items.map(i => i.json).filter(r => r && typeof r === 'object' && r.idempotency_key);\nreturn [{ json: { rows, row_count: rows.length, first_row: rows[0] || null } }];\n"}},{"id":"is-pending","name":"Row Exists & Pending?","type":"n8n-nodes-base.if","typeVersion":2.2,"position":[840,300],"parameters":{"conditions":{"options":{"caseSensitive":true,"typeValidation":"strict"},"conditions":[{"id":"a352b640-92de-4eea-ab32-ab2a704a8da8","leftValue":"={{ $json.row_count }}","rightValue":0,"operator":{"type":"number","operation":"gt"}},{"id":"32d3fc27-df9a-44c5-9f2e-70be95725fae","leftValue":"={{ $json.first_row.status }}","rightValue":"pending","operator":{"type":"string","operation":"equals"}}],"combinator":"and"}}},{"id":"update-revoked","name":"Update to Revoked","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1040,200],"parameters":{"method":"PATCH","url":"=https://jiidzeympaalzljyqvjq.supabase.co/rest/v1/approval_requests?idempotency_key=eq.{{ $('Normalize').item.json.idempotency_key }}&status=eq.pending","authentication":"none","sendHeaders":true,"headerParameters":{"parameters":[{"name":"apikey","value":"={{ $env.SB_LSA_PRO_SR }}"},{"name":"Authorization","value":"=Bearer {{ $env.SB_LSA_PRO_SR }}"},{"name":"Content-Profile","value":"ops"},{"name":"Content-Type","value":"application/json"},{"name":"Prefer","value":"return=representation"}]},"sendBody":true,"bodyParameters":{"parameters":[{"name":"status","value":"revoked"},{"name":"resolved_at","value":"={{ new Date().toISOString() }}"}]},"options":{}}},{"id":"send-revoke-notice","name":"Send Revoke Notice","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1240,200],"parameters":{"method":"POST","url":"=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN_OPS }}/sendMessage","authentication":"none","sendHeaders":true,"headerParameters":{"parameters":[{"name":"Content-Type","value":"application/json"}]},"sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify({chat_id: $env.TELEGRAM_CHAT_ID, text: `\\ud83d\\udd04 Revoked by agent ${$('Wrap Fetch Result').item.json.first_row.agent}: ${$('Wrap Fetch Result').item.json.first_row.description}\\nReason: ${$('Normalize').item.json.reason}`}) }}","options":{}}},{"id":"respond-revoked","name":"Respond: Revoked","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[1440,200],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify({status: \"revoked\", idempotency_key: $('Normalize').item.json.idempotency_key}) }}","options":{"responseCode":200}}},{"id":"respond-conflict","name":"Respond: Conflict","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[1040,400],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify({error: $json.row_count === 0 ? \"not_found\" : \"already_resolved\", current_status: $json.first_row ? $json.first_row.status : null}) }}","options":{"responseCode":409}}}],"connections":{"Revoke Webhook":{"main":[[{"node":"Normalize","type":"main","index":0}]]},"Normalize":{"main":[[{"node":"Fetch Row","type":"main","index":0}]]},"Fetch Row":{"main":[[{"node":"Wrap Fetch Result","type":"main","index":0}]]},"Wrap Fetch Result":{"main":[[{"node":"Row Exists & Pending?","type":"main","index":0}]]},"Row Exists & Pending?":{"main":[[{"node":"Update to Revoked","type":"main","index":0}],[{"node":"Respond: Conflict","type":"main","index":0}]]},"Update to Revoked":{"main":[[{"node":"Send Revoke Notice","type":"main","index":0}]]},"Send Revoke Notice":{"main":[[{"node":"Respond: Revoked","type":"main","index":0}]]}},"settings":{"executionOrder":"v1","saveExecutionProgress":true,"callerPolicy":"workflowsFromSameOwner","availableInMCP":false},"staticData":null,"meta":null,"pinData":null,"versionId":"fd2067c5-a849-439c-86f5-abab811f996e","activeVersionId":"fd2067c5-a849-439c-86f5-abab811f996e","versionCounter":3,"triggerCount":1,"shared":[{"updatedAt":"2026-04-24T17:43:30.458Z","createdAt":"2026-04-24T17:43:30.458Z","role":"workflow:owner","workflowId":"ZK0bkgVFJyOjH4CF","projectId":"WMTxW1hBsgAEIsg6","project":{"updatedAt":"2026-02-16T08:56:00.695Z","createdAt":"2026-02-16T07:50:49.259Z","id":"WMTxW1hBsgAEIsg6","name":"Joao Galhardo <jgalhardo.pt@gmail.com>","type":"personal","icon":null,"description":null,"creatorId":"0487392a-ff85-4881-beb2-1ef86afbe010"}}],"tags":[],"activeVersion":{"updatedAt":"2026-04-24T17:43:30.460Z","createdAt":"2026-04-24T17:43:30.460Z","versionId":"fd2067c5-a849-439c-86f5-abab811f996e","workflowId":"ZK0bkgVFJyOjH4CF","nodes":[{"id":"revoke-webhook","name":"Revoke Webhook","type":"n8n-nodes-base.webhook","typeVersion":2,"position":[240,300],"parameters":{"httpMethod":"POST","path":"approval-revoke","responseMode":"responseNode","options":{}},"webhookId":"428d405f-860b-4b07-8861-13b9053116a7"},{"id":"normalize-revoke","name":"Normalize","type":"n8n-nodes-base.set","typeVersion":3.4,"position":[440,300],"parameters":{"mode":"manual","assignments":{"assignments":[{"id":"2eb84ce0-8bb5-4307-8175-74036bd9ff22","name":"idempotency_key","value":"={{ $json.body.idempotency_key }}","type":"string"},{"id":"acefae00-42fa-4b65-8b99-39ef359e8e50","name":"reason","value":"={{ $json.body.reason || 'no reason given' }}","type":"string"}]}}},{"id":"fetch-row","name":"Fetch Row","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[640,300],"parameters":{"method":"GET","url":"=https://jiidzeympaalzljyqvjq.supabase.co/rest/v1/approval_requests?idempotency_key=eq.{{ $json.idempotency_key }}&select=*","authentication":"none","sendHeaders":true,"headerParameters":{"parameters":[{"name":"apikey","value":"={{ $env.SB_LSA_PRO_SR }}"},{"name":"Authorization","value":"=Bearer {{ $env.SB_LSA_PRO_SR }}"},{"name":"Accept-Profile","value":"ops"}]},"options":{"response":{"response":{"neverError":true,"responseFormat":"json"}}}},"alwaysOutputData":true},{"id":"wrap-fetch","name":"Wrap Fetch Result","type":"n8n-nodes-base.code","typeVersion":2,"position":[740,300],"parameters":{"mode":"runOnceForAllItems","language":"javaScript","jsCode":"// n8n's HTTP Request splits JSON arrays into items; each item's json is a single row.\n// With alwaysOutputData=true, a truly empty response still emits 1 item (possibly empty json).\n// Filter by idempotency_key presence to distinguish real rows from the phantom item.\nconst items = $input.all();\nconst rows = items.map(i => i.json).filter(r => r && typeof r === 'object' && r.idempotency_key);\nreturn [{ json: { rows, row_count: rows.length, first_row: rows[0] || null } }];\n"}},{"id":"is-pending","name":"Row Exists & Pending?","type":"n8n-nodes-base.if","typeVersion":2.2,"position":[840,300],"parameters":{"conditions":{"options":{"caseSensitive":true,"typeValidation":"strict"},"conditions":[{"id":"a352b640-92de-4eea-ab32-ab2a704a8da8","leftValue":"={{ $json.row_count }}","rightValue":0,"operator":{"type":"number","operation":"gt"}},{"id":"32d3fc27-df9a-44c5-9f2e-70be95725fae","leftValue":"={{ $json.first_row.status }}","rightValue":"pending","operator":{"type":"string","operation":"equals"}}],"combinator":"and"}}},{"id":"update-revoked","name":"Update to Revoked","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1040,200],"parameters":{"method":"PATCH","url":"=https://jiidzeympaalzljyqvjq.supabase.co/rest/v1/approval_requests?idempotency_key=eq.{{ $('Normalize').item.json.idempotency_key }}&status=eq.pending","authentication":"none","sendHeaders":true,"headerParameters":{"parameters":[{"name":"apikey","value":"={{ $env.SB_LSA_PRO_SR }}"},{"name":"Authorization","value":"=Bearer {{ $env.SB_LSA_PRO_SR }}"},{"name":"Content-Profile","value":"ops"},{"name":"Content-Type","value":"application/json"},{"name":"Prefer","value":"return=representation"}]},"sendBody":true,"bodyParameters":{"parameters":[{"name":"status","value":"revoked"},{"name":"resolved_at","value":"={{ new Date().toISOString() }}"}]},"options":{}}},{"id":"send-revoke-notice","name":"Send Revoke Notice","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1240,200],"parameters":{"method":"POST","url":"=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN_OPS }}/sendMessage","authentication":"none","sendHeaders":true,"headerParameters":{"parameters":[{"name":"Content-Type","value":"application/json"}]},"sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify({chat_id: $env.TELEGRAM_CHAT_ID, text: `\\ud83d\\udd04 Revoked by agent ${$('Wrap Fetch Result').item.json.first_row.agent}: ${$('Wrap Fetch Result').item.json.first_row.description}\\nReason: ${$('Normalize').item.json.reason}`}) }}","options":{}}},{"id":"respond-revoked","name":"Respond: Revoked","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[1440,200],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify({status: \"revoked\", idempotency_key: $('Normalize').item.json.idempotency_key}) }}","options":{"responseCode":200}}},{"id":"respond-conflict","name":"Respond: Conflict","type":"n8n-nodes-base.respondToWebhook","typeVersion":1.1,"position":[1040,400],"parameters":{"respondWith":"json","responseBody":"={{ JSON.stringify({error: $json.row_count === 0 ? \"not_found\" : \"already_resolved\", current_status: $json.first_row ? $json.first_row.status : null}) }}","options":{"responseCode":409}}}],"connections":{"Revoke Webhook":{"main":[[{"node":"Normalize","type":"main","index":0}]]},"Normalize":{"main":[[{"node":"Fetch Row","type":"main","index":0}]]},"Fetch Row":{"main":[[{"node":"Wrap Fetch Result","type":"main","index":0}]]},"Wrap Fetch Result":{"main":[[{"node":"Row Exists & Pending?","type":"main","index":0}]]},"Row Exists & Pending?":{"main":[[{"node":"Update to Revoked","type":"main","index":0}],[{"node":"Respond: Conflict","type":"main","index":0}]]},"Update to Revoked":{"main":[[{"node":"Send Revoke Notice","type":"main","index":0}]]},"Send Revoke Notice":{"main":[[{"node":"Respond: Revoked","type":"main","index":0}]]}},"authors":"Joao Galhardo","name":null,"description":null,"autosaved":false,"workflowPublishHistory":[{"createdAt":"2026-04-24T17:43:30.537Z","id":106,"workflowId":"ZK0bkgVFJyOjH4CF","versionId":"fd2067c5-a849-439c-86f5-abab811f996e","event":"activated","userId":"0487392a-ff85-4881-beb2-1ef86afbe010"}]}}