Moving tickets from Zendesk to JSM Cloud

Hi
Exalate support give idea that community can help :slight_smile:

I have created connection between Zendesk and JSM with plan move all tickets from Zendesk to JSM. Now i have allready set up some connection and i have some fields what i need to get into JSM also. Yes i know that i cant have assignee and reporter because lot of people have left and Atlassian Cloud does not support accountID if there is no license. But i created allready customfields into JSM

Zendesk_Assignee

{ @key : 16609674611740}

Zendesk_Reporter

{ @key : 16609674611740}

Right now there is key but how i could get into there e-mail address from orginal user?

Also in zendesk is

  • cc_emails
  • email_cc_ids
  • followers_ids

How i should write mapping for this e-mail values to get into customfield JSM?

with best
Urmo

Great to see you’re making progress with your Zendesk to Jira Service Management (JSM) Cloud migration! You’re right about the assignee/reporter limitations in Atlassian Cloud—if the user doesn’t exist or lacks a license, you can’t set them as the actual assignee/reporter, but you can store their info in custom fields.

To map Zendesk user fields (like assignee, reporter, cc_emails, etc.) to custom fields in JSM and store their email addresses, you’ll want to extract the email from the Zendesk ticket and set it into your JSM custom field during the sync.

Here’s how you can approach this in your Exalate sync script (on the JSM incoming side):

  • For the Zendesk assignee’s email:
    issue.customFields."Zendesk_Assignee" = replica.assignee?.email
    
  • For the Zendesk reporter’s email:
    issue.customFields."Zendesk_Reporter" = replica.reporter?.email
    
  • For cc_emails (which is usually a list), you can join them into a string or handle as needed:
    issue.customFields."Zendesk_CC_Emails" = replica.cc_emails?.join(", ")
    
  • For email_cc_ids or followers_ids, you’ll need to fetch the corresponding user objects and extract their emails, if available in the replica.

If you want to see more advanced user field mapping examples (including handling cases where emails aren’t available), check out the official documentation:

And for more Zendesk-Jira integration use cases and script ideas:

This approach will let you preserve the original user information in your custom fields, even if you can’t set them as the actual assignee/reporter in JSM Cloud.

How to get zendesk ticket created time into jira customfield Zendesk_createddate?

Hi @urmo ,

Could you please help me capture the Zendesk ticket created timestamp and send it through the replica?

Preferred (simple) approach

On the Zendesk outgoing sync, can you add:

replica.zendeskCreated = issue.created

Once we see zendeskCreated in the payload, mapping it on the Jira/JSM incoming side becomes straightforward:

issue.customFields."Zendesk_createddate".value = replica.zendeskCreated

Fallback (only if the above doesn’t show in payload)

If issue.created is not available / not coming through correctly, we can fetch it directly from the Zendesk API, parse it, and then pass it via the replica.

Example:

def response = httpClient.get("/api/v2/tickets/${ticket.id}.json")
def createdAt = response?.ticket?.created_at  // e.g. "2026-02-02T10:15:30Z"
replica.zendeskCreated = createdAt

Then we can map the same way on Jira:

issue.customFields."Zendesk_createddate".value = replica.zendeskCreated

If you can add the simple line first and confirm whether zendeskCreated appears in the outgoing payload, that would be ideal — we may not need the API route at all.

Thanks, Dhiren!

Hi

I updated Outgoing sync:
// Set Zendesk_follow to a comma-separated list of follower emails, only if the field exists

if (issue.customFields.containsKey(‘Zendesk_follow’)) {

*if (replica.followers instanceof List) {*

    *issue.customFields.'Zendesk_follow'.value = replica.followers.collect { it?.email ?: '' }.findAll { it }.join(', ')*

*} else {*

    *issue.customFields.'Zendesk_follow'.value = ''*

*}*

}

// Set Zendesk_cc to a comma-separated list of CC emails, only if the field exists

if (issue.customFields.containsKey(‘Zendesk_cc’)) {

*if (replica.ccs instanceof List) {*

    *issue.customFields.'Zendesk_cc'.value = replica.ccs.collect { it?.email ?: '' }.findAll { it }.join(', ')*

*} else {*

    *issue.customFields.'Zendesk_cc'.value = ''*

*}*

}

// Map other fields as per current configuration

replica.summary = issue.summary

replica.comments = issue.comments

replica.attachments = issue.attachments

replica.type = issue.type

replica.status = issue.status

replica.priority = issue.priority

replica.assignee = issue.assignee

replica.reporter = issue.reporter

replica.key = issue.key

replica.labels = issue.labels

replica.description = issue.description

replica.zendeskCreated = issue.created

And incoming sync:

// Helper function to safely set a custom field value if the field exists

void setCustomFieldValue(issue, fieldName, value) {

*if (issue.customFields\[fieldName\] != null) {*

    *issue.customFields\[fieldName\].value = value*

*}*

}

if (firstSync) {

*issue.projectKey = "ZEN"*

}

def defaultUser = nodeHelper.getUserByEmail(“jira-placeholder@lhv.ee”)

issue.reporter = nodeHelper.getUserByEmail(replica.requester?.email) ?: defaultUser

issue.assignee = nodeHelper.getUserByEmail(replica.assignee?.email) ?: defaultUser

// Set custom field Zendesk_follow to comma-separated emails from followers, or empty string

// if (replica.followers instanceof List) {

// setCustomFieldValue(issue, “Zendesk_follow”, replica.followers.collect { it?.email }.findAll { it }.join(“,”))

// } else {

// setCustomFieldValue(issue, “Zendesk_follow”, “”)

// }

// Set summary: use replica.summary if not empty/null, otherwise use replica.key if not empty/null, otherwise default

if (replica.summary?.trim()) {

*// Replace all newline characters in the summary with a space*

*issue.summary = replica.summary.replaceAll(/\[\\r\\n\]+/, ' ')*

} else if (replica.key?.trim()) {

*issue.summary = replica.key*

} else {

*issue.summary = 'No summary provided'*

}

issue.description = replica.description

issue.comments = commentHelper.mergeComments(issue, replica)

issue.attachments = attachmentHelper.mergeAttachments(issue, replica)

issue.labels = replica.labels

issue.type = nodeHelper.getIssueType(replica.type?.name, issue.projectKey) ?: nodeHelper.getIssueType(“Zendesk”, issue.projectKey)

def priorityMap = [

*"urgent": "Highest",*

*"high"  : "High",*

*"normal": "Medium",*

*"low"   : "Low"*

]

def priorityName = priorityMap[replica.priority?.name?.toLowerCase()] ?: “Medium”

issue.priority = nodeHelper.getPriority(priorityName)

// FIX: Improve status mapping to be case-insensitive and include missing statuses

def statusMap = [

*"new"      : "Open",*

*"open"     : "Open", // Added mapping for "open"*

*"pending"  : "In Progress",*

*"waiting"  : "In Progress",*

*"solved"   : "Closed",*

*"rejected" : "Closed",*

*"done"     : "Closed"*

]

def remoteStatusName = replica.status?.name?.toLowerCase()

// Only update status if a valid mapping exists to avoid accidental changes

if (statusMap.containsKey(remoteStatusName)) {

*issue.setStatus(statusMap\[remoteStatusName\])*

}

// Set custom fields for created date and key

issue.customFields.“Zendesk_createddate”.value = replica.zendeskCreated

setCustomFieldValue(issue, “Zendesk_key”, replica.key)

setCustomFieldValue(issue, “Zendesk_Assignee”, replica.assignee?.email)

setCustomFieldValue(issue, “Zendesk_Reporter”, replica.reporter?.email)

setCustomFieldValue(issue, “Zendesk_CC_Emails”, replica.cc_emails?.join(", "))

setCustomFieldValue(issue, “Zendesk_follow”, replica.followers?.join(", "))

Seems to be that right now we don’t get over Zendesk_createddate, Zendesk_CC_Emails and Zendesk_follow.

Maybe there is still some issue what i did wrong in configuration?

with best
Urmo

Hi Urmo,

The issue is most likely on the Zendesk outgoing side.

Right now you’re trying to set JSM custom fields (issue.customFields) in the Zendesk outgoing script — that won’t work. The outgoing script should only populate the replica, not custom fields.

Please make sure you’re setting things like followers, CCs and created date on the replica (e.g. replica.zendeskFollowers, replica.zendeskCCs, replica.zendeskCreated) and then map those exact fields in the JSM incoming script.

Also double-check that the field names match exactly between outgoing and incoming — at the moment you’re referencing fields in incoming that were never added to the replica.

First step: check the Replica tab and confirm those fields are visible there. If they’re not in the replica, they can’t be mapped in JSM.

Let me know what you see there :+1:

Thanks,
Dhiren

Hi @urmo ,

Just following up on my previous comment

Were you able to try my suggestions?

Let me know if you face any issues.

Thanks, Dhiren

Hi

I must say that i’m out of ideas how to get followers, CC and zendesk ticket created time over to Jira.

In zendesk created should be behind created_at field - Tickets .

outgoing side

replica.created_at = issue.customFields."Zendesk_createddate"

incoming side

setCustomFieldValue(issue, "Zendesk_createddate", replica.created_at)

Still no time coming to Jira.

Same issue with followers and cc values.

outgoing side

replica.follower_names = issue.customFields."Zendesk_follow"

Incoming side

// Set custom field Zendesk_follow to comma-separated emails from followers, or empty string
if (replica.follower_names instanceof List) {
    def emails = replica.follower_names.collect { it?.email }.findAll { it }.join(",")
    setCustomFieldValue(issue, "Zendesk_follow", emails)
} else {
    setCustomFieldValue(issue, "Zendesk_follow", "")
}

outgoing side

replica.email_ccs = issue.customFields."Zendesk_cc"

Incoming side

setCustomFieldValue(issue, "Zendesk_cc", replica.cc_emails?.join(", "))

I’m not sure that fields are correct in zendesk and logic to import into jira also.

With best

Urmo

Hi @urmo ,

I figured out a way already to sync the actual created date of an issue from Zendesk to a custom field in Jira.

It requires httpClient() and can also be used to transfer email, cc, followers etc in the same way.

Here’s an example for you.

ZD Outgoing
def response = httpClient.get("/api/v2/tickets/${ticket.id}")
def createdDate = ("${response.ticket.created_at}")
replica."Zendesk Created Date" = createdDate

Jira Incoming
//please note that the custom field used is of type Date Picker
import java.text.SimpleDateFormat;
import java.text.DateFormat;

def dateCustomFieldValue(replicaCustomField) {
def datePattern = "yyyy-MM-dd"; // define the desired date/time format
String dateString = replicaCustomField
if (dateString) {
dateString = dateString.replaceAll("\"","").trim();
DateFormat formatter = new SimpleDateFormat(datePattern);
date = formatter.parse(dateString);
return new java.sql.Timestamp(date.time);
}
}
issue.customFields."Jira Date Custom".value = dateCustomFieldValue(replica."Zendesk Created Date".values[0])

Let me know if you face any issues in this.

Thanks, Dhiren

Hi

Tnx for helping and now i have time value but how should get cc and followers also to work because i tryd to debug with gemini and i must say it did not help me at all.

Urmo

Hi @urmo ,

In my previous example, I showed the usage of httpClient() to fetch information from the Zendesk Ticket which is directly not available on the ticket.

The same logic should work for cc and followers as well.

“follower_ids” ,“email_cc_ids” should be the fields that you should be looking at.

In the ZD outgoing please try something like this

def response = httpClient.get("/api/v2/tickets/${ticket.id}")
debug.error("${response}")

This way you can print the entire response, and then you need to simply add the fields to the replica which you would like to send.

You can refer to this doc as well for more info : Make REST API Calls with HTTP Client | Exalate Docs

Thanks, Dhiren