{
  "id": "LYAnJfzpvewwxal6",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Send Lead from Web to Odoo",
  "tags": [],
  "nodes": [
    {
      "id": "87228205-1f02-474e-a99d-03471805e0d3",
      "name": "Webhook - Lead Webform",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -3344,
        560
      ],
      "parameters": {
        "path": "lead-webform",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode",
        "authentication": "headerAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "27f055d9-7e29-4acd-8d4b-de356b611355",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        -1872,
        544
      ],
      "parameters": {
        "numberInputs": 4
      },
      "typeVersion": 3.2,
      "alwaysOutputData": true
    },
    {
      "id": "dc8702e9-c4c6-487e-a5e0-19a2dda4ad94",
      "name": "Create Lead",
      "type": "n8n-nodes-base.odoo",
      "position": [
        -1504,
        544
      ],
      "parameters": {
        "resource": "custom",
        "customResource": "crm.lead",
        "fieldsToCreateOrUpdate": {
          "fields": [
            {
              "fieldName": "name",
              "fieldValue": "={{ $json.contact_name }}"
            },
            {
              "fieldName": "phone",
              "fieldValue": "={{ $json.phone }}"
            },
            {
              "fieldName": "email_from",
              "fieldValue": "={{ $json.email_from }}"
            },
            {
              "fieldName": "description",
              "fieldValue": "={{ $json.description }}"
            },
            {
              "fieldName": "source_id",
              "fieldValue": "={{ $json.source_id }}"
            },
            {
              "fieldName": "medium_id",
              "fieldValue": "={{ $json.medium_id }}"
            },
            {
              "fieldName": "campaign_id",
              "fieldValue": "={{ $json.campaign_id }}"
            },
            {
              "fieldName": "type",
              "fieldValue": "={{ $json.type }}"
            },
            {
              "fieldName": "contact_name",
              "fieldValue": "={{ $json.contact_name }}"
            }
          ]
        }
      },
      "credentials": {
        "odooApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1c7b3f98-b467-4218-934c-c34ab2efebc8",
      "name": "Prepare Request",
      "type": "n8n-nodes-base.code",
      "position": [
        -1696,
        528
      ],
      "parameters": {
        "jsCode": "// n8n Code node (Run once for all items)\n//\n// Purpose\n// -------\n// Merge the four inputs coming from the previous Merge node into a single,\n// clean JSON ready for Odoo `crm.lead` create.\n//\n// Expected inputs (from Merge):\n// - 1 item from the webhook: { body: { firstname, lastname, email, phone, notes, ... } }\n//   (or, in some setups, those fields may be at the root instead of under `body`)\n// - 1 item with { source_id }\n// - 1 item with { medium_id }\n// - 1 item with { campaign_id }\n//\n// Output:\n// - 1 item: { name, contact_name, email_from, phone, description, type, campaign_id, source_id, medium_id }\n\nfunction s(x) { return (x ?? '').toString().trim(); }\n\nlet payload = null;\nlet source_id = null;\nlet medium_id = null;\nlet campaign_id = null;\n\n// Scan all items coming from Merge and collect what we need\nfor (const it of items) {\n  const j = it.json ?? it;\n\n  // Identify the webhook item.\n  // Prefer `j.body` (typical webhook shape), but fall back to root-level fields just in case.\n  if (j.body || j.firstname || j.lastname || j.email || j.notes || j.phone) {\n    payload = j.body ?? j;\n  }\n\n  // Collect UTM IDs (take the first defined value seen for each)\n  if (j.source_id !== undefined && j.source_id !== null && source_id === null) {\n    const n = Number(j.source_id);\n    source_id = Number.isFinite(n) ? n : null;\n  }\n  if (j.medium_id !== undefined && j.medium_id !== null && medium_id === null) {\n    const n = Number(j.medium_id);\n    medium_id = Number.isFinite(n) ? n : null;\n  }\n  if (j.campaign_id !== undefined && j.campaign_id !== null && campaign_id === null) {\n    const n = Number(j.campaign_id);\n    campaign_id = Number.isFinite(n) ? n : null;\n  }\n}\n\n// Safety check: if the webhook item is missing, fail early with a helpful message\nif (!payload) {\n  throw new Error('Webhook payload not found after Merge. Ensure the IF(false) branch is connected to the Merge.');\n}\n\n// Build the final lead payload for Odoo\nconst first = s(payload.firstname);\nconst last  = s(payload.lastname);\nconst fullName = `${first} ${last}`.trim();\n\nconst out = {\n  // Core lead fields\n  name: fullName || s(payload.email) || 'New Lead',\n  contact_name: fullName,\n  email_from: s(payload.email),\n  phone: s(payload.phone),\n  description: s(payload.notes),\n  type: 'lead',\n\n  // UTM Many2one IDs (nullable if not provided/found)\n  campaign_id,\n  source_id,\n  medium_id,\n};\n\nreturn [{ json: out }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "6299f436-0481-4291-8b70-e62555cfb76b",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1936,
        416
      ],
      "parameters": {
        "color": 4,
        "width": 816,
        "height": 384,
        "content": "## Odoo - Lead Create \nPreparation of the request creating the right body for Lead custom object"
      },
      "typeVersion": 1
    },
    {
      "id": "1bb410d2-86d5-458a-86bb-a5b6a9938df2",
      "name": "Execution Data",
      "type": "n8n-nodes-base.executionData",
      "position": [
        -1296,
        544
      ],
      "parameters": {},
      "typeVersion": 1.1
    },
    {
      "id": "b3c48a34-824e-471a-bea7-a5a1f4ac0933",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3392,
        240
      ],
      "parameters": {
        "color": 5,
        "width": 400,
        "height": 544,
        "content": "## Webhook linked to different sources \nExample:\n{\n    \"firstname\" : \"John\",\n    \"lastname\" : \"Doe\",\n    \"phone\" : \"+393331212123\",\n    \"email\" : \"test@outlook.com\",\n    \"source\" : \"Ads\",\n    \"medium\" : \"Website\",\n    \"notes\" : \"test notes\",\n    \"campaign\" : \"WebMassive\"\n}"
      },
      "typeVersion": 1
    },
    {
      "id": "b4cfdde2-570e-4e0f-bd2f-7f69bfc5fa65",
      "name": "UTM: Get Source ID",
      "type": "n8n-nodes-base.odoo",
      "onError": "continueRegularOutput",
      "position": [
        -2704,
        176
      ],
      "parameters": {
        "limit": 1,
        "options": {
          "fieldsList": [
            "name",
            "id"
          ]
        },
        "resource": "custom",
        "operation": "getAll",
        "filterRequest": {
          "filter": [
            {
              "value": "={{ $json.body.source }}",
              "fieldName": "name"
            }
          ]
        },
        "customResource": "utm.source"
      },
      "credentials": {
        "odooApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "2db2e52d-ff07-4bf0-8f69-7ff99e39a3c4",
      "name": "UTM: Get Medium ID",
      "type": "n8n-nodes-base.odoo",
      "onError": "continueRegularOutput",
      "position": [
        -2704,
        304
      ],
      "parameters": {
        "limit": 1,
        "options": {
          "fieldsList": [
            "name",
            "id"
          ]
        },
        "resource": "custom",
        "operation": "getAll",
        "filterRequest": {
          "filter": [
            {
              "value": "={{ $json.body.medium }}",
              "fieldName": "name"
            }
          ]
        },
        "customResource": "utm.medium"
      },
      "credentials": {
        "odooApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "232bdab3-82c5-454a-866e-4ddb90eea4d7",
      "name": "UTM: Get Campaign ID",
      "type": "n8n-nodes-base.odoo",
      "onError": "continueRegularOutput",
      "position": [
        -2704,
        432
      ],
      "parameters": {
        "limit": 1,
        "options": {
          "fieldsList": [
            "name",
            "id"
          ]
        },
        "resource": "custom",
        "operation": "getAll",
        "filterRequest": {
          "filter": [
            {
              "value": "={{ $json.body.campaign }}",
              "fieldName": "name"
            }
          ]
        },
        "customResource": "utm.campaign"
      },
      "credentials": {
        "odooApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "569cdaf8-08aa-469d-a37e-c876359cc121",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4544,
        240
      ],
      "parameters": {
        "width": 512,
        "height": 672,
        "content": "## How To Use\n\n### Prerequisites\n- **Odoo**: enable Leads (CRM \u2192 Settings \u2192 Leads).\n- **Odoo API Key** for your user (use it as the *password*).\n- **n8n Odoo credentials**: URL, DB name, Login, **API Key**.\n- **Public URL** for the webhook (ngrok/Cloudflare/reverse proxy). Make sure WEBHOOK_URL / N8N_HOST / N8N_PROTOCOL / N8N_PORT are consistent.\n- **Header Auth**: set a secret, e.g. header `x-webhook-token: {{$env.WEBHOOK_SECRET}}`.\n\n### Payload (JSON)\n- **Required**: `firstname`, `lastname`, `email`\n- **Optional**: `phone`, `notes`, `source`, `medium`, `campaign`\n\n### Endpoints\n- **Test**: `/webhook-test/lead-webform`\n- **Prod**: `/webhook/lead-webform`\n\n### Quick test\ncurl -X POST \"https://<host>/webhook-test/lead-webform\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"x-webhook-token: <secret>\" \\\n  -d '{\"firstname\":\"John\",\"lastname\":\"Doe\",\"email\":\"john@ex.com\",\n       \"phone\":\"+39333...\", \"notes\":\"Demo\",\n       \"source\":\"Ads\",\"medium\":\"Website\",\"campaign\":\"Spring 2025\"}'\n"
      },
      "typeVersion": 1
    },
    {
      "id": "45fba007-2cc9-45a9-aafc-d3ba6281cc56",
      "name": "Success",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -1088,
        544
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "noData"
      },
      "typeVersion": 1.4
    },
    {
      "id": "c69cb8d1-78c8-4196-b3c4-384954e7e230",
      "name": "Required data missing?",
      "type": "n8n-nodes-base.if",
      "position": [
        -3120,
        512
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "60ce05fd-ff94-4e59-9ebc-09c058b8f7fb",
              "operator": {
                "type": "string",
                "operation": "notExists",
                "singleValue": true
              },
              "leftValue": "={{ $json.body.firstname }}",
              "rightValue": ""
            },
            {
              "id": "714cc2e5-e781-4fc2-a6af-af3e16a58d21",
              "operator": {
                "type": "string",
                "operation": "notExists",
                "singleValue": true
              },
              "leftValue": "={{ $json.body.lastname }}",
              "rightValue": ""
            },
            {
              "id": "c09b99e1-faf0-44c4-a9e1-2d4192a61c9c",
              "operator": {
                "type": "string",
                "operation": "notExists",
                "singleValue": true
              },
              "leftValue": "={{ $json.body.email }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "e89b2169-b944-407b-a3c5-05b1ed6c66e8",
      "name": "Bad Request",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -2944,
        32
      ],
      "parameters": {
        "options": {
          "responseCode": 400
        },
        "respondWith": "noData"
      },
      "typeVersion": 1.4
    },
    {
      "id": "b3b29fa8-89c6-42b4-9455-f6ed4110416f",
      "name": "Source ID Validation",
      "type": "n8n-nodes-base.code",
      "position": [
        -2512,
        176
      ],
      "parameters": {
        "jsCode": "const f = items[0]?.json;\nreturn [{ json: { source_id: f?.id ?? null } }]; \n"
      },
      "typeVersion": 2
    },
    {
      "id": "8c3852d9-bccc-41d6-8b44-ff22bbb9b458",
      "name": "Medium ID Validation",
      "type": "n8n-nodes-base.code",
      "position": [
        -2512,
        304
      ],
      "parameters": {
        "jsCode": "const f = items[0]?.json;\nreturn [{ json: { medium_id: f?.id ?? null } }]; \n"
      },
      "typeVersion": 2
    },
    {
      "id": "28199eb1-83e3-45a5-b6e8-cb1bad911bdb",
      "name": "Campaign ID Validation",
      "type": "n8n-nodes-base.code",
      "position": [
        -2512,
        432
      ],
      "parameters": {
        "jsCode": "const f = items[0]?.json;\nreturn [{ json: { campaign_id: f?.id ?? null } }]; \n"
      },
      "typeVersion": 2
    },
    {
      "id": "ca35714a-9459-484f-8b42-f59f26286533",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2736,
        48
      ],
      "parameters": {
        "color": 2,
        "width": 368,
        "height": 544,
        "content": "## Get Marketing Data\n"
      },
      "typeVersion": 1
    },
    {
      "id": "cbe239c6-de01-4d18-b523-bb856bf405bb",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4016,
        240
      ],
      "parameters": {
        "color": 6,
        "width": 608,
        "height": 800,
        "content": "## How it works\n\n1) **Ingest**\n   - Webhook receives POST at the path above.\n   - Header Auth is required (`x-webhook-token`).\n\n2) **Validate**\n   - IF node checks required fields; if any is missing \u2192 Respond **400 Bad Request**.\n\n3) **UTM lookup**\n   - Three Odoo `getAll` nodes fetch IDs by name:\n     - `utm.source` \u2192 `source_id`\n     - `utm.medium` \u2192 `medium_id`\n     - `utm.campaign` \u2192 `campaign_id`\n   - If not found, the ID remains `null` (safe default).\n\n4) **Consolidate**\n   - **Merge (append)** feeds a **Code** node that merges the 4 inputs into one clean object:\n     `{ name, contact_name, email_from, phone, description, type: \"lead\",\n        campaign_id, source_id, medium_id }`\n\n5) **Create in Odoo**\n   - Odoo node (`crm.lead \u2192 create`) writes the lead using standard fields.\n\n6) **Respond**\n   - **Success 200** \u2192 `{ \"status\": \"ok\", \"lead_id\": <id> }`\n   - **Bad Request 400** with a clear error message when validation fails.\n\n**Notes**\n- Recent Odoo versions don\u2019t have `mobile`: use **`phone`**.\n- Keep credentials out of the template; users will select theirs after import.\n- If you later want to auto-create missing UTM records, add an IF after each `getAll` and a `create` on `utm.*`.\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "9f880e8b-56c7-42f8-a18d-e3b9ae3cfb00",
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Prepare Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Lead": {
      "main": [
        [
          {
            "node": "Execution Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execution Data": {
      "main": [
        [
          {
            "node": "Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Request": {
      "main": [
        [
          {
            "node": "Create Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "UTM: Get Medium ID": {
      "main": [
        [
          {
            "node": "Medium ID Validation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "UTM: Get Source ID": {
      "main": [
        [
          {
            "node": "Source ID Validation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Medium ID Validation": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Source ID Validation": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "UTM: Get Campaign ID": {
      "main": [
        [
          {
            "node": "Campaign ID Validation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Campaign ID Validation": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Required data missing?": {
      "main": [
        [
          {
            "node": "Bad Request",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "UTM: Get Campaign ID",
            "type": "main",
            "index": 0
          },
          {
            "node": "UTM: Get Medium ID",
            "type": "main",
            "index": 0
          },
          {
            "node": "UTM: Get Source ID",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "Webhook - Lead Webform": {
      "main": [
        [
          {
            "node": "Required data missing?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}