Send Slack alerts for low Zendesk CSAT scores using n8n
Prerequisites
- n8n instance (cloud or self-hosted)
- Zendesk API credentials: subdomain, agent email, and API token (Admin Center → Apps and integrations → APIs → Zendesk API → enable Token Access)
- Slack Bot Token with
chat:writescope and the bot invited to your alert channel - Slack channel ID for your CSAT alerts channel (right-click channel → View channel details → copy ID at bottom)
Overview
Zendesk has no webhook or trigger for CSAT responses. When a customer rates a ticket "Bad," there's no event fired that you can listen for. The only way to detect new bad ratings is to poll the satisfaction ratings API on a schedule.
This workflow runs every 5 minutes, queries the Zendesk satisfaction ratings endpoint filtered to bad scores, checks for new ratings since the last poll, fetches ticket details for context, and sends a Slack Block Kit alert with a direct link to the ticket. This is the recommended approach because it provides near-real-time alerts with rich formatting and no code to maintain.
Step 1: Set up Zendesk credentials in n8n
Go to Credentials → Add credential → Zendesk API:
- Subdomain: your Zendesk subdomain (the
xxxpart ofxxx.zendesk.com) - Email: your agent email address
- API Token: the token from Admin Center → APIs → Zendesk API
Test the connection to verify access.
Step 2: Add a Schedule Trigger node
Add a Schedule Trigger node as the workflow entry point:
- Interval: Every 5 minutes
This is the polling frequency. Five minutes balances responsiveness with API rate limits.
Zendesk allows roughly 700 API requests per minute. This workflow uses 2-3 requests per poll cycle (1 for ratings, 1 per bad rating for ticket details). At 5-minute intervals, that's well under 1% of your rate limit — leaving plenty of headroom for other integrations.
Step 3: Poll the Zendesk satisfaction ratings API
Add an HTTP Request node:
- Method: GET
- URL:
https://YOUR_SUBDOMAIN.zendesk.com/api/v2/satisfaction_ratings?score=bad&sort_order=desc&per_page=10 - Authentication: Predefined Credential Type → Zendesk API (or use Basic Auth with
email/token:api_token)
The score=bad parameter filters to only unsatisfied ratings. The sort_order=desc returns newest first so you only need the first page.
Step 4: Filter to new ratings only
Add a Code node to deduplicate. Since we poll every 5 minutes, we only want ratings that appeared since the last poll:
const ratings = $input.first().json.satisfaction_ratings || [];
const fiveMinAgo = new Date(Date.now() - 5 * 60 * 1000).toISOString();
const newBadRatings = ratings.filter(r => r.updated_at > fiveMinAgo);
if (newBadRatings.length === 0) {
return []; // No new bad ratings — stop workflow
}
return newBadRatings.map(r => ({
json: {
ratingId: r.id,
ticketId: r.ticket_id,
comment: r.comment || '(no comment)',
createdAt: r.updated_at,
}
}));If no new bad ratings exist, the workflow stops here — no unnecessary Slack or Zendesk API calls.
If a poll cycle takes longer than expected or n8n skips an interval, you could miss ratings. A safer approach is to store the last-seen rating ID in n8n's static data and filter by ID instead of time. For most teams, the 5-minute time window is reliable enough.
Step 5: Fetch ticket details
Add an HTTP Request node to get context for each bad rating:
- Method: GET
- URL:
https://YOUR_SUBDOMAIN.zendesk.com/api/v2/tickets/{'{'}$json.ticketId{'}'}.json - Authentication: Same Zendesk credentials
- Batching: Process items individually (one request per bad rating)
This returns the ticket subject, requester name, assignee, tags, and other details you'll include in the Slack alert.
Step 6: Post a Slack Block Kit alert
Add an HTTP Request node (or Slack node) to post the alert:
- Method: POST
- URL:
https://slack.com/api/chat.postMessage - Headers:
Authorization: Bearer YOUR_SLACK_BOT_TOKEN - Body (JSON):
{
"channel": "CSAT_CHANNEL_ID",
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "Bad CSAT Rating Received",
"emoji": true
}
},
{
"type": "section",
"fields": [
{"type": "mrkdwn", "text": "*Ticket:* #{{$json.ticketId}}"},
{"type": "mrkdwn", "text": "*Subject:* {{$json.ticket.subject}}"},
{"type": "mrkdwn", "text": "*Customer:* {{$json.ticket.requester.name}}"},
{"type": "mrkdwn", "text": "*Assignee:* {{$json.ticket.assignee_id}}"}
]
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Customer comment:* {{$json.comment}}"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {"type": "plain_text", "text": "View in Zendesk"},
"url": "https://YOUR_SUBDOMAIN.zendesk.com/agent/tickets/{{$json.ticketId}}"
}
]
}
]
}Replace YOUR_SUBDOMAIN with your Zendesk subdomain and CSAT_CHANNEL_ID with your Slack channel ID.
When a customer rates "Bad," Zendesk optionally lets them leave a comment explaining why. Including this in the Slack alert gives the follow-up agent immediate context about what went wrong, without needing to open the ticket first.
Step 7: Reopen the ticket (optional)
If your team wants bad-rated tickets automatically reopened for follow-up, add another HTTP Request node:
- Method: PUT
- URL:
https://YOUR_SUBDOMAIN.zendesk.com/api/v2/tickets/{'{'}$json.ticketId{'}'}.json - Body:
{
"ticket": {
"status": "open",
"tags": ["csat-bad", "needs-followup"]
}
}This reopens the ticket and adds tags so agents can filter to all bad-CSAT tickets in a Zendesk View.
Step 8: Activate and test
- Set the workflow to Active
- To test immediately, submit a bad CSAT rating on a solved ticket in your Zendesk sandbox
- Wait for the next 5-minute poll cycle (or trigger the workflow manually)
- Verify the Slack alert appears with the correct ticket details and link
- Enable Retry On Fail on all HTTP Request nodes for resilience against temporary API errors
You must poll the API on a schedule. Polling every 5 minutes balances responsiveness with API rate limits. Zendesk allows roughly 700 requests per minute on Professional plans. Do not poll more frequently than every 2 minutes — it wastes executions and provides minimal benefit since customers rarely submit ratings in real-time.
Cost
- n8n Cloud Starter: $0-24/mo depending on execution volume. This workflow uses approximately 4 node executions per poll cycle. At 5-minute intervals, that's ~1,152 executions per day — well within the Starter plan limits.
- n8n self-hosted: Free. Only costs are your hosting infrastructure.
Need help implementing this?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.