How to Sync Zendesk Custom Statuses

In Zendesk, setting the regular ticket status only updates the status category such as new, open, pending, hold, or solved. If your workflow relies on custom statuses, you must update the custom_status_id field instead.

This is useful when syncing from platforms like Jira, Azure DevOps, or others into Zendesk, and you want the ticket to reflect a specific custom status rather than only its generic status category.


Script Overview

1. Retrieve the available Zendesk custom statuses
The script calls:

/api/v2/custom_status

This returns all custom statuses configured in Zendesk, including fields such as:

  • id

  • status_category

  • agent_label

  • description

  • active

2. Map the incoming Exalate status to a Zendesk custom status label
Instead of mapping directly to the numeric ID, the script maps the incoming status to the Zendesk agent_label.

3. Update the Zendesk ticket using custom_status_id
Once the matching custom status is found, the script updates the ticket with a PUT request.


Final Solution

Zendesk Incoming sync

def statusLabelMap = [
  'New'                  : 'Hold',
  'Active'               : 'Hold',
  'In QA'                : 'Hold',
  'Waiting for customer' : 'Pending',
  'Ready for QA'         : 'Hold',
  'Ready to Deploy'      : 'Hold',
  'Closed'               : 'Solved'
]

def desiredLabel = statusLabelMap[replica.status?.name]

if (desiredLabel) {
    def customStatusesResponse = httpClient.get("/api/v2/custom_statuses.json")
    def customStatuses = customStatusesResponse?.custom_statuses ?: []

    def matchedStatus = customStatuses.find { cs ->
        cs.active && cs.agent_label?.toString()?.equalsIgnoreCase(desiredLabel)
    }

    def desiredCustomStatusId = matchedStatus?.id as Long

    if (desiredCustomStatusId) {
        def currentTicket = httpClient.get("/api/v2/tickets/${issue.key}.json")
        def currentCustomStatusId = currentTicket?.ticket?.custom_status_id as Long

        if (currentCustomStatusId != desiredCustomStatusId) {
            httpClient.put(
                "/api/v2/tickets/${issue.key}.json",
                """{
                  "ticket": {
                    "custom_status_id": ${desiredCustomStatusId}
                  }
                }"""
            )
        }
    }
}

Zendesk Outgoing sync

def ticket = httpClient.get("/api/v2/tickets/${issue.key}.json")
def currentStatusId = ticket?.ticket?.custom_status_id as Long

if (currentStatusId) {
    def customStatuses = httpClient.get("/api/v2/custom_statuses.json")?.custom_statuses ?: []
    def matchedStatus = customStatuses.find { (it.id as Long) == currentStatusId }

    replica.status = matchedStatus?.agent_label
}

Alternative simpler approach

If you prefer, you can still use a direct ID mapping like this:

def statusMap = [
  'New'                  : 23181153952276,
  'Active'               : 23181153952276,
  'In QA'                : 23181153952276,
  'Waiting for customer' : 14559538794898,
  'Ready for QA'         : 23181153952276,
  'Ready to Deploy'      : 23181153952276,
  'Closed'               : 14559538794898
]

That works fine, but the label-based approach is usually easier to read and maintain.


Version

Tested on Version 5.32.1