{
  "name": "Profundis · verified client job notifications",
  "nodes": [
    {
      "id": "signed-notification",
      "name": "Signed notification",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "parameters": {
        "httpMethod": "POST",
        "path": "profundis-client-job-events",
        "responseMode": "responseNode",
        "options": {
          "rawBody": true
        }
      },
      "position": [
        0,
        0
      ],
      "webhookId": "profundis-client-job-events"
    },
    {
      "id": "receiver-configuration",
      "name": "Receiver configuration",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "parameters": {
        "jsCode": "// Configure these UUIDs once. No secret belongs in this node.\nconst organization_id = 'REPLACE_WITH_ORGANIZATION_UUID';\nconst client_id = 'REPLACE_WITH_CLIENT_UUID';\nconst endpoint_id = 'REPLACE_WITH_WEBHOOK_ENDPOINT_UUID';\nconst uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\nif (![organization_id,client_id,endpoint_id].every(x=>uuid.test(x))) throw new Error('Set organization, client and webhook endpoint IDs.');\nconst item=$input.first();\nconst body=await this.helpers.getBinaryDataBuffer(0,'data');\nconst signature=item.json.headers['x-profundis-signature'];\nconst event_id=item.json.headers['x-profundis-event-id'];\nif (body.length>8192 || typeof signature!=='string' || signature.length>256 || !uuid.test(event_id||'')) throw new Error('Invalid notification.');\nreturn [{json:{base:'https://api.profundis.io/api/v2/service/organizations/'+organization_id+'/clients/'+client_id,\n  organization_id,client_id,verification:{endpoint_id,event_id,signature,body:body.toString('utf8')}}}];"
      },
      "position": [
        240,
        0
      ]
    },
    {
      "id": "verify-notification",
      "name": "Verify notification",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "parameters": {
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 3000,
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          },
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "method": "POST",
        "url": "={{ $('Receiver configuration').first().json.base + '/webhook-events/verify' }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.verification }}"
      },
      "position": [
        480,
        0
      ]
    },
    {
      "id": "validate-verified-event",
      "name": "Validate verified event",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "parameters": {
        "jsCode": "const e=$input.first().json,c=$('Receiver configuration').first().json;\nif (e.ignored === true) return [{json:{ignored:true}}];\nif (e.organization_id!==c.organization_id || e.client_id!==c.client_id || e.event_id!==c.verification.event_id || e.resource_id!==e.event_id || !['job.completed','endpoint.test'].includes(e.type)) throw new Error('Unexpected verified event.');\nreturn [{json:e}];"
      },
      "position": [
        720,
        0
      ]
    },
    {
      "id": "result-available",
      "name": "Result available",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "result",
              "leftValue": "={{ $json.type === 'job.completed' && $json.state === 'succeeded' }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "position": [
        960,
        0
      ]
    },
    {
      "id": "fetch-completed-result",
      "name": "Fetch completed result",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "parameters": {
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 3000,
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          },
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "method": "GET",
        "url": "={{ $('Receiver configuration').first().json.base + '/jobs/' + $json.resource_id + '/result' }}"
      },
      "position": [
        1200,
        -100
      ]
    },
    {
      "id": "result-and-event",
      "name": "Result and event",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "parameters": {
        "jsCode": "return [{json:{event:$('Validate verified event').first().json,result:$input.first().json}}];"
      },
      "position": [
        1440,
        -100
      ]
    },
    {
      "id": "event-without-result",
      "name": "Event without result",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "parameters": {
        "jsCode": "return [{json:{event:$input.first().json,result:null}}];"
      },
      "position": [
        1200,
        100
      ]
    },
    {
      "id": "acknowledge-without-data",
      "name": "Acknowledge without data",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.4,
      "parameters": {
        "respondWith": "noData",
        "options": {
          "responseCode": 204
        }
      },
      "position": [
        1680,
        0
      ]
    },
    {
      "id": "keep-authorized-events",
      "name": "Keep authorized events",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "parameters": {
        "jsCode": "return $input.all().filter(item=>item.json.event.ignored !== true);"
      },
      "position": [
        1800,
        0
      ]
    },
    {
      "id": "remove-repeated-events",
      "name": "Remove repeated events",
      "type": "n8n-nodes-base.removeDuplicates",
      "typeVersion": 2,
      "parameters": {
        "operation": "removeItemsSeenInPreviousExecutions",
        "logic": "removeItemsWithAlreadySeenKeyValues",
        "dedupeValue": "={{ $json.event.organization_id + \":\" + $json.event.client_id + \":\" + $json.event.type + \":\" + $json.event.event_id }}",
        "options": {
          "scope": "node",
          "historySize": 10000
        }
      },
      "position": [
        1920,
        0
      ]
    },
    {
      "id": "verified-output",
      "name": "Verified output",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "parameters": {},
      "position": [
        2160,
        0
      ]
    },
    {
      "id": "setup-notes",
      "name": "Setup notes",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "parameters": {
        "content": "# Profundis · signed job notifications\n\nImport inactive. Configure the three UUIDs, then bind the same stored HTTP Header Auth service credential to both HTTP nodes. The service identity must own the jobs it reads.\n\nCreate a client webhook in Profundis subscribed to job.completed, using this workflow’s production webhook URL. Copy its endpoint UUID into Receiver configuration. The signing secret stays in Profundis: the authenticated verification API checks exact bytes, signature, timestamp, endpoint and job access.\n\nOnly verified events reach the output. Replies contain no client data. Repeated event IDs are suppressed using n8n’s last 10,000 IDs; your destination must also use the event ID as its idempotency key. No external destination is configured.",
        "height": 300,
        "width": 950
      },
      "position": [
        240,
        -480
      ]
    }
  ],
  "connections": {
    "Signed notification": {
      "main": [
        [
          {
            "node": "Receiver configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receiver configuration": {
      "main": [
        [
          {
            "node": "Verify notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify notification": {
      "main": [
        [
          {
            "node": "Validate verified event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate verified event": {
      "main": [
        [
          {
            "node": "Result available",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Result available": {
      "main": [
        [
          {
            "node": "Fetch completed result",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Event without result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch completed result": {
      "main": [
        [
          {
            "node": "Result and event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Result and event": {
      "main": [
        [
          {
            "node": "Acknowledge without data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Event without result": {
      "main": [
        [
          {
            "node": "Acknowledge without data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Acknowledge without data": {
      "main": [
        [
          {
            "node": "Keep authorized events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Keep authorized events": {
      "main": [
        [
          {
            "node": "Remove repeated events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove repeated events": {
      "main": [
        [
          {
            "node": "Verified output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "executionTimeout": 30,
    "saveDataSuccessExecution": "none",
    "saveDataErrorExecution": "all",
    "saveManualExecutions": false
  },
  "pinData": {},
  "tags": []
}
