# How to Sync Zendesk Custom Statuses

**URL:** <https://community.exalate.com/t/how-to-sync-zendesk-custom-statuses/7811>\
**Category:** Script Library\
**Tags:** status, zendesk\
**Created:** [March 13, 2026, 9:01pm UTC](https://community.exalate.com/t/how-to-sync-zendesk-custom-statuses/7811 "2026-03-13T21:01:54Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Javier](https://dub1.discourse-cdn.com/flex018/user_avatar/community.exalate.com/javier/32/338_2.png) [@Javier](https://community.exalate.com/u/Javier)\
**Post date:** [March 13, 2026, 9:01pm UTC](https://community.exalate.com/t/how-to-sync-zendesk-custom-statuses/7811/1 "2026-03-13T21:01:54Z")

</div>

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:

```auto
/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

```auto
def statusLabelMap = [
  'New' : 'New',
  'Active' : 'In Progress',
  '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

```auto
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:

```auto
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
