Hooks

Hooks help developers to introduce their own middleware into the execution flow of certain UI actions.
Like events, developers need to subscribe to these hooks to determine the execution of events by passing either boolean values or promises in the event handler.
If the boolean value TRUE is passed / if the extension resolves the promise, the event will be executed. If the boolean value FALSE is passed / if the extension rejects the promise, the event will be terminated and the reason for terminating the event will be displayed as an error message to end-users.
Extensions with subscriptions to hooks must respond within 30 seconds of the event being triggered. If not, control provided by the extension will be lost and the event will be executed. Multiple extensions can subscribe to a single hook. In such a scenario, the event will be executed only if all extensions subscribed to the event pass TRUE or resolve the promise.

Use Case: For instance, let us consider that you are creating an extension for a bug-tracking software. The extension records Zoho Desk tickets as issues in the bug-tracking software. Now, a support agent tries to close the ticket in zoho desk without resolving the issue in third-party tool, where it leads to a mismatch of ticket status.
To prevent such a scenario, you can configure extension to subscribe to the Zoho Desk hook defined for the close ticket event. With this, you can further configure your extension such that when the agent tries to close the ticket, the extension checks whether the corresponding issue has been resolved in the third-party tool. If the issue is resolved, the extension will close the ticket in Zoho Desk, else the ticket close event will not be executed and a relevant message will be displayed


Zoho Desk currently supports hooks for the following hooks

 

Reopening a ticket

The ticket.reOpen hook helps monitor the "reopen ticket" action. With this hook in place, if the custom logic returns the value true or if the promise is resolved by the extension, the ticket is reopened. If the custom logic returns false or if the promise is rejected by the extension, the ticket is not reopened.
This Hook can be invoked from the following locations.

  • desk.background
  • desk.bottomband
  • desk.extension.telephony
  • desk.ticket.detail.rightpanel
  • desk.ticket.detail.lefttab
  • desk.ticket.detail.moreaction
  • desk.ticket.thread.moreaction

Sample Request

CopiedApp.instance.on('ticket.reOpen', function(){ 
    return false: //Prevents ticket reopening
})

 

Closing a ticket

The ticket.close hook helps monitor the "close ticket" action. With this hook in place, if the custom logic returns the value true or if the promise is resolved by the extension, the ticket is closed. If the custom logic returns false or if the promise is rejected by the extension, the ticket is not closed.
This Hook can be invoked from the following locations.

  • desk.background
  • desk.bottomband
  • desk.extension.telephony
  • desk.ticket.detail.rightpanel
  • desk.ticket.detail.lefttab
  • desk.ticket.detail.moreaction
  • desk.ticket.thread.moreaction

Sample Request

CopiedApp.instance.on('ticket.close', function(){ 
    return false: //Prevents ticket closing
})

 

Changing the status of a ticket

The ticket.status hook helps monitor the "status change" action. With this hook in place, if the custom logic returns the value true or if the promise is resolved by the extension, the ticket status is changed. If the custom logic returns false or if the promise is rejected by the extension, the ticket status is not changed.
This hook can be invoked from the following locations.

  • desk.background
  • desk.bottomband
  • desk.extension.telephony
  • desk.ticket.detail.rightpanel
  • desk.ticket.detail.subtab
  • desk.ticket.detail.moreaction
  • desk.ticket.thread.moreaction

The response/data sent to the extension will be in the following format

FieldField TypeDescription
ticket.newStatusStringPrevious status of the ticket
ticket.oldStatusStringNew status of the ticket

Sample Request

CopiedApp.instance.on('ticket.status', function (data) {

    return new Promise((resolve, reject) => {
        if (data["ticket.newStatus"] == "Closed") {
            resolve(); // can only change status to close
        } else {
            reject({ msg: "Wrong ticket status" });
        }
    })

})

Sample Response

Copied{
    ticket.newStatus:"On Hold",
    ticket.oldStatus: "Closed"
     
}

 

Changing the assignee of a ticket

The ticket_assignee.change hook helps to decide whether the event 'ticket_assignee.changed' can happen or not. Using this hook, if the custom logic returns the value true or if the promise is resolved by the extension, you can change ticket to a new agent or team. If the custom logic returns false or if the promise is rejected by the extension, you will not be allowed to assign the ticket, and the corresponding notification will prompt simultaneously.
This hook can be invoked from the following locations.

  • desk.background
  • desk.bottomband
  • desk.extension.telephony
  • desk.ticket.detail.rightpanel
  • desk.ticket.detail.subtab
  • desk.ticket.detail.moreaction
  • desk.ticket.thread.moreaction

The response/data sent to the extension will be in the following format

FieldField TypeDescription
TypeStringType of ticket assignee. Values allowed are: Unassigned, agent, team, and teamAgent
agentObjectDetails of the agent. This key is returned only if the assignee is an individual agent or an agent from a team,Sent in format AGENT_OBJECT
teamObjectDetails of the team. This key is returned only if the assignee is a team or agent from a team,TEAM_OBJECT

AGENT_OBJECT

FieldField TypeDescription
idStringID of the agent
firstNameStringFirst name of the agent
lastNameStringLast name of the agent
fullNameStringFull name of the agent
emailStringEmail of the agent

TEAM_OBJECT

FieldField TypeDescription
teamIdStringID of the team
teamStringName of the team

Sample Request

CopiedApp.instance.on('ticket_assignee.change', function (data) {

    return new Promise((resolve, reject) => {
        if (data.type == "team") {
            resolve(); // can only assigned to team
        } else {
            reject({ msg: "Please assign to a team" });
        }
    })

})

Sample Response

Copied{
    type: "Agent",
    agent:
    {
        id: "90108000000190123",
        firstName: "NewAgent",
        lastName: "Test",
        fullName: "NewAgent",
        email: "newagent@something.com"
    }
}

 

Adding a ticket comment

The ticket.comment hook helps monitor the "comment on ticket" action. With this hook in place, if the custom logic returns the value true or if the promise is resolved by the extension, the comment is added to the ticket. Additionally, the ID of the ticket, content of the comment, and visibility of the comment are returned by the hook. If the custom logic returns false or if the promise is rejected by the extension, the comment is not added to the ticket.
This hook can be invoked from the following locations.

  • desk.background
  • desk.bottomband
  • desk.extension.telephony
  • desk.ticket.detail.rightpanel
  • desk.ticket.detail.lefttab
  • desk.ticket.detail.moreaction
  • desk.ticket.thread.moreaction

The response/data sent to the extension will be in the following format

FieldField TypeDescription
ticketIdStringID of the ticket
contentStringContent of the comment
isPublicBooleanKey that specifies if the comment is public or private

Sample Request

CopiedApp.instance.on('ticket.comment', function (data) {

    return new Promise((resolve, reject) => {
        if (data["isPublic"] == "false") {
            resolve();//Can only post private comments
        } else {
            reject({ msg: "Connot post public comment" });
        }
    })

})

Sample Response

Copied{
    ticketId: "26811000000760569", 
    content: "<div>Hello</div>",
    isPublic: false
}

 

Editing a ticket comment

The ticket.comment.edit hook helps monitor the "edit ticket comment" action. With this hook in place, if the custom logic returns the value true or if the promise is resolved by the extension, the comment becomes editable. Additionally, the ID of the ticket, content of the comment, and visibility of the comment are returned by the hook. If the custom logic returns false or if the promise is rejected by the extension, the comment does not become editable.
This hook can be invoked from the following locations.

  • desk.background
  • desk.bottomband
  • desk.extension.telephony
  • desk.ticket.detail.rightpanel
  • desk.ticket.detail.lefttab
  • desk.ticket.detail.moreaction
  • desk.ticket.thread.moreaction

The response/data sent to the extension will be in the following format

FieldField TypeDescription
ticketIdStringID of the ticket
newContentStringUpdated content of the comment
oldContentStringPrevious content of the comment
isPublicBooleanKey that specifies if the comment is public or private

Sample Request

CopiedApp.instance.on('ticket.comment', function (data) {

    return new Promise((resolve, reject) => {
        if (data["isPublic"] == "false") {
            resolve();//Can only post private comments
        } else {
            reject({ msg: "Connot post public comment" });
        }
    })

})

Sample Response

Copied{
    commentId: "90108000000849057",
    oldContent: "<div>Hello</div>",
    newContent: "<div>New Hello</div>",
    ticketId: "26811000000760569"
} 

 

Sending a ticket response

The ticket.reply hook helps monitor the "send ticket response" action. With this hook in place, if the custom logic returns the value true or if the promise is resolved by the extension, the response is sent. Additionally, the "from" address, "to" and "cc" addresses, ID and subject of the ticket, ID of the thread, and content of the response are returned by the hook. If the custom logic returns false or if the promise is rejected by the extension, the response is not sent.
This hook can be invoked from the following locations.

  • desk.background
  • desk.bottomband
  • desk.extension.telephony
  • desk.ticket.detail.rightpanel
  • desk.ticket.detail.lefttab
  • desk.ticket.detail.moreaction
  • desk.ticket.thread.moreaction

The response/data sent to the extension will be in the following format.

FieldField TypeDescription
ticketIdstringID of the ticket
fromAdddressStringEmail ID from which the response is sent
toAddressStringEmail ID(s) to which the response is sent, separated by commas
ccAddressstringEmail ID(s) CCed in the response, separated by commas
ticketSubjectStringSubject of the ticket and ticket number
threadIdStringID of the thread
contentStringContent of the response

Sample Request

CopiedApp.instance.on("ticket.reply", function(data){
    //data gives the ticket reply details
})

Sample Response

Copied{
    toAddress: "support@zohodesk.com",
    ticketId: "26811000000760569",
    ticketSubject: "[## 159 ##] sd",
    threadId: "67995000000222174",
    fromAdddress: ""mpdemo"",
    ccAddress: "support@mpdemo.zohodesk.com",
    content: "%3Cdivticket.replyetyle%3D'font-size%3A13.0px%3Bfont-family%3A%20Arial%20%2C%20Helvetica%20%2C%20Verdana%20%2C%sans-serif%3B'%3ETest%20Reply%3C%2Fdiv%3E"
}

 

Closing a ticket on sending ticket response

The ticket.replyAndClose hook helps monitor the "send response and close ticket" action. With this hook in place, if the custom logic returns the value true or if the promise is resolved by the extension, the response is sent and the ticket is closed. Additionally, the "from" address, "to" and "cc" addresses, ID and subject of the ticket, ID of the thread, and content of the response are returned by the hook. If the custom logic returns false or if the promise is rejected by the extension, the response is not sent and the ticket is not closed.
This hook can be invoked from the following locations.

  • desk.background
  • desk.bottomband
  • desk.extension.telephony
  • desk.ticket.detail.rightpanel
  • desk.ticket.detail.lefttab
  • desk.ticket.detail.moreaction
  • desk.ticket.thread.moreaction

The response/data sent to the extension will be in the following format

FieldField TypeDescription
ticketIdstringID of the ticket
fromAdddressStringEmail ID from which the response is sent
toAddressStringEmail ID(s) to which the response is sent, separated by commas
ccAddressstringEmail ID(s) CCed in the response, separated by commas
ticketSubjectStringSubject of the ticket and ticket number
threadIdStringID of the thread
contentStringContent of the response

Sample Request

CopiedApp.instance.on("ticket.replyAndClose", function(data){
    //data gives the ticket reply details
})

Sample Response

Copied{
    toAddress: "support@zohodesk.com",
    ticketId: "26811000000760569",
    ticketSubject: "[## 159 ##]  sd",
    threadId: "26811000000783105",
    fromAdddress: ""mpdemo"",
    ccAddress: "support@mpdemo.zohodesk.com",
    content: "%3Cdivticket.replyasndcloseetyle%3D'font-size%3A13.0px%3Bfont-family%3A%20Arial%20%2C%20Helvetica%20%2C%20Verdana%20%2C%sans-serif%3B'%3E%3Cdiv%3ETest%3C%2Fdiv%3E%3Cdiv%3E%3Cblockquote%style%3D%22border-left%3A%201px%dotted%20%23e5e5e5%3Bmargin-left%3A5px%3Bpadding-left%3A%205px%3B%22%3E%3Cdiv%style%3D%22padding-top%3A%2010px%3B%22%3E%3Cdiv%id%3D%22ZDeskInteg%22%3E%3Cmeta%itemprop%3D%22zdeskTicket%22%20content%3D%f10fd68b0cc315920245c7c0aa2b93fdf0a3db99826a12352285b65f8e5fa7a9458849c5b9779fef3c050436477df54fca922a12fa4922ab7fd919f18ae07%22%3E%3C%2Fdiv%3E%3C%2Fdiv%3E%3C%2Fblockquote%3E%3C%2Fdiv%3E%3C%2Fdiv%3E"
}

 

Updating agent status in a channel

The agent.channelStatus hook helps monitor the "change agent availability in channel" action. With this hook in place, if the custom logic returns the value true or if the promise is resolved by the extension, the availability status of the agent in the channel is updated. If the custom logic returns false or if the promise is rejected by the extension, the availability status of the agent is not updated.
This hook can be invoked from the following locations.

  • desk.background
  • desk.bottomband
  • desk.extension.telephony
  • desk.ticket.detail.rightpanel
  • desk.ticket.detail.subtab
  • desk.ticket.detail.lefttab
  • desk.ticket.detail.moreaction
  • desk.ticket.thread.moreaction
  • desk.contact.detail.rightpanel
  • desk.contact.detail.subtab
  • desk.contact.detail.lefttab
  • desk.contact.detail.moreaction
  • desk.account.detail.rightpanel
  • desk.account.detail.subtab
  • desk.account.detail.lefttab
  • desk.account.detail.moreaction

The response/data sent to the extension will be in the following format

FieldField TypeDescription
isOnlineBooleanKey that specifies if the agent is online or not
chatStatusBooleanKey that specifies if the agent is online in the Chat channel or not
mailStatusBooleanKey that specifies if the agent is online in the Mail channel or not
phoneStatusBooleanKey that specifies if the agent is online in the Phone channel or not

Sample Request

CopiedApp.instance.on("agent.channelStatus", function(data){
    //data gives the new owner details
})

Sample Response

Copied{
    chatStatus: "OFFLINE",
    mailStatus: "ONLINE",
    phoneStatus: "ONLINE",
    isOnline: true
}

 

Creating a new ticket

'ticket.add' hook helps to decide whether the ticket create operation should occur or not.

  1. If the custom logic that listens to the 'ticket.add' hook returns True or resolves the promise, then the ticket create operation will proceed. 
  2. If the custom logic returns False or rejects the promise, then the ticket create operation will be cancelled.

The response/data sent to the extension for the ticket.add hook will be as follows

FieldField TypeDescription
idStringID of the ticket
accountIdStringID of the account associated with ticket
contactIdStringID of the contact associated with ticket
assigneeIdStringID of the agent associated with ticket
teamIdStringID of the team associated with ticket
departmentIdStringID of the department
emailStringemail of the ticket
phoneStringPhone number in the ticket
subjectStringSubject of the ticket
descriptionBooleanDescription of the ticket
statusStringStatus of the ticket
secondaryContactsArrayArray of secondary contact Ids of the ticket
resolutionStringResolution of the ticket
dueDataStringDue Date of the ticket
priorityStringPriority of the ticket
channelStringChannel of the ticket
languageStringLanguage of the ticket
classificationStringClassification of the ticket
layoutIdStringLayoutId of the ticket
<customfield>Data Type of custom fieldValue of the custom fields in the ticket layout. Separate key value will be there for separate custom fields.

Sample Request

CopiedApp.instance.on("ticket.add", function(data){
    //data gives the ticket details
})

 

Editing a ticket

'ticket.update' hook helps to decide whether the ticket update operation should occur or not. When the ticket is updated, , the 'ticket.update' hook will be fired either from the ticket left tab or ticket edit form. 

  1. If the custom logic that listens to the 'ticket.update' hook returns True or resolves the promise, then the ticket update operation will proceed. 
  2. If the custom logic returns False or rejects the promise, then the ticket update operation will be cancelled.

The response/data sent to the extension for the ticket.update hook will be as follows

FieldField TypeDescription
idStringID of the ticket
accountIdStringID of the account associated with ticket
contactIdStringID of the contact associated with ticket
assigneeIdStringID of the agent associated with ticket
teamIdStringID of the team associated with ticket
departmentIdStringID of the department
emailStringemail of the ticket
phoneStringPhone number in the ticket
subjectStringSubject of the ticket
descriptionBooleanDescription of the ticket
statusStringStatus of the ticket
secondaryContactsArrayArray of secondary contact Ids of the ticket
resolutionStringResolution of the ticket
dueDataStringDue Date of the ticket
priorityStringPriority of the ticket
channelStringChannel of the ticket
languageStringLanguage of the ticket
classificationStringClassification of the ticket
layoutIdStringLayoutId of the ticket
<customfield>Data Type of custom fieldValue of the custom fields in the ticket layout. Separate key value will be there for separate custom fields.

Sample Request

CopiedApp.instance.on("ticket.update", function(data){
    //data gives the ticket details
})

Sample Response

Copied{
"id":"9049400045147",
"accountId":"9049400008165",
"contactId":"9049400010919",
"assigneeId":"9049400024290",
"teamId":null,
"departmentId":"9049400000067",
"email":"john@zylker.com",
"phone":"646 81234",
"subject":"Sample Ticket",
"description":null,
"status":"Open",
"secondaryContacts":[],
"resolution":null,
"dueDate":null,
"priority":null,
"channel":"Phone",
"language":null,
"classification":"Problem",
"layoutId":"9049400000911",
"cf_count":"",
}

 

Saving/updating/deleting a ticket reply draft

ticket_reply.draft hook helps you decide whether the reply draft can be saved, updated, or deleted.

  1. If the custom logic returns True or resolves the promise, then the reply draft action will proceed. 
  2. If the custom logic returns False or rejects the promise, then the user will not be allowed to perform the action on the reply draft and the appropriate message will be displayed.

The response/data sent to the extension for the 'save draft' and 'update draft' actions will be in the following format

FieldField TypeDescription
actionTypeStringDescribes the action. Value will be either "saveDraft" or "updateDraft"
ccAddressStringCC Address of the draft
toAddressStringto Address of the draft
contentStringContent of the draft
fromAddressStringfrom address of the draft
threadIdStringThreadId of the ticket
ticketIdStringId of the ticket
ticketSubjectStringSubject of the ticket

Sample Request

CopiedApp.instance.on("ticket_reply.draft", function(data){
    //data gives the ticket details
})

Sample Response

Copied{
"actionType":"updateDraft",
"ccAddress":"support@zylker.zohodesk.com",
"toAddress":"john@zylker.com",
"content":"%3Cdiv%20style%3D%22font-size%3A%2013px%3B%20font-family%3A%20Arial%2C%20Helvetica%2C%20sans-serif%3B%22%3E%3Cdiv%3EThis%20is%20a%20draft%3C%2Fdiv%3E%3C%2Fdiv%3E",
"fromAdddress":"support@zylker.zohodesk.com",
"threadId":"9049400045494",
"ticketId":"904940045147",
"ticketSubject":"[## 647 ##]  Support for call"
}

 

On Creating / Cloning a ticket

'ticket.createAfter' hook helps to perform operations after the ticket has been created from the ticket form page and before moving on to the ticket detail page.

  1. If the custom logic that listens to the 'ticket.createAfter hook returns True or resolves the promise, then we'll be moved to ticket detail page. 
  2. If the custom logic returns False or rejects the promise, then the ticket will be created, but the page will stay on the ticket form page.


Note:As of now the ticket.createAfter hook must be resolved after custom logic , otherwise the ticket will still be created and the UI will be stuck on ticket detail page.

The response/data sent to the extension for the ticket.createAfter hook will be as follows 

FieldField TypeDescription
idStringID of the ticket
accountIdStringID of the account associated with ticket
contactIdStringID of the contact associated with ticket
assigneeIdStringID of the agent associated with ticket
teamIdStringID of the team associated with ticket
departmentIdStringID of the department
emailStringemail of the ticket
phoneStringPhone number in the ticket
subjectStringSubject of the ticket
descriptionBooleanDescription of the ticket
statusStringStatus of the ticket
secondaryContactsArrayArray of secondary contact Ids of the ticket
resolutionStringResolution of the ticket
dueDataStringDue Date of the ticket
priorityStringPriority of the ticket
channelStringChannel of the ticket
languageStringLanguage of the ticket
classificationStringClassification of the ticket
layoutIdStringLayoutId of the ticket
WeburlStringWeb url of the ticket
<customfield>Data Type of custom fieldValue of the custom fields in the ticket layout. Separate key value will be there for separate custom fields.

Sample Request

CopiedApp.instance.on("ticket.createAfter", function(data){
    //data gives the ticket details
})

Sample Response

Copied{
"id":"9049400045147",
"accountId":"9049400008165",
"contactId":"9049400010919",
"assigneeId":"9049400024290",
"teamId":null,
"departmentId":"9049400000067",
"email":"john@zylker.com",
"phone":"646 81234",
"subject":"Sample Ticket",
"description":null,
"status":"Open",
"secondaryContacts":[],
"resolution":null,
"dueDate":null,
"priority":null,
"channel":"Phone",
"language":null,
"classification":"Problem",
"layoutId":"9049400000911",
"cf_count":"",
}

 

On merging tickets

The ticket.merge hook helps monitor the "ticket merge" action. With this hook in place, if the custom logic returns true or if the promise is resolved by the extension, the ticket merge action will be proceeded. Additionally the ids of the tickets and status of the tickets will be returned by the hook. If the custom logic returns false or if the promise is rejected by the extension, the ticket merge action will be declined.
This hook can be invoked from the following locations.

  • desk.topband
  • desk.bottomband
  • desk.extension.telephony
  • desk.background

The response/data sent to the extension will be in the following format.

FieldField TypeDescription
idStringId of the ticket.
statusStringStatus of the ticket.

 

Sample request

CopiedApp.instance.on("tickets.merge", function(data){
    //data gives the merged ticket details
})

Sample response

Copied[
   {
        id:"1936620001",
        status: "Open"
   }
,
    {
        id: "1342323111",
        status: "Closed"
   }
]

On closing a modal box

The modal.close hook helps monitor the "closing of modal box" action. With this hook in place, if the custom logic returns true or if the promise is resolved by the extension, the 'closing of modal box' action will proceed triggering the associated function. If the custom logic returns false or if the promise is rejected by the extension, the action will be declined.

Sample Request

CopiedApp.instance.on("modal.close", function(data){
  //data will be empty
})