Quando i dati arrivano da molte fonti (advertising, CRM, billing, supporto), il valore nasce dalla capacità di unirli in modo coerente. Con aggregazione dati con n8n puoi costruire pipeline ETL/ELT low‑code che estraggono da API eterogenee, normalizzano i payload JSON, eseguono join e deduplica dei record e pubblicano un unico report per marketing e vendite. In questa guida pratica costruirai un’architettura riutilizzabile: dall’ingest tramite HTTP Request con paginazione e autenticazione, alla normalizzazione dei dati JSON e mapping dello schema dati, fino alla scrittura su Google Sheets o DB. Vedrai pattern reali per join e dedup (anche tra array annidati), rate limiting e backoff, e calcolo KPI con Code/Function. Obiettivo: passare da screenshot manuali a report quotidiani affidabili che combinano più API, riducendo errori e tempo uomo. Perfetta per marketer che desiderano orchestrare integrazione API con n8n senza scrivere un backend complesso, mantenendo controllo su performance, costi e governance.
[IMG: Panoramica flusso: HTTP Request (API A/B/C) → Set/Code (normalize) → Item Lists (split) → Merge (join) → Code (KPI) → Google Sheets/DB (report)]
Architettura ETL/ELT low‑code con n8n: principi e design
Per unire dati multi‑API serve uno schema e un flusso chiari:
- Estrai (E): usa HTTP Request per interrogare ogni sorgente (ads, CRM, fatturazione), gestendo query, header, autenticazione e rate limit.
- Trasforma (T): normalizzazione dei dati JSON e mapping dello schema dati per uniformare campi (es. campaignId, date, spend, leads, revenue).
- Carica (L): scrivi il risultato in un’unica tabella (Google Sheets, Airtable o DB) e alimenta dashboard BI.
Componenti tipici
- HTTP Request, Item Lists (splitIntoItems), Set (keepOnlySet), Code/Function (functionCode), Merge (mergeByKey), Split In Batches (batchSize), IF/Switch per routing condizionale, Google Sheets (append).
- Scelta ELT vs ETL: ELT spinge i dati quasi raw a un DB/warehouse e trasforma a valle; ETL normalizza già in n8n. Per marketer, ETL low‑code spesso è più veloce da mettere in produzione.
Linee guida
- Progetta upfront lo schema “reportReady”: campi coerenti tra sorgenti, tipizzazioni, chiavi di join, granularità temporale (giorno/settimana).
- Inserisci metadati: source, fetchTimestamp, version per audit e tracciabilità.
- Versiona i workflow: una modifica al mapping non deve “rompere” i report esistenti.
[IMG: Schema logico: API raw → Normalize → Join → KPI → Report]
Ingest da API multiple: HTTP Request, credenziali e paginazione
Il nodo “n8n-nodes-base.httpRequest” è il cavallo di battaglia per integrazione API con n8n. Parametri chiave (esatti) da configurare nel JSON del nodo:
- url
- method
- responseFormat
- jsonParameters
- queryParametersUi
- headerParametersUi
- bodyParametersUi o bodyParametersJson (se invii payload JSON)
- options (es. bodyContentType: “json”)
- credentials (se usi auth gestite)
Esempio GET con query e header
{
"name": "Fetch Ads",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"parameters": {
"url": "https://api.example-ads.com/v1/spend",
"method": "GET",
"responseFormat": "json",
"queryParametersUi": {
"parameter": [
{ "name": "date", "value": "={{ $json.reportDate }}" },
{ "name": "page", "value": "={{ $json.page || 1 }}" },
{ "name": "limit", "value": "100" }
]
},
"headerParametersUi": {
"parameter": [
{ "name": "Authorization", "value": "Bearer {{ $json.apiToken }}" }
]
},
"jsonParameters": true,
"options": { "bodyContentType": "json" }
}
}
Gestione paginazione delle API
- Non esiste un parametro “pagination” dedicato: implementa loop variando “page” (o “cursor”) via queryParametersUi.
- Pattern consigliato:
1) Set iniziale con page=1.
2) HTTP Request → If “items.length > 0”.
3) Item Lists (splitIntoItems) per elaborare i record.
4) Merge/Append in una lista cumulativa o scrittura batch.
5) Incrementa page e ripeti finché la risposta è vuota.
Idempotenza e resilienza
- Passa un “fetchId” (es. data + sorgente) per evitare duplicati in scrittura.
- Se l’API supporta etag o since/updated_after, preferiscili a page/offset per job incrementali.
[IMG: Loop paginazione: Set(page=1) → HTTP → IF (hasItems) → split → aggregate/append → page++ → loop]
Normalizzazione dei dati JSON e mapping dello schema
Prima di unire le fonti, porta ogni payload a uno schema coerente.
Set node come “mapper visivo”
- Parametri chiave:
- keepOnlySet: true per mantenere solo i campi mappati.
- values: definisci i nuovi campi, tipizzati.
- removeFields: opzionale per eliminare campi indesiderati.
Esempio Set (mapping minimo)
{
"name": "Normalize Ads Row",
"type": "n8n-nodes-base.set",
"typeVersion": 3,
"parameters": {
"keepOnlySet": true,
"values": {
"string": [
{ "name": "source", "value": "example-ads" },
{ "name": "campaignId", "value": "={{ $json.campaign_id }}" },
{ "name": "date", "value": "={{ $json.date }}" }
],
"number": [
{ "name": "spend", "value": "={{ +$json.spend_cents/100 }}" },
{ "name": "clicks", "value": "={{ +$json.clicks }}" }
]
}
}
}
Code node per trasformazioni custom
- functionCode utile per normalizzare formati, calcolare campi derivati, validare valori:
// Run Once For Each Item
const row = $json;
row.cpc = row.clicks ? +(row.spend / row.clicks).toFixed(2) : 0;
row.fetchTimestamp = $now; // timestamp corrente
return [{ json: row }];
Best practice di normalizzazione dei dati JSON
- Tipizza subito numeri e date (evita stringhe ambigue).
- Uniforma i nomi dei campi tra fonti (campaignId, date, spend, leads, revenue).
- Aggiungi “source” per tracciare la provenienza e facilitare debug/join.
[IMG: Set (keepOnlySet) → Code (derive fields) con anteprima campi normalizzati]
Join e deduplica dei record tra fonti
Per unire più dataset (es. Ads + CRM + Billing):
- Allinea la granularità: per data e campaignId (o altra chiave).
- Esegui join e deduplica dei record con i nodi Item Lists e Merge.
Item Lists per preparare i dataset
- splitIntoItems: trasforma un array di record in item singoli per elaborazioni per‑riga.
{
"name": "Split CRM Leads",
"type": "n8n-nodes-base.itemLists",
"typeVersion": 1,
"parameters": {
"operation": "splitIntoItems",
"property": "results"
}
}
- aggregateItems: ricompone più item in un singolo array quando necessario.
Merge node per join
- mergeByKey con propertyName per unire per chiave (es. campaignId+date).
{
"name": "Join Ads + CRM",
"type": "n8n-nodes-base.merge",
"typeVersion": 1,
"parameters": {
"mode": "mergeByKey",
"propertyName": "joinKey",
"outputDataFrom": "both"
}
}
Suggerimento: crea “joinKey” prima del Merge (es. “{{ $json.campaignId }}|{{ $json.date }}”).
Deduplica
- In caso di duplicati (es. CRM con più lead sulla stessa chiave), consolida con Code:
// Run Once For All Items: deduplica per joinKey sommando metriche
const map = new Map();
for (const item of items) {
const r = item.json;
const k = r.joinKey;
const acc = map.get(k) || { ...r, leads: 0, revenue: 0 };
acc.leads += +r.leads || 0;
acc.revenue += +r.revenue || 0;
map.set(k, acc);
}
return Array.from(map.values()).map(v => ({ json: v }));
[IMG: Normalize (A/B/C) → Set(joinKey) → Merge by Key → Code(dedup/sum) → Report]
Calcolo KPI e arricchimento: dal dataset al report
Dopo il join, calcola KPI utili per marketing:
- CPC = spend / clicks
- CPL = spend / leads
- ROAS = revenue / spend
- Conversion rate = leads / clicks
Esempio Code (calcolo KPI)
// Run Once For Each Item
const r = $json;
const safe = (n) => (isFinite(n) && !isNaN(n)) ? n : 0;
r.cpc = safe(r.spend) && safe(r.clicks) ? +(r.spend / r.clicks).toFixed(2) : 0;
r.cpl = safe(r.spend) && safe(r.leads) ? +(r.spend / r.leads).toFixed(2) : 0;
r.roas = safe(r.spend) ? +(safe(r.revenue) / r.spend).toFixed(2) : 0;
return [{ json: r }];
Regole e segmentazione
- Usa Switch per segmentare per canale/paese:
{
"name": "Segment By Channel",
"type": "n8n-nodes-base.switch",
"typeVersion": 1,
"parameters": {
"dataType": "string",
"value1": "={{ $json.source }}",
"rules": { "rules": [
{ "operation": "equal", "value2": "example-ads" },
{ "operation": "equal", "value2": "example-crm" }
] },
"fallbackOutput": 2
}
}
- IF per filtri qualità dati (es. escludi linee senza date/campaignId):
{
"name": "Has Required Fields",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.campaignId }}",
"operation": "isEmpty"
}
]
},
"combineOperation": "and"
}
}
(Usa operatori coerenti con la tua versione; in alternativa verifica con Code e droppa l’item.)
[IMG: Code(KPI) → Switch(segment) → IF(filter) → Append]
Scrittura del report: Google Sheets, Airtable o DB
Google Sheets: append automatico
{
"name": "Append Report",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"parameters": {
"operation": "append",
"spreadsheetId": "1A2b3C4D5E6FgHiJkLMnoPQrstu",
"sheetName": "Campaign_Report",
"dataMode": "autoMap",
"options": { "valueInputMode": "USER_ENTERED" }
},
"credentials": {
"googleSheetsOAuth2Api": { "name": "Google Sheets OAuth2" }
}
}
Alternative
- Airtable: tabella con schema “reportReady”.
- DB: più robusto per storici (MySQL/Postgres) e BI. In questo caso, valuta ELT: carica raw e trasforma con viste/materialized views.
Consigli pratici
- Mappa le colonne con header coerenti: date, campaignId, source, spend, clicks, leads, revenue, cpc, cpl, roas.
- Appendi solo i record del giorno/periodo corrente; per ricalcoli completi (backfill) esegui job separati.
[IMG: Foglio “Campaign_Report” con colonne mappate automaticamente]
Rate limiting, backoff e performance
Rate limiting e backoff
- Throttling per API “sensibili”: usa Split In Batches con batchSize per limitare le chiamate simultanee.
{
"name": "Batch Loop",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 1,
"parameters": { "batchSize": 10 }
}
- Tra un batch e l’altro, inserisci una breve pausa (Code o nodo di attesa) e riprendi il loop finché ci sono items.
- In caso di errori/429, implementa backoff crescente (2s → 5s → 10s) e ritenta.
Gestione paginazione efficiente
- Interrompi il loop quando la risposta non ritorna più elementi.
- Usa parametri “updated_after” quando disponibili per job incrementali.
Ottimizzazioni
- Riduci i campi al minimo prima di Merge/Sheets (Set keepOnlySet).
- Esegui calcoli KPI “Run Once For Each Item” per ridurre la memoria.
- Evita log ridondanti: conserva solo il necessario per audit.
[IMG: SplitInBatches(10) → HTTP → (retry/backoff) → Append/Join → next batch]
Esempio end‑to‑end: report giornaliero Ads + CRM + Billing
Obiettivo: unire spend (Ads), leads (CRM), revenue (Billing) per campaignId e date.
Step‑by‑step
1) Inizializza
- Set: reportDate = oggi, page=1, apiToken da credenziali (o variabili).
- [IMG: Nodo Set iniziale con costanti job]
2) Ingest Ads
- HTTP Request (GET) con queryParametersUi (date, page, limit), headerParametersUi (Authorization).
- Loop paginato fino a esaurimento.
- Item Lists splitIntoItems su “data” (array di righe).
- Set keepOnlySet: source, campaignId, date, spend, clicks.
3) Ingest CRM
- HTTP Request su endpoint leads per date.
- splitIntoItems, Set => source=crm, campaignId, date, leads.
- Deduplica per campaignId/date con Code (somma leads).
4) Ingest Billing
- HTTP Request su fatture pagate per date.
- splitIntoItems, Set => source=billing, campaignId, date, revenue.
- Deduplica revenue per campaignId/date.
5) Join
- Per ogni dataset, crea joinKey = “{{ $json.campaignId }}|{{ $json.date }}”.
- Merge by Key (Ads + CRM), poi Merge by Key (risultato + Billing) con:
{
"parameters": {
"mode": "mergeByKey",
"propertyName": "joinKey",
"outputDataFrom": "both"
}
}
6) KPI e filtri
- Code: calcola cpc, cpl, roas.
- IF/Switch: filtra righe incomplete o segmenta per mercato.
7) Report
- Google Sheets (append) su Campaign_Report con dataMode: autoMap.
- Aggiungi fetchTimestamp = $now per audit.
8) Osservabilità
- Log sintetico: righe processate, sorgenti chiamate, durata job.
- Notifica Slack finale con conteggi record.
[IMG: Canvas completo con nodi principali e rami di join]
Osservabilità, governance e versioning
- Logging: salva contatori per source (righe lette/scritte), errori per endpoint, tempo totale job.
- Alert: Slack in caso di errori ripetuti o risposte 5xx.
- Versioning: duplica workflow per nuove versioni; annota in header foglio la “schema_version”.
- Sicurezza: credenziali delle API nelle “credentials” n8n, variabili d’ambiente per segreti; limita permessi in scrittura.
Scalabilità
- Quando crescono le fonti, replica il pattern: una fase di ingest/normalize per source, poi join per chiave, KPI e scrittura.
- Passa da Sheets a DB quando i volumi aumentano, mantenendo lo stesso schema concettuale.
[IMG: Dashboard KPI di job con record processati e time per fase]
Quick Takeaways
- Progetta uno schema “reportReady” e usa Set/Code per normalizzare le fonti prima del join.
- Implementa la gestione paginazione delle API con loop su page/limit via queryParametersUi.
- Esegui join con Merge (mode: mergeByKey, propertyName: joinKey) e deduplica con Code aggregando metriche.
- Applica rate limiting e backoff con Split In Batches (batchSize) e retry controllati su errori/429.
- Pubblica il report con Google Sheets (append) o DB e aggiungi KPI (CPC, CPL, ROAS) in Code.
- Monitora: logging sintetico, alert Slack, versioning del workflow per modifiche sicure.
Conclusione
L’aggregazione dati con n8n ti permette di creare un layer di integrazione API low‑code rapido da rilasciare e semplice da mantenere. Con pochi nodi chiave — HTTP Request, Set, Item Lists, Merge, Code e Google Sheets — trasformi payload diversi in un report unico e coerente. La chiave è progettare lo schema a monte, normalizzare presto e unire con chiavi stabili, aggiungendo KPI utili per decisioni quotidiane. Con rate limiting, paginazione e deduplica riduci errori e costi, mentre logging e versioning assicurano governance. Inizia con due sorgenti e poche metriche core, poi estendi a nuove API e breakdown (canali, paesi, segmenti). In poche iterazioni avrai un’unica vista affidabile di spend, lead e ricavi, pronta per BI e pianificazione. Se vuoi fare il salto di qualità nella produttività marketing, imposta oggi il primo workflow: domani non tornerai più agli export manuali.
FAQ
1) Come gestisco più pagine di un’API in n8n?
Usa queryParametersUi per passare page/limit e implementa un loop: incrementa “page” finché la risposta non ritorna più record. Puoi orchestrare il ciclo con IF e Set, e processare i risultati con Item Lists (splitIntoItems).
2) Come unisco dati di diverse fonti in un’unica riga?
Crea una joinKey (es. campaignId|date) con Set/Code e usa il Merge node con mode: “mergeByKey”, propertyName: “joinKey”, outputDataFrom: “both”. Prima del join, assicurati la normalizzazione dei dati JSON.
3) Come evito duplicati dopo il join?
Applica join e deduplica dei record con un Code “Run Once For All Items” aggregando per joinKey (somma leads/revenue). In alternativa, pulisci i duplicati a monte in ciascuna fonte.
4) Come rispetto i limiti delle API durante l’ingest?
Usa Split In Batches con batchSize adeguato, inserisci pause tra i batch e implementa rate limiting e backoff: in caso di 429/5xx, ritenta con delay progressivo e limita la concorrenza.
5) È meglio scrivere su Google Sheets o su un database?
Per volumi piccoli e prototipi, Google Sheets con operation: “append” e dataMode: “autoMap” è rapido. Per storici e query avanzate, passa a un DB (MySQL/Postgres) mantenendo lo stesso mapping dello schema dati.
Ci dai una mano?
Quale combinazione di API vuoi unire per prima (Ads + CRM, CRM + Billing, altro)? Raccontaci il tuo caso e condividi questo articolo con il team: confrontiamo setup e pattern per costruire insieme report più completi e affidabili!
Scopri la consulenza →

