This workflow corresponds to n8n.io template #16338 — we link there as the canonical source.
This workflow follows the Datatable → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Multi-Carrier Tracking Aggregator (Nodrel)",
"tags": [],
"nodes": [
{
"id": "443f7e47-e315-4222-af63-842b6761a4f9",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
2304,
4224
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 15
}
]
}
},
"typeVersion": 1.2
},
{
"id": "c176aab5-fe25-4909-970d-0a7d9419cc6d",
"name": "Load active shipments",
"type": "n8n-nodes-base.dataTable",
"position": [
2528,
4224
],
"parameters": {
"operation": "get",
"returnAll": true,
"dataTableId": {
"__rl": true,
"mode": "name",
"value": "shipment_tracking"
}
},
"typeVersion": 1
},
{
"id": "73db10e2-17b3-4e1e-b692-e66e0717ccc0",
"name": "Active only",
"type": "n8n-nodes-base.filter",
"position": [
2752,
4224
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $json.canonicalStatus }}",
"rightValue": "DELIVERED"
},
{
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $json.canonicalStatus }}",
"rightValue": "RETURNED_TO_SENDER"
}
]
}
},
"typeVersion": 2
},
{
"id": "653d387f-5bd1-4e80-905e-add89df79e6f",
"name": "Switch by carrier",
"type": "n8n-nodes-base.switch",
"position": [
2928,
4192
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "fedex",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.carrier }}",
"rightValue": "fedex"
}
]
},
"renameOutput": true
},
{
"outputKey": "ups",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.carrier }}",
"rightValue": "ups"
}
]
},
"renameOutput": true
},
{
"outputKey": "dhl",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.carrier }}",
"rightValue": "dhl"
}
]
},
"renameOutput": true
}
]
},
"options": {
"fallbackOutput": "extra"
}
},
"typeVersion": 3
},
{
"id": "351d0476-ba99-4fc4-a115-f4ffeb4ce875",
"name": "FEDEX \u00b7 Check Breaker",
"type": "n8n-nodes-base.code",
"position": [
3184,
3872
],
"parameters": {
"jsCode": "const sd = $getWorkflowStaticData('global');\nsd.breakers = sd.breakers || {};\nconst COOLDOWN_MS = 5 * 60 * 1000; // OPEN duration before a half-open probe\nconst now = Date.now();\nconst out = [];\nfor (const item of $input.all()) {\n const j = item.json;\n const carrier = j.carrier;\n const cb = sd.breakers[carrier] || { state: 'CLOSED', failures: 0, openedAt: 0 };\n let allowed = true;\n if (cb.state === 'OPEN') {\n if (now - cb.openedAt >= COOLDOWN_MS) { cb.state = 'HALF_OPEN'; allowed = true; }\n else { allowed = false; }\n }\n sd.breakers[carrier] = cb;\n out.push({ json: { ...j, _cbAllowed: allowed, _cbState: cb.state } });\n}\nreturn out;"
},
"typeVersion": 2
},
{
"id": "823363c6-5cf8-4ef7-8865-01a37d62a2e8",
"name": "FEDEX \u00b7 IF allowed",
"type": "n8n-nodes-base.if",
"position": [
3408,
3872
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json._cbAllowed }}",
"rightValue": true
}
]
}
},
"typeVersion": 2
},
{
"id": "f9abc54e-6700-473c-a926-fd678f73b393",
"name": "FEDEX \u00b7 Track API",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"maxTries": 3,
"position": [
3648,
3824
],
"parameters": {
"url": "https://apis.fedex.com/track/v1/trackingnumbers",
"method": "POST",
"options": {
"batching": {
"batch": {
"batchSize": 1
}
},
"response": {
"response": {
"neverError": true,
"fullResponse": true
}
}
},
"jsonBody": "={{ { trackingInfo: [ { trackingNumberInfo: { trackingNumber: $json.trackingNumber } } ], includeDetailedScans: false } }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "oAuth2Api",
"headerParameters": {
"parameters": [
{
"name": "x-locale",
"value": "en_US"
}
]
}
},
"retryOnFail": true,
"typeVersion": 4.2,
"alwaysOutputData": true,
"waitBetweenTries": 2000
},
{
"id": "20b1c9d0-192f-46bc-b605-6f35f9692cb1",
"name": "FEDEX \u00b7 Skipped",
"type": "n8n-nodes-base.set",
"position": [
3648,
3984
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "938ac278-9c47-4439-8f4c-a16b4e436115",
"name": "canonicalStatus",
"type": "string",
"value": "CARRIER_UNAVAILABLE"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "9af2ab7f-f60b-427d-886a-73d60eaad270",
"name": "FEDEX \u00b7 Normalize + Breaker",
"type": "n8n-nodes-base.code",
"position": [
3904,
3872
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// FEDEX normalize + circuit-breaker update (run once for each item)\nconst ref = $('FEDEX \u00b7 IF allowed').item.json;\nconst trackingNumber = ref.trackingNumber;\nconst carrier = ref.carrier || 'fedex';\n\nconst sd = $getWorkflowStaticData('global');\nsd.breakers = sd.breakers || {};\nconst cb = sd.breakers[carrier] || { state: 'CLOSED', failures: 0, openedAt: 0 };\nconst THRESHOLD = 5;\nconst now = () => new Date().toISOString();\n\n// Breaker was OPEN -> call was skipped. Pass through, leave counters untouched.\nif ($json.canonicalStatus === 'CARRIER_UNAVAILABLE') {\n return { json: { trackingNumber, carrier, canonicalStatus: 'CARRIER_UNAVAILABLE',\n rawStatus: null, breakerState: cb.state, checkedAt: now() } };\n}\n\nconst httpErr = !!$json.error;\nconst code = typeof $json.statusCode === 'number' ? $json.statusCode : (httpErr ? 599 : 200);\nconst outage = httpErr || code === 429 || code >= 500; // only outages trip the breaker\n\nif (outage) {\n cb.failures += 1;\n if (cb.failures >= THRESHOLD) { cb.state = 'OPEN'; cb.openedAt = Date.now(); }\n} else { cb.failures = 0; cb.state = 'CLOSED'; }\nsd.breakers[carrier] = cb;\n\nconst MAP = { 'OC': 'PRE_TRANSIT', 'PU': 'IN_TRANSIT', 'IT': 'IN_TRANSIT', 'OD': 'OUT_FOR_DELIVERY', 'DL': 'DELIVERED', 'DE': 'EXCEPTION', 'RS': 'RETURNED_TO_SENDER', 'CA': 'EXCEPTION' };\nlet canonical, raw = null;\nif (outage) { canonical = 'EXCEPTION'; }\nelse if (code >= 400) { canonical = 'UNKNOWN'; } // e.g. 404 not-found for this tracking #\nelse { raw = $json.body?.output?.completeTrackResults?.[0]?.trackResults?.[0]?.latestStatusDetail?.derivedCode; canonical = MAP[raw] || 'UNKNOWN'; }\n\nreturn { json: { trackingNumber, carrier, canonicalStatus: canonical, rawStatus: raw,\n breakerState: cb.state, httpStatus: code, checkedAt: now() } };"
},
"typeVersion": 2
},
{
"id": "0a437b30-4bdc-476b-9a94-5cd861dd9459",
"name": "UPS \u00b7 Check Breaker",
"type": "n8n-nodes-base.code",
"position": [
3184,
4192
],
"parameters": {
"jsCode": "const sd = $getWorkflowStaticData('global');\nsd.breakers = sd.breakers || {};\nconst COOLDOWN_MS = 5 * 60 * 1000; // OPEN duration before a half-open probe\nconst now = Date.now();\nconst out = [];\nfor (const item of $input.all()) {\n const j = item.json;\n const carrier = j.carrier;\n const cb = sd.breakers[carrier] || { state: 'CLOSED', failures: 0, openedAt: 0 };\n let allowed = true;\n if (cb.state === 'OPEN') {\n if (now - cb.openedAt >= COOLDOWN_MS) { cb.state = 'HALF_OPEN'; allowed = true; }\n else { allowed = false; }\n }\n sd.breakers[carrier] = cb;\n out.push({ json: { ...j, _cbAllowed: allowed, _cbState: cb.state } });\n}\nreturn out;"
},
"typeVersion": 2
},
{
"id": "6821525e-25c9-4ce5-98bf-e5ec55cca9a6",
"name": "UPS \u00b7 IF allowed",
"type": "n8n-nodes-base.if",
"position": [
3408,
4192
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json._cbAllowed }}",
"rightValue": true
}
]
}
},
"typeVersion": 2
},
{
"id": "2d04f03a-e108-4ac3-aed2-039a4970e5b1",
"name": "UPS \u00b7 Track API",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"maxTries": 3,
"position": [
3648,
4144
],
"parameters": {
"url": "=https://onlinetools.ups.com/api/track/v1/details/{{ $json.trackingNumber }}",
"options": {
"batching": {
"batch": {
"batchSize": 1
}
},
"response": {
"response": {
"neverError": true,
"fullResponse": true
}
}
},
"sendHeaders": true,
"authentication": "genericCredentialType",
"genericAuthType": "oAuth2Api",
"headerParameters": {
"parameters": [
{
"name": "transId",
"value": "={{ Date.now() }}"
},
{
"name": "transactionSrc",
"value": "n8n-tracking-aggregator"
}
]
}
},
"retryOnFail": true,
"typeVersion": 4.2,
"alwaysOutputData": true,
"waitBetweenTries": 2000
},
{
"id": "c8d6e202-3693-402c-94ab-b4f7f4607405",
"name": "UPS \u00b7 Skipped",
"type": "n8n-nodes-base.set",
"position": [
3648,
4304
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "8c809b84-c88c-4707-9efa-753f4ae644f9",
"name": "canonicalStatus",
"type": "string",
"value": "CARRIER_UNAVAILABLE"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "09bbb97d-41d7-4b4f-b7fd-0bce9237bc33",
"name": "UPS \u00b7 Normalize + Breaker",
"type": "n8n-nodes-base.code",
"position": [
3904,
4192
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// UPS normalize + circuit-breaker update (run once for each item)\nconst ref = $('UPS \u00b7 IF allowed').item.json;\nconst trackingNumber = ref.trackingNumber;\nconst carrier = ref.carrier || 'ups';\n\nconst sd = $getWorkflowStaticData('global');\nsd.breakers = sd.breakers || {};\nconst cb = sd.breakers[carrier] || { state: 'CLOSED', failures: 0, openedAt: 0 };\nconst THRESHOLD = 5;\nconst now = () => new Date().toISOString();\n\n// Breaker was OPEN -> call was skipped. Pass through, leave counters untouched.\nif ($json.canonicalStatus === 'CARRIER_UNAVAILABLE') {\n return { json: { trackingNumber, carrier, canonicalStatus: 'CARRIER_UNAVAILABLE',\n rawStatus: null, breakerState: cb.state, checkedAt: now() } };\n}\n\nconst httpErr = !!$json.error;\nconst code = typeof $json.statusCode === 'number' ? $json.statusCode : (httpErr ? 599 : 200);\nconst outage = httpErr || code === 429 || code >= 500; // only outages trip the breaker\n\nif (outage) {\n cb.failures += 1;\n if (cb.failures >= THRESHOLD) { cb.state = 'OPEN'; cb.openedAt = Date.now(); }\n} else { cb.failures = 0; cb.state = 'CLOSED'; }\nsd.breakers[carrier] = cb;\n\nconst MAP = { 'M': 'PRE_TRANSIT', 'P': 'IN_TRANSIT', 'I': 'IN_TRANSIT', 'O': 'OUT_FOR_DELIVERY', 'D': 'DELIVERED', 'X': 'EXCEPTION', 'RS': 'RETURNED_TO_SENDER' };\nlet canonical, raw = null;\nif (outage) { canonical = 'EXCEPTION'; }\nelse if (code >= 400) { canonical = 'UNKNOWN'; } // e.g. 404 not-found for this tracking #\nelse { raw = $json.body?.trackResponse?.shipment?.[0]?.package?.[0]?.currentStatus?.type; canonical = MAP[raw] || 'UNKNOWN'; }\n\nreturn { json: { trackingNumber, carrier, canonicalStatus: canonical, rawStatus: raw,\n breakerState: cb.state, httpStatus: code, checkedAt: now() } };"
},
"typeVersion": 2
},
{
"id": "43a3a675-1723-452e-9c52-b4b66f3ebc6e",
"name": "DHL \u00b7 Check Breaker",
"type": "n8n-nodes-base.code",
"position": [
3184,
4512
],
"parameters": {
"jsCode": "const sd = $getWorkflowStaticData('global');\nsd.breakers = sd.breakers || {};\nconst COOLDOWN_MS = 5 * 60 * 1000; // OPEN duration before a half-open probe\nconst now = Date.now();\nconst out = [];\nfor (const item of $input.all()) {\n const j = item.json;\n const carrier = j.carrier;\n const cb = sd.breakers[carrier] || { state: 'CLOSED', failures: 0, openedAt: 0 };\n let allowed = true;\n if (cb.state === 'OPEN') {\n if (now - cb.openedAt >= COOLDOWN_MS) { cb.state = 'HALF_OPEN'; allowed = true; }\n else { allowed = false; }\n }\n sd.breakers[carrier] = cb;\n out.push({ json: { ...j, _cbAllowed: allowed, _cbState: cb.state } });\n}\nreturn out;"
},
"typeVersion": 2
},
{
"id": "ffb95bec-8ed6-4db1-9e43-2171800017fd",
"name": "DHL \u00b7 IF allowed",
"type": "n8n-nodes-base.if",
"position": [
3408,
4512
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json._cbAllowed }}",
"rightValue": true
}
]
}
},
"typeVersion": 2
},
{
"id": "3b301a9f-7b55-4298-beab-b440d0f22eb6",
"name": "DHL \u00b7 Track API",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"maxTries": 3,
"position": [
3648,
4464
],
"parameters": {
"url": "https://api-eu.dhl.com/track/shipments",
"options": {
"batching": {
"batch": {
"batchSize": 1
}
},
"response": {
"response": {
"neverError": true,
"fullResponse": true
}
}
},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "oAuth2Api",
"queryParameters": {
"parameters": [
{
"name": "trackingNumber",
"value": "={{ $json.trackingNumber }}"
}
]
}
},
"retryOnFail": true,
"typeVersion": 4.2,
"alwaysOutputData": true,
"waitBetweenTries": 2000
},
{
"id": "c260d815-3466-44c6-827e-93ce7fe72622",
"name": "DHL \u00b7 Skipped",
"type": "n8n-nodes-base.set",
"position": [
3648,
4624
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "dea3856a-3f2b-4141-9e9a-7f9ca9de3d10",
"name": "canonicalStatus",
"type": "string",
"value": "CARRIER_UNAVAILABLE"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "07b2f046-7646-4034-b4b1-7cf77d5f3284",
"name": "DHL \u00b7 Normalize + Breaker",
"type": "n8n-nodes-base.code",
"position": [
3904,
4512
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// DHL normalize + circuit-breaker update (run once for each item)\nconst ref = $('DHL \u00b7 IF allowed').item.json;\nconst trackingNumber = ref.trackingNumber;\nconst carrier = ref.carrier || 'dhl';\n\nconst sd = $getWorkflowStaticData('global');\nsd.breakers = sd.breakers || {};\nconst cb = sd.breakers[carrier] || { state: 'CLOSED', failures: 0, openedAt: 0 };\nconst THRESHOLD = 5;\nconst now = () => new Date().toISOString();\n\n// Breaker was OPEN -> call was skipped. Pass through, leave counters untouched.\nif ($json.canonicalStatus === 'CARRIER_UNAVAILABLE') {\n return { json: { trackingNumber, carrier, canonicalStatus: 'CARRIER_UNAVAILABLE',\n rawStatus: null, breakerState: cb.state, checkedAt: now() } };\n}\n\nconst httpErr = !!$json.error;\nconst code = typeof $json.statusCode === 'number' ? $json.statusCode : (httpErr ? 599 : 200);\nconst outage = httpErr || code === 429 || code >= 500; // only outages trip the breaker\n\nif (outage) {\n cb.failures += 1;\n if (cb.failures >= THRESHOLD) { cb.state = 'OPEN'; cb.openedAt = Date.now(); }\n} else { cb.failures = 0; cb.state = 'CLOSED'; }\nsd.breakers[carrier] = cb;\n\nconst MAP = { 'pre-transit': 'PRE_TRANSIT', 'transit': 'IN_TRANSIT', 'delivered': 'DELIVERED', 'failure': 'EXCEPTION', 'unknown': 'UNKNOWN' };\nlet canonical, raw = null;\nif (outage) { canonical = 'EXCEPTION'; }\nelse if (code >= 400) { canonical = 'UNKNOWN'; } // e.g. 404 not-found for this tracking #\nelse { raw = $json.body?.shipments?.[0]?.status?.statusCode; canonical = MAP[raw] || 'UNKNOWN'; }\n\nreturn { json: { trackingNumber, carrier, canonicalStatus: canonical, rawStatus: raw,\n breakerState: cb.state, httpStatus: code, checkedAt: now() } };"
},
"typeVersion": 2
},
{
"id": "d8f0e345-95ea-4c73-bfea-8bfb53ebb2bf",
"name": "Unknown carrier",
"type": "n8n-nodes-base.set",
"position": [
3184,
4832
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "9be11100-cb45-4a1a-956b-aa0ed2ed59d6",
"name": "canonicalStatus",
"type": "string",
"value": "UNKNOWN"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "b05bd787-beda-432f-b03d-2efaf2dda9e1",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
4176,
4224
],
"parameters": {
"numberInputs": 4
},
"typeVersion": 3
},
{
"id": "f93e063c-f910-4ace-8227-b5e68ffc1402",
"name": "Persist \u2192 Data Table (upsert)",
"type": "n8n-nodes-base.dataTable",
"position": [
4400,
4256
],
"parameters": {
"columns": {
"value": {},
"mappingMode": "autoMapInputData",
"matchingColumns": [
"trackingNumber"
]
},
"filters": {
"conditions": [
{
"keyName": "trackingNumber",
"keyValue": "={{ $json.trackingNumber }}"
}
]
},
"options": {},
"operation": "upsert",
"dataTableId": {
"__rl": true,
"mode": "name",
"value": "shipment_tracking"
}
},
"typeVersion": 1
},
{
"id": "ee5c027f-b672-41b8-91f8-59f4416d8fe4",
"name": "Sticky 20f4c",
"type": "n8n-nodes-base.stickyNote",
"position": [
3120,
3552
],
"parameters": {
"color": 4,
"width": 952,
"height": 136,
"content": "# Multi-Carrier Tracking Aggregator\nFedEx \u00b7 UPS \u00b7 DHL \u2192 one canonical status schema. Resilient by design: **circuit breaker \u00b7 rate limiting \u00b7 retries**. \u2014 Built by Nodrel"
},
"typeVersion": 1
},
{
"id": "36e444f8-1b75-4111-b352-0c8522fd3437",
"name": "Sticky 88222",
"type": "n8n-nodes-base.stickyNote",
"position": [
2224,
4112
],
"parameters": {
"color": 5,
"width": 860,
"height": 288,
"content": "## 1 \u00b7 Ingest & route\nRead active shipments \u00b7 drop terminal \u00b7 route per carrier \u00b7 unmatched \u2192 UNKNOWN"
},
"typeVersion": 1
},
{
"id": "a9463968-710a-419d-bcc5-8922083937e4",
"name": "Sticky 8ce49",
"type": "n8n-nodes-base.stickyNote",
"position": [
3120,
3696
],
"parameters": {
"color": 3,
"width": 952,
"height": 1080,
"content": "## 2 \u00b7 Resilient carrier call (\u00d73)"
},
"typeVersion": 1
},
{
"id": "05dcae09-f13e-4bec-90d3-014e3f1d08f5",
"name": "Sticky c6eb2",
"type": "n8n-nodes-base.stickyNote",
"position": [
4112,
4112
],
"parameters": {
"color": 6,
"width": 492,
"height": 336,
"content": "## 3 \u00b7 Aggregate & persist\nMerge \u2192 Upsert on `trackingNumber`"
},
"typeVersion": 1
},
{
"id": "e5eb7166-8202-413a-9da4-7859cdfe1fbb",
"name": "Sticky 29f88",
"type": "n8n-nodes-base.stickyNote",
"position": [
3472,
4784
],
"parameters": {
"color": 3,
"width": 620,
"height": 230,
"content": "### How the resilience works\nPer branch: **Check Breaker** \u2192 **IF allowed** \u2192 **Track API** \u2192 **Normalize + Breaker**.\n\nBreaker state lives in workflow static data. Track API runs with **Continue-On-Fail + neverError**, so 4xx/5xx come back as data (not thrown) and the run completes \u2014 which is what lets the breaker state persist. Only 429/5xx/network errors trip the breaker; a 404 (unknown tracking #) does not."
},
"typeVersion": 1
},
{
"id": "360b42ad-7a0e-45f9-a73c-62df99083fd5",
"name": "Sticky b8971",
"type": "n8n-nodes-base.stickyNote",
"position": [
2224,
4416
],
"parameters": {
"color": 7,
"width": 900,
"height": 348,
"content": "## Configure once (not gaps \u2014 these are environment, not logic)\n**A. Credentials** (n8n never stores secrets in a workflow):\n\u2022 FedEx \u2192 OAuth2 API cred, token URL `https://apis.fedex.com/oauth/token`, grant = client credentials\n\u2022 UPS \u2192 OAuth2 API cred, token URL `https://onlinetools.ups.com/security/v1/oauth/token`\n\u2022 DHL \u2192 Header Auth cred, name `DHL-API-Key`, value = your key\nSelect each in its **Track API** node.\n\n**B. Data Table** \u2192 create `shipment_tracking` once, then it's auto-selected by name in Load + Persist.\nColumns: trackingNumber \u00b7 carrier \u00b7 canonicalStatus \u00b7 rawStatus \u00b7 breakerState \u00b7 httpStatus \u00b7 checkedAt\nSeed it by inserting rows (carrier lowercase: fedex/ups/dhl, canonicalStatus PRE_TRANSIT).\n\nThen **Publish** \u2014 static data persists only on active, production runs."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"nodeGroups": [],
"connections": {
"Merge": {
"main": [
[
{
"node": "Persist \u2192 Data Table (upsert)",
"type": "main",
"index": 0
}
]
]
},
"Active only": {
"main": [
[
{
"node": "Switch by carrier",
"type": "main",
"index": 0
}
]
]
},
"DHL \u00b7 Skipped": {
"main": [
[
{
"node": "DHL \u00b7 Normalize + Breaker",
"type": "main",
"index": 0
}
]
]
},
"UPS \u00b7 Skipped": {
"main": [
[
{
"node": "UPS \u00b7 Normalize + Breaker",
"type": "main",
"index": 0
}
]
]
},
"Unknown carrier": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 3
}
]
]
},
"DHL \u00b7 Track API": {
"main": [
[
{
"node": "DHL \u00b7 Normalize + Breaker",
"type": "main",
"index": 0
}
]
]
},
"FEDEX \u00b7 Skipped": {
"main": [
[
{
"node": "FEDEX \u00b7 Normalize + Breaker",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Load active shipments",
"type": "main",
"index": 0
}
]
]
},
"UPS \u00b7 Track API": {
"main": [
[
{
"node": "UPS \u00b7 Normalize + Breaker",
"type": "main",
"index": 0
}
]
]
},
"DHL \u00b7 IF allowed": {
"main": [
[
{
"node": "DHL \u00b7 Track API",
"type": "main",
"index": 0
}
],
[
{
"node": "DHL \u00b7 Skipped",
"type": "main",
"index": 0
}
]
]
},
"Switch by carrier": {
"main": [
[
{
"node": "FEDEX \u00b7 Check Breaker",
"type": "main",
"index": 0
}
],
[
{
"node": "UPS \u00b7 Check Breaker",
"type": "main",
"index": 0
}
],
[
{
"node": "DHL \u00b7 Check Breaker",
"type": "main",
"index": 0
}
],
[
{
"node": "Unknown carrier",
"type": "main",
"index": 0
}
]
]
},
"UPS \u00b7 IF allowed": {
"main": [
[
{
"node": "UPS \u00b7 Track API",
"type": "main",
"index": 0
}
],
[
{
"node": "UPS \u00b7 Skipped",
"type": "main",
"index": 0
}
]
]
},
"FEDEX \u00b7 Track API": {
"main": [
[
{
"node": "FEDEX \u00b7 Normalize + Breaker",
"type": "main",
"index": 0
}
]
]
},
"FEDEX \u00b7 IF allowed": {
"main": [
[
{
"node": "FEDEX \u00b7 Track API",
"type": "main",
"index": 0
}
],
[
{
"node": "FEDEX \u00b7 Skipped",
"type": "main",
"index": 0
}
]
]
},
"DHL \u00b7 Check Breaker": {
"main": [
[
{
"node": "DHL \u00b7 IF allowed",
"type": "main",
"index": 0
}
]
]
},
"UPS \u00b7 Check Breaker": {
"main": [
[
{
"node": "UPS \u00b7 IF allowed",
"type": "main",
"index": 0
}
]
]
},
"Load active shipments": {
"main": [
[
{
"node": "Active only",
"type": "main",
"index": 0
}
]
]
},
"FEDEX \u00b7 Check Breaker": {
"main": [
[
{
"node": "FEDEX \u00b7 IF allowed",
"type": "main",
"index": 0
}
]
]
},
"DHL \u00b7 Normalize + Breaker": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 2
}
]
]
},
"UPS \u00b7 Normalize + Breaker": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"FEDEX \u00b7 Normalize + Breaker": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow runs every 15 minutes to fetch active shipments from an n8n Data Table, query FedEx, UPS, or DHL tracking APIs with circuit-breaker protection, normalize each response into a shared status schema, and upsert the latest status back into the same table. Runs every 15…
Source: https://n8n.io/workflows/16338/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Tags: Image Compression, Tinify API, TinyPNG, SEO Optimisation, E-commerce, Marketing
Using official APIs from X (formerly Twitter) and YouTube, the workflow fetches daily follower and subscriber counts, stores them in a structured n8n Data Table, and now sends automated weekly summary
📺 Full walkthrough video: https://youtu.be/-xqjoUvbXbs
📺 Full walkthrough video: https://www.youtube.com/watch?v=Me4d4BILvHk
Automate WhatsApp communication for recruitment agencies with an interactive, structured customer experience. This workflow handles pricing inquiries, request submissions, tracking, complaints, and hu