---
ref: workflow:support-ticket-churn-risk
title: Flag churn risk in new support tickets for customer success
author: thedogwiththedataonit
tools: [tool:zendesk/search, tool:typesafe/answer-typed-questions, tool:hubspot/search-crm-records, tool:zendesk/update-ticket, tool:slack/find-user-by-email, tool:slack/post-message]
tags: [capability:classify-signals, capability:manage-crm, capability:manage-tasks, capability:route-alerts, capability:search-conversations, channel:chat, has:api, motion:retention]
updated: 2026-09-29
---

# Flag churn risk in new support tickets for customer success

Reads new Zendesk tickets, scores churn risk and urgency with Jev, tags and prioritizes the risky ones, and alerts each owner in Slack.

Set up the tools below, then run the steps in order for the user, carrying each step's results into the next. The run is done when the user has the outcome below.

## Outcome

- A table of every new ticket with its churn-risk level, issue type, urgency and Jev's confidence.
- Each at-risk ticket tagged, reprioritized and given an internal note in Zendesk.
- A Slack post for each at-risk ticket that mentions its HubSpot owner and carries a drafted check-in.

## Inputs

Ask the user for these before you start.

- `since`: when the last run started, as a time, so only newer tickets are read, e.g. 2026-09-28T09:00:00Z
- `risk_level`: the lowest churn-risk level that counts as at risk, e.g. weighing a downgrade or another tool
- `min_confidence`: how sure Jev must be before its answer is used without you, e.g. 0.8; a yes-or-no answer counts as yes at or above it and as no at or below 1 minus it
- `risk_tag`: the Zendesk tag that marks an at-risk ticket, e.g. churn_risk
- `cs_channel`: the Slack channel customer success watches, e.g. #cs-alerts

## Set up

### Zendesk (tool:zendesk/search, tool:zendesk/update-ticket)

Use the API.

- Base URL: https://{subdomain}.zendesk.com
- Search tickets, users and organizations: `GET /api/v2/search?query={query}`
- Update a ticket: `PUT /api/v2/tickets/{ticket_id}`
- Auth: send the header `Authorization: Bearer $ZENDESK_OAUTH_TOKEN`
- Get a key: https://developer.zendesk.com/documentation/authentication/oauth-migration/#getting-your-first-token

Note: `{subdomain}` is your Zendesk subdomain, as in `acme.zendesk.com`. Tokens from OAuth clients created since April 30, 2026 expire after 30 minutes.

Notes:

- Search tickets, users and organizations: Returns at most 1,000 results per query and 100 per page; for more, use `GET /api/v2/search/export`.
- Update a ticket: A `comment` with `public: true` is a reply the requester can see; `public: false` makes it an internal note. This endpoint has its own rate limit and returns 429 when it runs out.

### Answer typed questions (TypeSafe AI, tool:typesafe/answer-typed-questions)

Use the API.

- Base URL: https://api.typesafe.ai
- Endpoint: `POST /v1/systemone`
- Auth: send the header `Authorization: Bearer $TYPESAFE_API_KEY`
- Get a key: https://console.typesafe.ai

Note: Put every question about one record in one request. `GET /v1/models` lists the models and is the cheapest check of a key; a request over the rate limit gets a 429 with a Retry-After header.

Note: Send `state`, `model` (`jev-latest`) and named `questions`, each with `type`, `instructions` and `criteria`: a choice maps labels to descriptions, a score lists its levels in order, a noul's is optional. Read a score by its most likely level; send unsure answers to a person.

### Search CRM records (HubSpot, tool:hubspot/search-crm-records)

Use the first option your agent supports.

Note: The MCP tool takes up to five groups of six filters and returns up to 200 records per page.

#### MCP (official, remote)

Add this server to your agent's MCP settings, then sign in when asked.

```json
{ "mcpServers": { "hubspot": { "url": "https://mcp.hubspot.com" } } }
```

Call the MCP tool `search_crm_objects`.

#### API (official)

- Base URL: https://api.hubapi.com
- Endpoint: `POST /crm/objects/2026-09/{objectType}/search`
- Auth: send the header `Authorization: Bearer $HUBSPOT_API_KEY`

Note: Use a service key or an app's static access token with the CRM scopes the calls need; paths carry a dated version such as `2026-09`.

### Slack (tool:slack/find-user-by-email, tool:slack/post-message)

Use the first option your agent supports.

Notes:

- Find a user by email: Needs the `users:read.email` scope; a deactivated user returns `users_not_found`.
- Post a message: Needs the `chat:write` scope. Over MCP it posts as the signed-in user; the API and CLI post as the app's bot.

#### MCP (official, remote)

Add this server to your agent's MCP settings, then sign in when asked.

```json
{ "mcpServers": { "slack": { "url": "https://mcp.slack.com/mcp" } } }
```

- Find a user by email: call the MCP tool `slack_search_users`
- Post a message: call the MCP tool `slack_send_message`

#### CLI (official)

Install the command, then confirm it runs.

```sh
curl -fsSL https://downloads.slack-edge.com/slack-cli/install.sh | bash
slack --version
```

- Find a user by email: run `slack api users.lookupByEmail`
- Post a message: run `slack api chat.postMessage`

Set `$SLACK_BOT_TOKEN` in your environment first (get a key: https://api.slack.com/apps).

Before step 1, confirm access to each service with its cheapest read-only call, like a list or a search. Never send or change anything to test access.

## Steps

1. **Pull new tickets** with Search tickets, users and organizations (Zendesk). Search for tickets (`type:ticket`) created after `since`, leaving out those already tagged `risk_tag`. Keep each ticket's ID, subject, description, priority, tags and requester ID.
2. **Read each ticket** with Answer typed questions (TypeSafe AI). Send the subject and description as the state, with a score `churn_risk` on four levels (no risk; frustrated; weighing a downgrade or another tool; says they will cancel), a choice `issue` of bug, billing, how_to, missing_feature, performance, access, cancellation and other, a score `urgency` on three levels (can wait; this week; today), a noul `mentions_competitor` and a noul `asks_for_refund`. Keep every answer with its confidence or probability.
3. **Check the unsure ones with the user** yourself. Show the user every ticket whose `churn_risk` confidence is below `min_confidence`, and keep the level the user picks. Keep the tickets whose most likely `churn_risk` level is `risk_level` or higher, and look up each one's requester email, a read-only call.
4. **Find the owner** with Search CRM records (HubSpot). Search companies by each at-risk requester's email domain, skipping free email domains. Keep the company's record ID, name and `hubspot_owner_id`, and look up each owner's name and email, a read-only call. Note the tickets with no match.
5. **Mark the ticket** with Update a ticket (Zendesk). After the user approves, add `risk_tag` alongside each at-risk ticket's existing tags, raise its priority to high, or urgent when its urgency is today, and add an internal note, not a public reply, with the churn-risk level, the issue and the competitor and refund probabilities.
6. **Draft the check-in** yourself. Write a short note from each company's owner, by name, that names the problem in the ticket, says what happens next and offers a call, leaving a [fill in] for any fix date or commitment you don't have. Show the drafts to the user.
7. **Find the owners in Slack** with Find a user by email (Slack). Look up each owner's email. Keep their Slack user ID.
8. **Alert the owner** with Post a message (Slack). Post each at-risk ticket to `cs_channel`, mentioning its owner, with the company, the churn-risk level, the issue, the competitor and refund probabilities, the ticket ID and the drafted check-in.

## Notes

Judge risk by the most likely level, not the expected score. Start with `risk_level` at weighing a downgrade or another tool, and move it once you have a week of results. Support keeps answering the ticket as usual; the account owner hears about it the same day.

Run it every few hours with `since` set to the time the previous run started.

## Rules

- Use only the services set up above. The read-only calls they need, like listing ids or polling for results, are fine.
- Ask the user before anything that sends messages, costs money, or changes data, and say how many records it touches. One approval covers a batch the user has seen.
- Never print API keys.
