{ "name": "MagRicambi - Ricerca prezzi articolo", "nodes": [ { "parameters": { "content": "## MagRicambi โ€” Ricerca prezzi\n\n**Setup:**\n1. Credenziale Header Auth sul Webhook (`X-Webhook-Secret`)\n2. Credenziale Tavily su nodo HTTP\n3. Credenziale OpenAI\n4. Attiva workflow โ†’ copia Production URL in `N8N_PRICE_SEARCH_WEBHOOK_URL`\n\n**Chiamata da MagRicambi:**\n`POST /api/articoli/ricerca-prezzi` โ†’ proxy verso questo webhook", "height": 320, "width": 420 }, "id": "sticky-note-setup", "name": "Note Setup", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [-80, 80] }, { "parameters": { "httpMethod": "POST", "path": "magricambi-ricerca-prezzi", "responseMode": "responseNode", "authentication": "headerAuth", "options": {} }, "id": "webhook-magricambi", "name": "Webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [240, 300], "webhookId": "magricambi-ricerca-prezzi", "credentials": { "httpHeaderAuth": { "id": "REPLACE_WITH_CREDENTIAL_ID", "name": "MagRicambi Webhook Secret" } } }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 }, "conditions": [ { "id": "cond-descrizione-not-empty", "leftValue": "={{ ($json.body?.descrizione ?? $json.descrizione ?? '').toString().trim() }}", "rightValue": "", "operator": { "type": "string", "operation": "notEmpty", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "id": "if-descrizione-valida", "name": "IF Descrizione valida", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [480, 300] }, { "parameters": { "jsCode": "const item = $input.first().json;\nconst body = item.body ?? item;\n\nconst descrizione = String(body.descrizione ?? '').trim();\nconst barcode = String(body.barcode ?? '').trim();\nconst codice = String(body.codice ?? '').trim();\n\nconst parts = [];\nif (barcode) parts.push(barcode);\nif (codice) parts.push(codice);\nif (descrizione) parts.push(descrizione);\nparts.push('prezzo acquisto ricambio industriale Italia EUR');\n\nreturn [{\n json: {\n descrizione,\n barcode: barcode || null,\n codice: codice || null,\n quantita: body.quantita ?? null,\n query: parts.join(' '),\n meta: body.meta ?? {},\n },\n}];" }, "id": "code-build-query", "name": "Build Query", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [720, 200] }, { "parameters": { "method": "POST", "url": "https://api.tavily.com/search", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "sendBody": true, "specifyBody": "json", "jsonBody": "={\n \"query\": {{ JSON.stringify($json.query) }},\n \"search_depth\": \"basic\",\n \"max_results\": 8,\n \"include_answer\": false\n}", "options": { "timeout": 20000 } }, "id": "http-tavily-search", "name": "Tavily Search", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [960, 200], "credentials": { "httpHeaderAuth": { "id": "REPLACE_WITH_CREDENTIAL_ID", "name": "Tavily API" } } }, { "parameters": { "method": "POST", "url": "https://api.openai.com/v1/chat/completions", "authentication": "predefinedCredentialType", "nodeCredentialType": "openAiApi", "sendBody": true, "specifyBody": "json", "jsonBody": "={\n \"model\": \"gpt-4o-mini\",\n \"temperature\": 0.2,\n \"response_format\": { \"type\": \"json_object\" },\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"Sei un assistente per acquisti industriali. Ricevi risultati di ricerca web. Estrai SOLO offerte con prezzo esplicito nei testi forniti. Se il prezzo non รจ chiaro usa price null e confidence low. Rispondi SOLO con JSON valido nel formato: {\\\"offers\\\":[{\\\"vendor\\\":\\\"\\\",\\\"title\\\":\\\"\\\",\\\"price\\\":null,\\\"currency\\\":\\\"EUR\\\",\\\"url\\\":\\\"\\\",\\\"shippingNote\\\":\\\"\\\",\\\"confidence\\\":\\\"high|medium|low\\\",\\\"source\\\":\\\"snippet|page|estimate\\\"}]}. Massimo 8 offerte. Valuta EUR. Nessun testo fuori dal JSON.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"Query: \" + $('Build Query').first().json.query + \"\\n\\nRisultati ricerca:\\n\" + JSON.stringify($json.results ?? $json)\n }\n ]\n}", "options": { "timeout": 25000 } }, "id": "http-openai-extract", "name": "OpenAI Extract", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [1200, 200], "credentials": { "openAiApi": { "id": "REPLACE_WITH_CREDENTIAL_ID", "name": "OpenAI API" } } }, { "parameters": { "jsCode": "const query = $('Build Query').first().json.query;\nconst descrizione = $('Build Query').first().json.descrizione;\n\nlet parsed = { offers: [] };\n\ntry {\n const item = $input.first().json;\n const raw =\n item.choices?.[0]?.message?.content ??\n item.message?.content ??\n item.text ??\n '{}';\n const clean = String(raw).replace(/```json|```/g, '').trim();\n parsed = JSON.parse(clean);\n} catch (e) {\n parsed = { offers: [] };\n}\n\nconst offers = (parsed.offers ?? [])\n .map((o) => ({\n vendor: String(o.vendor ?? 'Sconosciuto').slice(0, 120),\n title: String(o.title ?? descrizione).slice(0, 200),\n price:\n o.price === null || o.price === undefined || o.price === ''\n ? null\n : Number(o.price),\n currency: 'EUR',\n url: String(o.url ?? ''),\n shippingNote: o.shippingNote ? String(o.shippingNote) : undefined,\n confidence: ['high', 'medium', 'low'].includes(o.confidence)\n ? o.confidence\n : 'low',\n source: ['page', 'snippet', 'estimate'].includes(o.source)\n ? o.source\n : 'snippet',\n }))\n .filter((o) => o.url && o.url !== '#');\n\nreturn [{\n json: {\n query,\n searchedAt: new Date().toISOString(),\n offers,\n disclaimer:\n 'Prezzi indicativi da fonti web: verificare su ordine fornitore prima dell\\'acquisto.',\n },\n}];" }, "id": "code-format-response", "name": "Format Response", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [1440, 200] }, { "parameters": { "respondWith": "json", "responseBody": "={{ $json }}", "options": { "responseCode": 200 } }, "id": "respond-ok", "name": "Respond OK", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "position": [1680, 200] }, { "parameters": { "respondWith": "json", "responseBody": "={{ { \"error\": \"descrizione obbligatoria\" } }}", "options": { "responseCode": 400 } }, "id": "respond-errore-400", "name": "Respond Errore 400", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "position": [720, 420] } ], "connections": { "Webhook": { "main": [ [ { "node": "IF Descrizione valida", "type": "main", "index": 0 } ] ] }, "IF Descrizione valida": { "main": [ [ { "node": "Build Query", "type": "main", "index": 0 } ], [ { "node": "Respond Errore 400", "type": "main", "index": 0 } ] ] }, "Build Query": { "main": [ [ { "node": "Tavily Search", "type": "main", "index": 0 } ] ] }, "Tavily Search": { "main": [ [ { "node": "OpenAI Extract", "type": "main", "index": 0 } ] ] }, "OpenAI Extract": { "main": [ [ { "node": "Format Response", "type": "main", "index": 0 } ] ] }, "Format Response": { "main": [ [ { "node": "Respond OK", "type": "main", "index": 0 } ] ] } }, "pinData": {}, "settings": { "executionOrder": "v1", "saveManualExecutions": true, "callerPolicy": "workflowsFromSameOwner", "errorWorkflow": "" }, "staticData": null, "tags": [ { "name": "magricambi" }, { "name": "ricerca-prezzi" } ], "triggerCount": 1, "updatedAt": "2026-06-04T12:00:00.000Z", "versionId": "magricambi-ricerca-prezzi-v1", "meta": { "templateCredsSetupCompleted": false, "instanceId": "magricambi-local" }, "active": false }