This workflow corresponds to n8n.io template #8910 — 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 →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "0659ddd3-cbad-4f95-a993-f2362e5fdd97",
"name": "Crypto",
"type": "n8n-nodes-base.crypto",
"notes": "This node prepares the response to the Zoom endpoint in order to answer to the validation.",
"position": [
-160,
-112
],
"parameters": {
"type": "SHA256",
"value": "={{ $json.body.payload.plainToken }}",
"action": "hmac",
"secret": "YOUR_CREDENTIAL_HERE"
},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "f66d1b66-7059-4261-94df-f5fa65bea7a1",
"name": "Build Validation Body",
"type": "n8n-nodes-base.set",
"notes": "This node builds the body for the response to the Zoom endpoint in order to answer for validation.",
"position": [
64,
-112
],
"parameters": {
"values": {
"string": [
{
"name": "plainToken",
"value": "={{ $json.body.payload.plainToken }}"
},
{
"name": "encryptedToken",
"value": "={{ $json.data }}"
}
]
},
"options": {},
"keepOnlySet": true
},
"notesInFlow": true,
"typeVersion": 2
},
{
"id": "e6cdca55-ccaf-4786-8dc5-530ef2cc0394",
"name": "Respond to Webhook (Validation)",
"type": "n8n-nodes-base.respondToWebhook",
"notes": "This node sends the response back to the Zoom endpoint.",
"position": [
272,
-112
],
"parameters": {
"options": {}
},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "106611cd-1631-4fd8-871d-28fdda7ee5b2",
"name": "Build Normal Event Body",
"type": "n8n-nodes-base.set",
"notes": "This node builds a body for a response to the endpoint in case of a regular event.",
"position": [
-160,
80
],
"parameters": {
"values": {
"string": [
{
"name": "status",
"value": "ok"
}
]
},
"options": {},
"keepOnlySet": true
},
"notesInFlow": true,
"typeVersion": 2
},
{
"id": "dbec7433-2faf-47d0-8f06-6d7c4c20971b",
"name": "Respond to Webhook (Events)",
"type": "n8n-nodes-base.respondToWebhook",
"notes": "This node answers to the endpoint in case of regular Zoom events - such as ending meetings in this case.",
"position": [
64,
80
],
"parameters": {
"options": {}
},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "dd1d32e6-ab68-4aaf-b1f4-6df83223724e",
"name": "URL Validation check",
"type": "n8n-nodes-base.if",
"notes": "This node checks whether the received data was a ending meeting or a URL validation call by Zoom.",
"position": [
-384,
-16
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "8516a75f-6aa9-4b72-828a-f5627d066ecc",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.body.event }}",
"rightValue": "endpoint.url_validation"
}
]
}
},
"notesInFlow": true,
"typeVersion": 2.2
},
{
"id": "78ecf8f8-9fb4-4b14-b079-6038e0693c79",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-656,
-304
],
"parameters": {
"color": 4,
"width": 1104,
"height": 752,
"content": "## Meeting data reception\n"
},
"typeVersion": 1
},
{
"id": "ee6d0810-3968-481b-b214-dd81e7afeb69",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
448,
-304
],
"parameters": {
"color": 2,
"width": 448,
"height": 752,
"content": "## Pulling webinar data - participants and absentees\n\n"
},
"typeVersion": 1
},
{
"id": "b115c42e-89c4-48ab-aad8-2557978053bd",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
896,
-304
],
"parameters": {
"color": 3,
"width": 1312,
"height": 752,
"content": "## Segment all participants and absentees from the past webinar\n\n"
},
"typeVersion": 1
},
{
"id": "6de2ce31-dca8-43aa-ab89-4aeebc327822",
"name": "Tag participant with full attendance1",
"type": "CUSTOM.klicktipp",
"notes": "This node tags the contacts of your first webinar branch for the full attendance if they have attended more than 90% of the webinar.",
"position": [
2016,
224
],
"parameters": {
"email": "={{ $json.email }}",
"tagId": [
"13445224"
],
"resource": "contact-tagging"
},
"credentials": {
"klickTippApi": {
"name": "<your credential>"
}
},
"notesInFlow": true,
"typeVersion": 2
},
{
"id": "27d21cf7-6378-466d-b196-bf11806e2ed8",
"name": "Tag participant for full attendance",
"type": "CUSTOM.klicktipp",
"notes": "This node tags the contacts of your first webinar branch for the full attendance if they have attended more than 90% of the webinar.",
"position": [
2000,
-48
],
"parameters": {
"email": "={{ $json.email }}",
"tagId": [
"13440472"
],
"resource": "contact-tagging"
},
"credentials": {
"klickTippApi": {
"name": "<your credential>"
}
},
"notesInFlow": true,
"typeVersion": 2
},
{
"id": "5296bb95-1c28-4a95-b776-11997cf344be",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
96,
480
],
"parameters": {
"color": 7,
"width": 1104,
"height": 1920,
"content": "Community Node Disclaimer: This workflow uses KlickTipp community nodes.\n\n\n### Introduction\nThis workflow automates the end-to-end integration between Zoom and KlickTipp using n8n. It listens to Zoom meeting events (specifically, when a meeting ends), fetches attendance data, subscribes the participants and tags them in KlickTipp based on how long they attended. This enables precise segmentation for follow-ups and campaign automation based on actual user engagement.\n\n### Benefits\n- **Fully automated tagging**: No manual effort\u2014participants are tagged in KlickTipp based on attendance percentage.\n- **Precise segmentation**: Supports differentiated tags for full, partial, or no attendance per meeting type.\n- **Dual-meeting support**: Handles multiple meeting types (e.g. Beginner vs. Expert) in one automated flow.\n\n### Key Features\n- **Zoom Webhook Listener**:\n - Reacts to `meeting.ended` events.\n - Validates Zoom URL on webhook registration using HMAC.\n- **Zoom API Integration**:\n - Pulls past meeting details and participant list using meeting UUID.\n - Uses `duration` fields to calculate attendance.\n- **Conditional Logic**:\n - Uses Switch nodes to differentiate meetings by topic.\n - Filters out internal users.\n - Applies IF logic to check attendance thresholds (e.g. \u2265\u202f90%, \u2265\u202f60%).\n- **KlickTipp Subscription & Tagging**:\n - Subscribes participant to KlickTipp based on meeting data\n - Applies different tags for each attendance segment:\n - Full attendance (\u2265\u202f90%)\n - General attendance\n - No attendance\n - Supports tagging per meeting name (ex.: meeting E-Mail Zustellung f\u00fcr Anf\u00e4nger vs. meeting E-Mail Zustellung f\u00fcr Experten).\n- **Scalable Structure**:\n - Easily extendable to more meetings by duplicating the Switch and tagging logic.\n\n### Setup Instructions\n**Zoom Preparation**\n - Ensure Zoom API access is enabled.\n - Set up recurring meetings or meetings.\n - Enable webhook event `meeting.ended` for your Zoom webhook and the scopes `meeting:read:meeting`, `meeting:read:list_past_participants`.\n\n**KlickTipp Preparation**\n - Create these custom fields (if not already present):\n - `Zoom | meeting selection` (Text)\n- `Zoom | meeting start` (Date & Time)\n- `Zoom | Join URL` (URL)\n- `Zoom | Registration ID` (Text)\n- `Zoom | Duration meeting` (Text)\n\n - Manually add the following tags:\n - `Zoom meeting E-Mail Zustellung f\u00fcr Anf\u00e4nger`\n - `Zoom meeting E-Mail Zustellung f\u00fcr Anf\u00e4nger attended`\n - `Zoom meeting E-Mail Zustellung f\u00fcr Anf\u00e4nger attended fully`\n - `Zoom meeting E-Mail Zustellung f\u00fcr Anf\u00e4nger not attended`\n - `Zoom meeting E-Mail Zustellung f\u00fcr Experten`\n - `Zoom meeting E-Mail Zustellung f\u00fcr Experten attended`\n - `Zoom meeting E-Mail Zustellung f\u00fcr Experten attended fully`\n - `Zoom meeting E-Mail Zustellung f\u00fcr Experten not attended`\n\n\n**n8n Configuration**\n - Configure the webhook node for the Zoom endpoint and connect it to your webhook.\n - Set up API credentials for Zoom and KlickTipp.\n \n\n### Testing and Deployment\n1. End a Zoom meeting connected to the flow.\n2. Verify:\n - The webhook triggers.\n - Participants are fetched from Zoom.\n - Tags are applied correctly in KlickTipp.\n3. Review KlickTipp contact records for tag and custom field updates.\n\n> \ud83d\udca1 *Pro Tip*: Use test emails and simulate different durations to validate tagging logic.\n\n### Campaign Expansion Ideas\n- Add additional meeting topics and tag logic by extending the Switch node.\n- Trigger personalized email sequences in KlickTipp based on attendance status.\n- Combine this workflow with registration tracking for full-funnel automation.\n- Use conditional routing to send follow-ups only to specific attendance segments.\n\n### Customization\n- Update tag names in the KlickTipp nodes to match your campaign naming convention.\n- Adjust attendance thresholds (e.g. 80% instead of 90%) in IF nodes.\n- Add fallback conditions for missing data or error handling on Zoom API calls.\n- Use more granular time ranges for segmentation of attendance (e.g. 0\u201330%, 30\u201360%, etc.).\n"
},
"typeVersion": 1
},
{
"id": "4ac87f98-1281-41d5-b607-3a4e2919ac2e",
"name": "Filter internal users",
"type": "n8n-nodes-base.filter",
"notes": "This node filters out internal users from the webinar such as the host.",
"position": [
976,
80
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6c66e073-723a-44a2-83c7-0ece10385a3b",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $('Split webinar participants').item.json.internal_user }}",
"rightValue": ""
}
]
}
},
"notesInFlow": true,
"typeVersion": 2.2
},
{
"id": "fc1718e5-321e-4e7e-8cdd-cd6bf34e78e6",
"name": "Split webinar participants",
"type": "n8n-nodes-base.splitOut",
"notes": "This node iterates through all participants.",
"position": [
752,
80
],
"parameters": {
"options": {},
"fieldToSplitOut": "participants"
},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "3a7eb266-fc42-4448-a750-287230ccc4cc",
"name": "Route by webinar name",
"type": "n8n-nodes-base.switch",
"notes": "This node filters for the different webinars via the name in order to process them.",
"position": [
1184,
80
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "E-Mail Zustellung f\u00fcr Anf\u00e4nger",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "8de9cdc6-5393-4daa-acbd-7d14fbcd2586",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $('Listen to ending Zoom meetings').item.json.body.payload.object.topic }}",
"rightValue": "E-Mail Zustellung f\u00fcr Anf\u00e4nger"
}
]
},
"renameOutput": true
},
{
"outputKey": "E-Mail Zustellung f\u00fcr Experten",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "bb59b2cb-b22c-4325-8373-f2a50dcb5ef1",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $('Listen to ending Zoom meetings').item.json.body.payload.object.topic }}",
"rightValue": "E-Mail Zustellung f\u00fcr Experten"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"notesInFlow": true,
"typeVersion": 3.2
},
{
"id": "9e068327-0a37-41a0-95b0-31fdd60ae887",
"name": "Check full attendance",
"type": "n8n-nodes-base.if",
"notes": "This node checks whether the participants have spent more than 90% in the webinar or not.",
"position": [
1744,
-32
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "55a73ac0-4754-4ae4-b6ac-cc3b46b19580",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $('Split webinar participants').item.json.duration }}",
"rightValue": "={{ $('Listen to ending Zoom meetings').item.json.body.payload.object.duration * 60 * 0.9 }}"
}
]
}
},
"notesInFlow": true,
"typeVersion": 2.2
},
{
"id": "6da889bc-4e87-4a27-93bb-a3875469ab7b",
"name": "Check full attendance1",
"type": "n8n-nodes-base.if",
"notes": "This node checks whether the participants have spent more than 90% in the webinar or not.",
"position": [
1760,
240
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "f3673baa-a23b-4f19-91d9-5b1bac26e064",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $('Split webinar participants').item.json.duration }}",
"rightValue": "={{ $('Listen to ending Zoom meetings').item.json.body.payload.object.duration * 60 * 0.9 }}"
}
]
}
},
"notesInFlow": true,
"typeVersion": 2.2
},
{
"id": "3368dabc-71e4-4b5d-a5d7-2cd767806672",
"name": "Wait 1 second",
"type": "n8n-nodes-base.wait",
"notes": "This node waits 1 second to avoid calling the past webinar to early before it is available.",
"position": [
272,
80
],
"parameters": {
"amount": 1
},
"notesInFlow": true,
"typeVersion": 1.1
},
{
"id": "05b7c7ee-4a94-4f6f-b8a7-daddb0f774c9",
"name": "Listen to ending Zoom meetings",
"type": "n8n-nodes-base.webhook",
"notes": "This node is listening to Zoom events and triggers when a webinar was ended.",
"position": [
-608,
-16
],
"parameters": {
"path": "0149ebe4-2dd9-420d-af7c-d60439f22451",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"notesInFlow": true,
"typeVersion": 2.1
},
{
"id": "0c825304-48c3-4d03-9228-9a0f4b806b62",
"name": "Get past Zoom meeting participants",
"type": "n8n-nodes-base.httpRequest",
"notes": "This node pulls data of the webinar participants in order to process them.",
"position": [
512,
80
],
"parameters": {
"url": "=https://api.zoom.us/v2/past_meetings/{{ $('Listen to ending Zoom meetings').item.json.body.payload.object.uuid }}/participants",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "zoomOAuth2Api"
},
"credentials": {
"zoomOAuth2Api": {
"name": "<your credential>"
}
},
"notesInFlow": true,
"typeVersion": 4.2
},
{
"id": "fc622fae-a156-41a2-a10f-601f8aca4dc5",
"name": "Subscribe participant",
"type": "n8n-nodes-klicktipp.klicktipp",
"notes": "This node subscribes the participant based on the meeting data as a new contact in KlickTipp or updates it.",
"position": [
1536,
-32
],
"parameters": {
"email": "={{ $json.user_email }}",
"tagId": "13327964",
"fields": {
"dataFields": [
{
"fieldId": "fieldFirstName",
"fieldValue": "={{ \n // Take the \"name\" field from the current JSON item\n $json.name\n // Remove leading/trailing spaces (just in case)\n .trim()\n // Split the string into an array, using a space as the separator\n .split(\" \")\n // Select the first element of the array (the first word)\n [0] \n}}"
},
{
"fieldId": "fieldLastName",
"fieldValue": "={{ \n /* take \"name\" or empty string if missing */ ($json.name || '')\n /* trim leading/trailing whitespace */ .trim()\n /* split into words by any whitespace */ .split(/\\s+/)\n /* get last word */ .slice(-1)[0]\n /* fallback */ || ''\n}}"
}
]
},
"listId": "358895",
"resource": "subscriber",
"operation": "subscribe"
},
"credentials": {
"klickTippApi": {
"name": "<your credential>"
}
},
"notesInFlow": true,
"typeVersion": 3
},
{
"id": "bd71ca87-dbd7-4525-9ae6-a072069baa67",
"name": "Subscribe participant1",
"type": "n8n-nodes-klicktipp.klicktipp",
"notes": "This node subscribes the participant based on the meeting data as a new contact in KlickTipp or updates it.",
"position": [
1536,
240
],
"parameters": {
"email": "={{ $json.user_email }}",
"tagId": "13327965",
"fields": {
"dataFields": [
{
"fieldId": "fieldFirstName",
"fieldValue": "={{ \n // Take the \"name\" field from the current JSON item\n $json.name\n // Remove leading/trailing spaces (just in case)\n .trim()\n // Split the string into an array, using a space as the separator\n .split(\" \")\n // Select the first element of the array (the first word)\n [0] \n}}"
},
{
"fieldId": "fieldLastName",
"fieldValue": "={{ /* take \"name\" or empty string if missing */ ($json.name || '') /* trim leading/trailing whitespace */ .trim() /* split into words by any whitespace */ .split(/\\s+/) /* get last word */ .slice(-1)[0] /* fallback */ || '' }}"
}
]
},
"listId": "358895",
"resource": "subscriber",
"operation": "subscribe"
},
"credentials": {
"klickTippApi": {
"name": "<your credential>"
}
},
"notesInFlow": true,
"typeVersion": 3
}
],
"connections": {
"Crypto": {
"main": [
[
{
"node": "Build Validation Body",
"type": "main",
"index": 0
}
]
]
},
"Wait 1 second": {
"main": [
[
{
"node": "Get past Zoom meeting participants",
"type": "main",
"index": 0
}
]
]
},
"URL Validation check": {
"main": [
[
{
"node": "Crypto",
"type": "main",
"index": 0
}
],
[
{
"node": "Build Normal Event Body",
"type": "main",
"index": 0
}
]
]
},
"Build Validation Body": {
"main": [
[
{
"node": "Respond to Webhook (Validation)",
"type": "main",
"index": 0
}
]
]
},
"Check full attendance": {
"main": [
[
{
"node": "Tag participant for full attendance",
"type": "main",
"index": 0
}
],
[]
]
},
"Filter internal users": {
"main": [
[
{
"node": "Route by webinar name",
"type": "main",
"index": 0
}
]
]
},
"Route by webinar name": {
"main": [
[
{
"node": "Subscribe participant",
"type": "main",
"index": 0
}
],
[
{
"node": "Subscribe participant1",
"type": "main",
"index": 0
}
]
]
},
"Subscribe participant": {
"main": [
[
{
"node": "Check full attendance",
"type": "main",
"index": 0
}
]
]
},
"Check full attendance1": {
"main": [
[
{
"node": "Tag participant with full attendance1",
"type": "main",
"index": 0
}
],
[]
]
},
"Subscribe participant1": {
"main": [
[
{
"node": "Check full attendance1",
"type": "main",
"index": 0
}
]
]
},
"Build Normal Event Body": {
"main": [
[
{
"node": "Respond to Webhook (Events)",
"type": "main",
"index": 0
}
]
]
},
"Split webinar participants": {
"main": [
[
{
"node": "Filter internal users",
"type": "main",
"index": 0
}
]
]
},
"Respond to Webhook (Events)": {
"main": [
[
{
"node": "Wait 1 second",
"type": "main",
"index": 0
}
]
]
},
"Listen to ending Zoom meetings": {
"main": [
[
{
"node": "URL Validation check",
"type": "main",
"index": 0
}
]
]
},
"Get past Zoom meeting participants": {
"main": [
[
{
"node": "Split webinar participants",
"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.
klickTippApizoomOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Community Node Disclaimer: This workflow uses KlickTipp community nodes.
Source: https://n8n.io/workflows/8910/ — 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.
Community Node Disclaimer: This workflow uses KlickTipp community nodes.
Community Node Disclaimer: This workflow uses KlickTipp community nodes.
Community Node Disclaimer: This workflow uses KlickTipp community nodes.
Struggling with inaccurate Meta Ads tracking due to iOS 14+ and ad blockers? 📉 This workflow is your solution. It provides a robust, server-side endpoint to reliably send conversion events directly to
Community Node Disclaimer: This workflow uses KlickTipp community nodes.