Send Slack alerts for low Zendesk CSAT scores using n8n

low complexityCost: $0-24/moRecommended

Prerequisites

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:write scope 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)

Why n8n?

n8n is the best option for teams that want near-real-time CSAT alerts without writing or maintaining code. The visual workflow builder makes the polling logic transparent — you can see each step, test with real data, and modify the alert format without touching a script. Self-hosted n8n is free with unlimited executions; Cloud starts at $24/mo.

While Zendesk triggers can natively alert on bad ratings (Professional plan+), n8n adds rich Block Kit formatting, external context enrichment, and batch processing that native Slack notifications can't match. The trade-off is a polling delay (up to 5 minutes) vs. instant native triggers. For most teams, the richer alerts are worth the slight delay.

How it works

  • Schedule Trigger runs the workflow every 5 minutes
  • HTTP Request polls the Zendesk satisfaction ratings API filtered to score=bad, sorted newest first
  • Code node filters to ratings submitted since the last poll cycle, preventing duplicate alerts
  • HTTP Request fetches full ticket details (subject, requester, assignee) for each new bad rating
  • HTTP Request posts a Block Kit message to Slack with ticket details, customer feedback, and a direct link to the Zendesk ticket

Step 1: Set up Zendesk credentials in n8n

Go to Credentials → Add credential → Zendesk API:

  • Subdomain: your Zendesk subdomain (the xxx part of xxx.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.

Why 5 minutes?

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.

Time window overlap

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.

Include the customer comment

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

  1. Set the workflow to Active
  2. To test immediately, submit a bad CSAT rating on a solved ticket in your Zendesk sandbox
  3. Wait for the next 5-minute poll cycle (or trigger the workflow manually)
  4. Verify the Slack alert appears with the correct ticket details and link
  5. Enable Retry On Fail on all HTTP Request nodes for resilience against temporary API errors
No CSAT webhook exists in Zendesk

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.

Troubleshooting

Common questions

Will this hit Zendesk API rate limits?

No. Zendesk allows roughly 700 requests per minute on Professional plans. This workflow makes 1 request per poll cycle (the ratings endpoint), plus 1 per bad rating for ticket details. Even with 10 bad ratings per cycle at 5-minute intervals, that's well under 1% of your rate limit.

How do I prevent duplicate alerts for the same bad rating?

The Code node filters by a 5-minute time window. For more robust deduplication, store the last-seen rating ID in n8n's static data ($getWorkflowStaticData('global').lastSeenId) and filter by ID instead of time. This eliminates gaps if a poll cycle is delayed or skipped.

Should I use n8n Cloud or self-hosted for this?

For a single workflow like this, self-hosted is free and straightforward — a $5/mo VPS handles it. n8n Cloud ($24/mo starter) makes sense if you're running multiple workflows and don't want to manage infrastructure. The workflow is identical on both.

Can I also reopen tickets with bad ratings automatically?

Yes. Step 7 in this guide shows how to add an HTTP Request node that reopens the ticket and adds a csat-bad tag. This creates a follow-up queue your team can work through. Skip this if you prefer manual triage.

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.

Looking to scale your AI operations?

We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.