Auto-triage and tag Zendesk tickets using n8n
Prerequisites
- n8n instance (cloud or self-hosted)
- Zendesk API credentials: account email and API token (Admin Center → Apps and integrations → APIs → Zendesk API)
- Your Zendesk subdomain (the
your-companypart ofyour-company.zendesk.com)
Overview
This workflow uses the Zendesk Trigger node to receive new ticket events, a Code node to run keyword matching against a topic map, and an HTTP Request node to update the ticket with tags and a group assignment via the Zendesk API. Because n8n sits outside Zendesk, you can extend this workflow to also log to a spreadsheet, notify Slack, or call other APIs as part of the same triage step.
Step 1: Set up Zendesk credentials in n8n
Create a new credential in n8n:
- Type: Zendesk API
- Subdomain: your Zendesk subdomain (e.g.,
acme) - Email: your Zendesk account email
- API Token: your Zendesk API token
Zendesk API auth uses the format {'{'}email{'}'}/token:{'{'}api_token{'}'} as the username with Basic auth. n8n handles this automatically when you use the Zendesk credential type.
Step 2: Add a Zendesk Trigger node
Add a Zendesk Trigger node as the workflow trigger:
- Credential: Select the Zendesk credential you just created
- Event: Ticket Created
This node creates a webhook in your Zendesk instance automatically. When a new ticket arrives, Zendesk sends the ticket data to n8n.
The Zendesk Trigger node delivers the full ticket object including id, subject, description, tags, status, group_id, and requester details. You'll use subject and description for keyword matching.
Step 3: Add a Code node for keyword matching
Add a Code node connected to the trigger. This node checks the ticket text against a topic-keyword map and returns the matched tags and target group:
const ticket = $input.first().json;
const subject = (ticket.subject || '').toLowerCase();
const description = (ticket.description || '').toLowerCase();
const text = `${subject} ${description}`;
const topicMap = {
'topic-billing': ['invoice', 'charge', 'billing', 'payment', 'receipt', 'subscription', 'refund'],
'topic-shipping': ['tracking', 'delivery', 'shipping', 'package', 'lost', 'delayed', 'transit'],
'topic-product': ['broken', 'defective', 'quality', 'size', 'color', 'damaged', 'wrong item'],
'topic-account': ['password', 'login', 'account', 'access', 'locked', 'sign in', 'reset'],
'topic-technical': ['error', 'bug', 'crash', 'not loading', 'api', 'integration', 'timeout'],
};
const groupMap = {
'topic-billing': 'Billing Support',
'topic-shipping': 'Shipping Support',
'topic-product': 'Product Support',
'topic-account': 'Account Support',
'topic-technical': 'Technical Support',
};
const matchedTopics = [];
for (const [tag, keywords] of Object.entries(topicMap)) {
if (keywords.some(kw => text.includes(kw))) {
matchedTopics.push(tag);
}
}
const primaryTopic = matchedTopics[0] || 'topic-uncategorized';
return [{
json: {
ticketId: ticket.id,
tags: matchedTopics.length > 0 ? matchedTopics : ['topic-uncategorized'],
group: groupMap[primaryTopic] || 'General Support',
matchedTopics,
}
}];The Code node outputs the ticket ID, an array of matched topic tags, and the target group name.
When a ticket matches multiple topics, the first match in topicMap becomes the primary group assignment. Order your topics from most specific (Technical) to least specific (Shipping) if you want specificity to win ties.
Step 4: Look up group IDs
The Zendesk API requires a numeric group_id, not a group name. Add an HTTP Request node to fetch your groups:
- Method: GET
- URL:
https://{'{'}your-subdomain{'}'}.zendesk.com/api/v2/groups.json - Authentication: Predefined Credential Type → Zendesk API
- Credential: Select your Zendesk credential
Then add another Code node to map the group name to the ID:
const groups = $('HTTP Request').first().json.groups;
const targetGroup = $('Code').first().json.group;
const classification = $('Code').first().json;
const groupId = groups.find(g => g.name === targetGroup)?.id;
return [{
json: {
...classification,
groupId: groupId || null,
}
}];Group IDs rarely change. After you look them up once, you can hardcode them in the Code node to skip the extra API call. Use the format: const groupIds = {'{'} 'Billing Support': 12345, 'Shipping Support': 67890 {'}'}.
Step 5: Update the ticket via Zendesk API
Add a final HTTP Request node to apply the tags and group assignment:
- Method: PUT
- URL:
https://{'{'}your-subdomain{'}'}.zendesk.com/api/v2/tickets/{'{'}$json.ticketId{'}'}.json - Authentication: Predefined Credential Type → Zendesk API
- Credential: Select your Zendesk credential
- Body Content Type: JSON
- Body:
{
"ticket": {
"tags": "{{ $json.tags }}",
"group_id": "{{ $json.groupId }}"
}
}The Zendesk API's tags field on PUT replaces the entire tag array. Since you're processing newly created tickets, the tag list is typically empty. If you need to preserve existing tags, fetch the current ticket first and merge the arrays in a Code node before updating.
Step 6: Add error handling and activate
- Enable Retry On Fail on the HTTP Request nodes (2 retries, 5-second wait)
- Add an Error Workflow to alert you via email or Slack if the workflow fails
- Run a test by creating a ticket in Zendesk with subject "Question about my invoice"
- Verify the
topic-billingtag appears and the ticket is assigned to Billing Support - Toggle the workflow to Active
Cost
- n8n Cloud: Each ticket triggers ~4 node executions. 500 tickets/month = ~2,000 executions. Fits within the Starter plan.
- Self-hosted: Free. You provide the infrastructure.
Need help implementing this?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.