{
  "name": "REVOPS-LI-006 | 01 | Attribution event capture",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "revops-li-006-conversion",
        "authentication": "headerAuth",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "d35d4025-28a5-4335-866b-751a7f257b87",
      "name": "Conversion Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "webhookId": "5d75a3a1-4b30-4035-b1ab-a808d3945ed1",
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_FORM_SECRET_CREDENTIAL",
          "name": "ATTRIBUTION Form Secret"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const raw=$input.first().json.body;const errors=[];\nif(!raw||typeof raw!=='object'||Array.isArray(raw))return [{json:{valid:false,errors:['JSON object required']}}];\nif(JSON.stringify(raw).length>65536)errors.push('Payload exceeds 64 KiB');\nif(typeof raw.event_id!=='string'||!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(raw.event_id))errors.push('Stable event_id UUID required');\nif(!['website','form','crm','analytics'].includes(raw.producer))errors.push('producer must be website, form, crm or analytics');\nfor(const k of ['submission_id','email','contact_id','visitor_id','session_id','form_id','conversion_timestamp'])if(raw[k]!=null&&typeof raw[k]!=='string')errors.push(k+' must be a string');\nreturn [{json:{valid:!errors.length,errors,raw,tenant:'REVOPS-LI-006'}}];"
      },
      "id": "baae506c-3292-41f1-8d72-7d79d9c0e582",
      "name": "Validate Envelope",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        270,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "5b7e039f-45f3-46ae-91aa-5c2198748e21",
              "leftValue": "={{ $json.valid === true }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a5179fae-b963-4522-89d5-0db40f533bf9",
      "name": "Envelope Valid?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        540,
        0
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT * FROM attribution.enqueue($1::text,$2::uuid,$3::jsonb);",
        "options": {
          "queryReplacement": "={{ [$json.tenant,$json.raw.event_id,JSON.stringify($json.raw)] }}"
        }
      },
      "id": "d946bc9f-ff53-4d84-a053-35af1f6870d8",
      "name": "Persist Raw Event",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        810,
        0
      ],
      "credentials": {
        "postgres": {
          "id": "REPLACE_POSTGRES_CREDENTIAL",
          "name": "ATTRIBUTION Postgres"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ {accepted:$json.accepted,duplicate:!$json.inserted,event_id:$json.event_id} }}",
        "options": {
          "responseCode": "={{ $json.accepted ? 202 : 409 }}"
        }
      },
      "id": "0a581b14-90a9-46b1-8425-1e2839db09af",
      "name": "Acknowledge Capture",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.4,
      "position": [
        1080,
        0
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ {accepted:false,errors:$json.errors} }}",
        "options": {
          "responseCode": 400
        }
      },
      "id": "7ffac936-98f3-4dd3-8997-79cc8f945a4d",
      "name": "Reject Envelope",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.4,
      "position": [
        1350,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "57c27556-ab55-428b-9e13-78a24de03fb6",
              "leftValue": "={{ $('Persist Raw Event').first().json.inserted === true }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a2a2b313-6d21-4ade-8a3c-9831bad6309a",
      "name": "New Event?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1620,
        0
      ]
    },
    {
      "parameters": {
        "source": "database",
        "workflowId": {
          "__rl": true,
          "value": "REPLACE_ATTRIBUTION_WORKER_ID",
          "mode": "id"
        },
        "mode": "once",
        "options": {
          "waitForSubWorkflow": false
        }
      },
      "id": "d03e56c2-0f5b-4309-919f-84cbdfbf0e72",
      "name": "Start Attribution Worker",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.3,
      "position": [
        1890,
        0
      ]
    },
    {
      "parameters": {
        "content": "## Raw capture before cleanup\nUse a trusted form/site backend with Header Auth; no browser secrets.\nevent_id is unique per producer event; submission_id is shared across systems for one conversion.\nThe raw parsed JSON is retained unchanged in PostgreSQL. Reused event_id with different payload returns 409.\nBind Start Attribution Worker after importing workflow 02.",
        "width": 700,
        "height": 220
      },
      "id": "c8223ef7-6cb3-487e-9dba-a6668513e21c",
      "name": "Workflow Guide",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -270
      ]
    }
  ],
  "connections": {
    "Conversion Webhook": {
      "main": [
        [
          {
            "node": "Validate Envelope",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Envelope": {
      "main": [
        [
          {
            "node": "Envelope Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Envelope Valid?": {
      "main": [
        [
          {
            "node": "Persist Raw Event",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Reject Envelope",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Persist Raw Event": {
      "main": [
        [
          {
            "node": "Acknowledge Capture",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Acknowledge Capture": {
      "main": [
        [
          {
            "node": "New Event?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "New Event?": {
      "main": [
        [
          {
            "node": "Start Attribution Worker",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "timezone": "Asia/Tbilisi",
    "saveExecutionProgress": true
  },
  "active": false,
  "pinData": {},
  "tags": []
}

