This workflow corresponds to n8n.io template #13663 — we link there as the canonical source.
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 →
{
"id": "u3Rrbxden8smAQrx",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "KlickTipp tag manager: update contact tags by names",
"tags": [
{
"id": "15wrq9sti6wyqr6J",
"name": "TEMPLATE",
"createdAt": "2025-01-08T16:34:30.163Z",
"updatedAt": "2025-01-08T16:34:30.163Z"
}
],
"nodes": [
{
"id": "49694fe4-0bb6-4e89-a386-a661eb7c856c",
"name": "Input: Email + Tag names",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
-16,
320
],
"parameters": {
"inputSource": "jsonExample",
"jsonExample": "{\n \"email\": \"example@email.com\",\n \"tagNamesToAdd\": [\"Tag name 1\", \"Tag name 2\"],\n \"tagNamesToRemove\": [\"Tag name 3\"]\n}"
},
"typeVersion": 1.1
},
{
"id": "cd405fb5-b0f3-4186-8da2-9ecbd7ab8b34",
"name": "Compare desired vs current tags",
"type": "n8n-nodes-base.code",
"position": [
368,
320
],
"parameters": {
"jsCode": "// Retrieve input variables\nconst allTags = $(\"Get tag list\").all() || [];\nconst input = $('Input: Email + Tag names').first().json || {};\nconst tagNamesToAdd = Array.isArray(input.tagNamesToAdd) ? input.tagNamesToAdd : [];\nconst tagNamesToRemove = Array.isArray(input.tagNamesToRemove) ? input.tagNamesToRemove : [];\n\n// Helpers\nconst normalizeString = (s) => String(s ?? '').trim();\nconst normalizeKey = (s) => normalizeString(s).toLowerCase();;\n\n// Build lookup: tag name -> tag ID\n// allTags items expected like: { label: \"Tag name\", value: \"123\" }\nconst tagNameToId = /** @type {Record<string, number>} */ ({});\nfor (const tag of allTags) {\n if (!tag || typeof tag.json.value !== 'string') {\n continue;\n }\n const id = Number(tag.json.id);\n\n if (Number.isNaN(id)) {\n continue;\n }\n tagNameToId[normalizeKey(tag.json.value)] = id;\n}\n\n// Resolve names -> IDs (dedupe, ignore unknown)\nconst getUniqueNormalizedKeys = (arr) => [...new Set(arr.map(normalizeKey).filter(k => k !== ''))];\n\nconst addKeys = getUniqueNormalizedKeys(tagNamesToAdd);\nconst removeKeys = getUniqueNormalizedKeys(tagNamesToRemove);\n\nconst tagIdsToAdd = addKeys\n .map(k => tagNameToId[k])\n .filter(id => typeof id === 'number' && !Number.isNaN(id));\n\nconst tagIdsToRemove = removeKeys\n .map(k => tagNameToId[k])\n .filter(id => typeof id === 'number' && !Number.isNaN(id));\n\nreturn [{ tagIdsToAdd, tagIdsToRemove }];"
},
"typeVersion": 2
},
{
"id": "e39cf9aa-d4b7-4f2e-8947-45a78da336de",
"name": "If tags to add",
"type": "n8n-nodes-base.filter",
"position": [
592,
208
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "c22b609f-a2a3-4230-ad2c-ff43565f13ff",
"operator": {
"type": "array",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.tagIdsToAdd }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "b9412193-df1f-4731-908b-fb9d43ad14c4",
"name": "If tags to remove",
"type": "n8n-nodes-base.filter",
"position": [
592,
432
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "c22b609f-a2a3-4230-ad2c-ff43565f13ff",
"operator": {
"type": "array",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.tagIdsToRemove }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "0d2bdd22-08e4-4bef-be63-b09d7acd2995",
"name": "Add tags to contact",
"type": "n8n-nodes-klicktipp.klicktipp",
"position": [
784,
208
],
"parameters": {
"email": "={{ $('Input: Email + Tag names').first().json.email }}",
"tagId": "={{ $json.tagIdsToAdd }}",
"resource": "contact-tagging"
},
"credentials": {
"klickTippApi": {
"name": "<your credential>"
}
},
"typeVersion": 3
},
{
"id": "8295bbc0-465d-4543-9446-6e88f5aaa5aa",
"name": "Split tags to remove",
"type": "n8n-nodes-base.splitOut",
"position": [
784,
432
],
"parameters": {
"options": {},
"fieldToSplitOut": "tagIdsToRemove"
},
"typeVersion": 1
},
{
"id": "ed6b2475-6ea9-4a90-ae77-dd7ab0d7f4b2",
"name": "Remove tag from contact",
"type": "n8n-nodes-klicktipp.klicktipp",
"position": [
992,
432
],
"parameters": {
"email": "={{ $('Input: Email + Tag names').first().json.email }}",
"tagId": "={{ $json.tagIdsToRemove }}",
"resource": "contact-tagging",
"operation": "untag"
},
"credentials": {
"klickTippApi": {
"name": "<your credential>"
}
},
"typeVersion": 3
},
{
"id": "44076f87-9cbc-432b-a633-6c898fcfcc92",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-80,
144
],
"parameters": {
"color": 7,
"width": 224,
"height": 496,
"content": "## Input"
},
"typeVersion": 1
},
{
"id": "8faa2178-729c-4405-981a-25046c538d8b",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
160,
144
],
"parameters": {
"color": 7,
"width": 160,
"height": 496,
"content": "## Fetch tag list"
},
"typeVersion": 1
},
{
"id": "37393001-dcbb-451c-9309-62870560b7ac",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
720,
144
],
"parameters": {
"color": 7,
"width": 608,
"height": 224,
"content": "## Add missing tags"
},
"typeVersion": 1
},
{
"id": "2341be14-61d8-4c2a-95cd-9bfffbd3a507",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
720,
384
],
"parameters": {
"color": 7,
"width": 608,
"height": 256,
"content": "## Remove extra tags"
},
"typeVersion": 1
},
{
"id": "cb6590d1-22aa-41fa-98ac-f58bfb45f25a",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
336,
144
],
"parameters": {
"color": 7,
"width": 368,
"height": 496,
"content": "## Compare tags and build actions"
},
"typeVersion": 1
},
{
"id": "a01abb7c-12e1-4b75-a0c2-b94b9d9dd3ed",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-752,
144
],
"parameters": {
"width": 656,
"height": 1344,
"content": "### Introduction\nThis workflow receives a contact email plus two arrays of tag names: one list of tags to add and one list of tags to remove. It loads the full KlickTipp tag list, resolves the provided tag names into tag IDs, and then applies the changes to the contact by tagging and untagging. It is designed to be a reusable \"Contact Tagging\" sub-workflow for any n8n automation.\n\n### Benefits\n- Applies explicit tag changes (add/remove) without requiring full tag synchronization.\n- Automatically resolves tag names to KlickTipp tag IDs.\n- Keeps parent workflows simple (no manual tag ID lookups).\n- Reusable module for consistent tagging behavior across automations.\n\n---\n\n### Key Features\n\n#### Input\nExpects the contact email plus two arrays of tag names.\n\nExample input:\n```\n{\n \"email\": \"example@email.com\",\n \"tagNamesToAdd\": [\"Tag name 1\", \"Tag name 2\"],\n \"tagNamesToRemove\": [\"Tag name 3\"]\n}\n```\n\n#### Processing\n- Loads the full KlickTipp tag list.\n- Resolves tagNamesToAdd[] into tagIdsToAdd[].\n- Resolves tagNamesToRemove[] into tagIdsToRemove[].\n- If tagIdsToAdd[] is not empty, adds these tags to the contact.\n- If tagIdsToRemove[] is not empty, splits the array and removes tags from the contact one-by-one.\n\n#### Output\nThis workflow updates the contact in KlickTipp by:\n- adding all tags resolved from tagNamesToAdd[]\n- removing all tags resolved from tagNamesToRemove[]\n\n---\n\n### Testing\n- Test with a contact that already exists in KlickTipp.\n- Provide tagNamesToAdd[] containing:\n - existing KlickTipp tags (should be added to the contact)\n - non-existing tags (will be ignored unless you add \u201ccreate tag\u201d logic)\n- Provide tagNamesToRemove[] containing:\n - existing KlickTipp tags (should be removed from the contact if present)\n - non-existing tags (will be ignored)\n- Confirm in KlickTipp that:\n - tags from tagNamesToAdd[] were added\n - tags from tagNamesToRemove[] were removed"
},
"typeVersion": 1
},
{
"id": "4a07b3ef-55bf-4422-86b8-49b5d7a8aebb",
"name": "Get tag list",
"type": "n8n-nodes-klicktipp.klicktipp",
"position": [
192,
320
],
"parameters": {},
"credentials": {
"klickTippApi": {
"name": "<your credential>"
}
},
"typeVersion": 3
},
{
"id": "14044bb4-54fc-42ae-9dd8-d7cefb3379d6",
"name": "Return result to parent workflow",
"type": "n8n-nodes-base.set",
"position": [
1616,
304
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "66b091f0-9d62-4adf-9365-3961b0879616",
"name": "success",
"type": "boolean",
"value": "={{\n $if(\n $(\"Collect removal results\").isExecuted,\n (\n Array.isArray($(\"Collect removal results\").first().json.success)\n ? $(\"Collect removal results\").first().json.success.every(Boolean)\n : Boolean($(\"Collect removal results\").first().json.success)\n ),\n true\n )\n &&\n $if(\n $(\"Add tags to contact\").isExecuted,\n Boolean($(\"Add tags to contact\").first().json.success),\n true\n )\n}}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "950d20e7-9950-44f1-b048-1d6f97bde0a2",
"name": "Collect removal results",
"type": "n8n-nodes-base.aggregate",
"position": [
1184,
432
],
"parameters": {
"options": {},
"fieldsToAggregate": {
"fieldToAggregate": [
{
"fieldToAggregate": "success"
}
]
}
},
"typeVersion": 1
},
{
"id": "d0cca038-e52a-44c4-b0ef-fc0d93e77a72",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1344,
144
],
"parameters": {
"color": 7,
"width": 464,
"height": 496,
"content": "## Compute final success (add + remove)"
},
"typeVersion": 1
},
{
"id": "e93a5a83-b597-4201-9a19-25876d907764",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
1408,
304
],
"parameters": {},
"typeVersion": 3.2
}
],
"active": true,
"settings": {
"executionOrder": "v1"
},
"versionId": "2efaad98-0f14-470f-ba1e-dcb95e7b4076",
"connections": {
"Merge": {
"main": [
[
{
"node": "Return result to parent workflow",
"type": "main",
"index": 0
}
]
]
},
"Get tag list": {
"main": [
[
{
"node": "Compare desired vs current tags",
"type": "main",
"index": 0
}
]
]
},
"If tags to add": {
"main": [
[
{
"node": "Add tags to contact",
"type": "main",
"index": 0
}
]
]
},
"If tags to remove": {
"main": [
[
{
"node": "Split tags to remove",
"type": "main",
"index": 0
}
]
]
},
"Add tags to contact": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Split tags to remove": {
"main": [
[
{
"node": "Remove tag from contact",
"type": "main",
"index": 0
}
]
]
},
"Collect removal results": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Remove tag from contact": {
"main": [
[
{
"node": "Collect removal results",
"type": "main",
"index": 0
}
]
]
},
"Input: Email + Tag names": {
"main": [
[
{
"node": "Get tag list",
"type": "main",
"index": 0
}
]
]
},
"Compare desired vs current tags": {
"main": [
[
{
"node": "If tags to add",
"type": "main",
"index": 0
},
{
"node": "If tags to remove",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
klickTippApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow uses KlickTipp community nodes, available for self-hosted n8n instances only.
Source: https://n8n.io/workflows/13663/ — 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.
This workflow uses KlickTipp community nodes, available for self-hosted n8n instances only.
Agendamiento. Uses n8n-nodes-evolution-api, redis, dataTable, executeWorkflowTrigger. Event-driven trigger; 60 nodes.
Prevent concurrent workflow runs using Redis. Uses executeWorkflowTrigger, manualTrigger, stickyNote, executeWorkflow. Event-driven trigger; 43 nodes.
This workflow sets a small "lock" value in Redis so that only one copy of a long job can run at the same time. If another trigger fires while the job is still busy, the workflow sees the lock, stops e
Reputation Engine — Site Refresh. Uses httpRequest, executeWorkflowTrigger. Event-driven trigger; 35 nodes.