Zoho Desk Platform
Introduction

ZOHO MARKETPLACE - AN INTRODUCTION

At Zoho Desk, we believe in delivering good user experiences and making already good experiences better. In that vein, stepping into Zoho Marketplace is an effort towards achieving the latter goal.

Zoho Marketplace is an online store where users can find extensions that deliver business value by enhancing the core functionality of the Zoho product they use. It is similar to Google's Play Store, where you find apps for installation and use, according to your need. Until now, one could create extensions only for Zoho CRM, Creator, Connect, Cliq, Recruit, SalesIQ, Mail, and Reports. Going forward, they can create extensions for Zoho Desk too, and make a positive impact in the customer service space.

As a developer, you can create extensions that combine Zoho Desk's functionalities with those of third-party tools to make work easier and more effective for the end-users of Zoho Desk. You do not require extensive expertise or experience in programming to create extensions. Functional knowledge of HTML, CSS, and JavaScript can help you to a great extent.

After you create and submit your extension for review, we test its functionalities and provide you with feedback for improving its effectiveness, if required. When the extension is ready for use, we host it on Marketplace for end-users to discover and use.

To keep yourself updated about changes to Zoho Desk Extension development capabilities, such as APIs and SDKs, follow the Zoho Desk Extension Developers forum.

Quick Start

Installing the Node

$ node -v v10.x or Above

As the first step to creating an extension, you must install the node.js runtime environment. You can download node.js from here (versions above 10.X are supported). After installing node.js, you can verify its version using the following command:

Installing the ZET CLI

Next, you must install the ZET (Zoho Extension Toolkit) Command Line Interface (CLI) tool, which enables you to build, test, and package extensions for Zoho products.

sudo npm install -g zoho-extension-toolkit

If you use a Mac/Unix system for development, run the following command to install ZET:

npm install -g zoho-extension-toolkit

If you use a Microsoft Windows system, run the following command:

The -g command option ensures that the installation is global. With a global installation, you can call commands and work on your extensions and CLI from anywhere within your machine.

zet -v 0.21.0 or Above

After ZET is installed, help information regarding the zet command would appear. You can then verify the version of the tool, using the following command:

D: \zet\projects\demoproject>zet -help Usage: index [options] [command] Options: -v, —version Show the version number -h, —help output usage information Commands: Init, Creates a new project template directory run Starts a local server with current directory as context Validate Validates the current app with validation rules Pack Packs the project to upload into marketplace

Running ZET will display all the commands supported, as shown in the right panel.














init

zet init

This command creates a new project for the extension.

D:\zet\projects>zet init ? Select the Zoho service for your widget and hit enter key (Use arrow keys) > Zoho Desk Zoho CRM ZES

Executing this command displays the list of Zoho Services available. Choose Zoho Desk and press the Enter key.




D:\zet\projects>zet init ? Select the Zoho service for your widget and hit enter key Zoho Desk ? Project Name demoproject

After choosing Zoho Desk, provide a name for the new project.



D:\zet\projects>D:\zet\projects>zet init ? Select the Zoho service for your widget and hit enter key Zoho Desk ? Project Name demoproject Initializing project at: D:\zet\projects\demoprojects Installing NPM dependencies… Project Initialized D:\zet\projects\demoprojects Run the following commands: Cd demoproject Zet run D:\zet\projects>

After you enter the name, a project template directory with all the necessary folders, dependency node packages, and files is created.









The image below shows the default folder structure of an extension project.

run

This command runs the http server hosting the extension.

D:\zet\projects>D:\zet\projects>zet run

1. To start the server and test the extension, run the following command:

This command makes the http server accessible through the 5000 port of your local machine. Make sure that the 5000 port is not occupied, before you start the server.

2. To verify if the server started successfully, open the following URL in your browser: http://localhost:5000/plugin-manifest.json

validate

D:\zet\projects>zet validate

This command validates the extension by checking if it follows the guidelines defined in this section. To perform this validation, run the following command:

After you execute this command, the result of the validation appears. Check the result and make any changes, if required. After this step, you can proceed to package the extension files for upload.

Guidelines

During validation, the details in the plugin-manifest.json file are checked to verify if the following conditions are met:

pack

zet pack

The project directory contains the source code and other node modules necessary for locally testing the extension. However, the final zip file you upload to Marketplace must contain only the files and folders essential for running the extension. ZET makes this packaging process easy through the following command:

After you execute this command, ZET creates a zip file containing all files in the app folder and the plugin-manifest.json file of your extension. This zip file is stored in the dist folder of the project.

Building Your First Extension

Let’s get started by creating an extension. First, open the terminal/command prompt window, and navigate to the directory under which you want to create your extension.

Then, perform the following steps:

  1. Run the init command.
  2. Under the list of services, choose Zoho Desk.
  3. Enter a name for the extension project.
  4. Press the Enter key.

The extension project is created with the necessary directories and files.

Testing the Extension

To test your extension, perform the following steps:

  1. Open the terminal/command prompt and navigate to your project folder.
  2. Execute the zet run command.
  3. Log into your Zoho Desk account. If you do not have a Zoho Desk account, sign up here.
  4. While on the Zoho Desk page, open browser developer tools, and from the console, invoke the runDevMode() JavaScript code. This action refreshes the page and activates the development mode.
  5. Open a ticket and click the Marketplace icon. The extension appears.



The runDevMode() command loads Zoho Desk in developer mode. To revert to normal mode, execute the runProdMode() command in developer tools.

Validating the Extension

To validate your extension and verify if it follows the guidelines mentioned earlier, perform the following steps:

  1. Open the terminal/command prompt and navigate to your project folder.
  2. Execute the zet validate command.
    The results of the validation process appear. Make changes, if required.

Packaging the Extension

To package your extension as a zip file that contains only the relevant files, perform the following steps:

  1. Open the terminal/command prompt and navigate to your project folder.
  2. Execute the zet pack command.
    The zip file of your extension is created in the dist folder of your project.

Uploading the Extension

Uploading the Extension to the Marketplace

After packaging your extension, perform the following steps to upload the zip file of your extension for review:

  1. Go to the Sigma website.
  2. Click the New Extension button.



    The upload process flow begins.

Based on the visibility option you choose, your extension can be of two types:

Private:

If you want to use the extension only within your organization or help desk portal, choose Private in the upload form. After you complete the upload process, you will be provided with an installation URL, using which you can install the extension in your portal.

Public:

If you want to let other end-users of Zoho Desk find your extension in Marketplace and install it in their portal, choose Public in the upload form. The form through which you submit more details of your extension appears. Follow the wizard through and upload your extension.

  1. In the General information form, provide the following details:
    • Release Audience: Visibility option for the extension: Public or Private
    • Developer Name/Org: Name of the organization or individual developer who created the extension
    • Contact Number: Phone number to contact the extension developer
    • Developer Website: Website of the extension developer
    • Help URL: Link to the extension's help documentation

    After entering these details, click the Next Step button. The App information form appears.

  2. In the App information form, enter the following details:
    • App Title: Name of the extension
    • Tagline: Short, catchy phrase that describes the function of the extension
    • Category: Business function/operation related to the extension
    • App Description: Brief description of the extension
    • App Summary: Development summary of the extension and other important notes
    • Logo: Logo of the extension/product/developer
    • Banner: Banner image that should appear on the extension detail page
    • Screenshots: Screenshots of the extension

    After entering these details, click the Next Step button. The Uploads form appears.

  3. In the Uploads form, configure the following settings:
    • Zoho Service: Zoho product related to the extension
    • Upload Zip File: Final, packaged zip file of the extension

    After performing these steps, click the Next Step button. The Validation page appears.

  4. On the Validation page, verify the details you entered, and perform one of these two steps::
    • If all the information is accurate, click the Save button. The extension will be submitted for review.
    • If any inaccurate information is present, click the Back button and perform the required corrections in the relevant form. After making the necessary correction(s), click the Save button in the Validation page.

    After you upload your extension on the Sigma site, the Zoho Desk team reviews it for functionality and usability, and provides you with feedback if any enhancements are required. The extension is made available on Marketplace after it passes the review and functions as intended.

Updating the Extension

Updating the Extension to the Marketplace

IIf you come up with new features, enhancements to existing features, or bug fixes, you can update your extension to improve its functionality and performance. Each update increments the version of the extension.

To upload the updated version of your extension, perform the following steps:

  1. Go to the Sigma website and click your extension.
  2. On the upper-right corner of the extension detail page, click the Edit button. The extension information form with previously filled data appears. Edit the details, if required, and click Next.
  3. In the final form, upload the zip file of the updated extension and submit it for review.

The review process of the updated extension begins.

After the review is completed, one of these two events happens:

Installing the Extension

Installing the Extension in Zoho Desk

End-users of Zoho Desk can view and install the public extensions you create, from two locations:

If the Zoho Desk administrator installs an extension from the Setup page, the extension is made available to users in the current portal. On the other hand, if the administrator tries to install the extension from the Marketplace site, a list of all the available portals appears, letting the admin choose the one in which the extension must be installed.

Configuration

Below are the different configurations you can set for your extension.

plugin-manifest

The configuration details of each extension are stored in the plugin-manifest.json file in the extension project directory. The various keys included in this file are as follows:



































{
 “location” - Location of the widget. All locations are predefined and explained in this document, “url” - starting point of the widget. Can be either a relative path (for internally hosted extensions) or an absolute path (for externally hosted extensions), “name” - Name of the extension (displayed in the extension header), “logo” - Logo of the extension (displayed in the extension header), “icon” - Icon of the extension (displayed only on widgets on the chat bar and subtab) }



















{ “name” : Name of the parameter which will be used to replace the values and displayed as a label “userdefined” : true (input value to be given by user), “type” : Input type (text field/drop-down) list/radio button); only text field is supported currently, “value” : Initial value of the parameter, “mandatory” : This property performs validation check when the user misses to enter values during installation, “secure” : true - values will be available only on the server side of the product; the client side will not have access to the values. false - values will be available on the client side0. }

{ “name” : Name of the parameter which will be used to replace the values “value” : Value of the parameter }

Configuring connectors

To configure a connector for a third-party service, perform the following steps:

  1. Visit the All Connections page on the Sigma site.
  2. Click Connections from side menu.
    The list of third-party services currently supported appears.
  3. Click the service you want.
    The Connection Details form appears.
  4. In this form, enter a name for the connection of your choice and choose the access scopes for the connection.
  5. Then, click the Create and Connect button.
    The Connection Summary page appears.
  6. On this page, click the JSON tab.
  7. Copy the code snippet under the JSON tab.
  8. Go to the plugin-manifest.json file of your extension and paste the code snippet under the connectors key.

Your extension is now connected to the third-party service.

To configure your extension to fetch data from the third-party service, include the connectionLinkName key in the request API.

Locations

This key, which must be configured in the plugin-manifest file, defines the location(s) for your extension's widgets. Keep in mind that a single extension can be rendered in multiple locations.

Product Level

Top Band - desk.topband

This location refers to the top band from which users navigate between Zoho Desk modules. The extensions you installed in this location will be shown up under the more icon. When user picks an extension from the more icon, it will utilise the entire screen.


Image



Ticket Detail page

Right Panel - desk.ticket.detail.rightpanel

This location refers to a collapsible panel on the right side of the ticket detail page. The extension widget loads on this panel when the user clicks the Marketplace icon.




Sub-Tab - desk.ticket.detail.subtab

This location refers to the sub-tab (More icon) in the ticket detail page. Clicking the icon lists the extensions installed in this location. When a user clicks an extension from this list, the extension loads in the entire sub-tab section.






Left Tab - desk.ticket.detail.lefttab

This location refers to the black, vertical strip on the left side of the ticket detail page. The Marketplace logo appears here. Clicking the logo lists the extensions installed in this location.






More Actions - desk.ticket.detail.moreaction

This location refers to the more actions icon at the top-right side of the ticket detail page. Clicking this icon displays a menu in which the extensions are listed below the other standard ticket-related actions, such as Edit, Share, Follow, Delete, and so on.






Thread More Actions - desk.ticket.thread.moreaction

This location refers to the more actions icon at the top-right side of an individual thread. Clicking this icon displays a menu in which the extensions are listed below the other standard thread-related actions, such as Reply, Forward, Print, and so on.






Ticket Form Page

desk.ticket.form.rightpanel

This location refers to the Marketplace icon at the top-right side of the Add Ticket form. Clicking this icon displays a collapsible panel that lists all the extensions configured in this location. 






Features

Data APIs

Zoho Desk provides a set of APIs that facilitate interaction between your extension and your help desk portal. The APIs available are listed below:

Ticket Object

This object provides you with access to the various properties of a ticket. It is accessible only from the desk.ticket.detail.rightpanel, desk.ticket.detail.subtab and desk.ticket.detail.lefttab widget locations.

  1. departmentId
  2. email
  3. subject
  4. description
  5. status
  6. dueDate
  7. threadCount
  8. isSpam
  9. createdTime
  10. modifiedTime
  11. owner
  12. id
  13. comments
  14. attachments
  15. accountName
  16. phone
  17. productName
  18. commentCount
  19. priority
  20. channel
  21. classification
  22. category
  23. subCategory
  24. contactName
  25. customFields
  26. thread
  27. threads
  28. conversation
  29. contactId
ZOHODESK.get("ticket.").then(function(response){ //response returns the property data of the ticket }) where <property> must be replaced with the particular property or data you want to fetch

To access any property of a ticket, use the following code snippet.




Methods
The ticket object supports the following two methods:

Get ticket: Fetch a ticket along with all associated properties from the help desk portal.

ZOHODESK.get(“ticket”).then(function(response) { //response returns the ticket with all its properties })

Request:


{ "ticket": { "departmentId": "12345678901234567", "email": "youremail@domainname.com", "subject": "Faulty phone charging port", "description": "Product replacement", "status": "Open", "dueDate":"21/02/2018 12:00 PM", "threadCount": "1", "isSpam": false, "createdTime": "29/01/2018 11:55 PM", "modifiedTime": "31/01/2018 11:27 AM", "owner": "Agent Name", "accountName": "Account Name", "phone": "1234567890", "commentCount": "1", "priority": "High", "channel": "Phone", "classification": "Problem", "category": "General", "subCategory": "Sub General", "contactName": "Contact Name" }, "status": "success" }

Response:
























You can fetch specific properties of a ticket using the get method.

Get department ID: Fetch the ID of the department associated with the ticket.

ZOHODESK.get("ticket.departmentId").then(function(response){ //response returns the ID of the department associated with the ticket }) .catch(function(err){ // Error handling })

Request:



{ "ticket.departmentId": "12345678901234567", "status": "success" }

Response:




Get ticket email: Fetch the email ID associated with the ticket (Ticket creator email ID/contact email ID).

ZOHODESK.get("ticket.email").then(function(response){ //response returns the email ID associated with the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.email": "emailID@domainname.com", "status": "success" }

Response:




Get ticket subject: Fetch the subject of the ticket. 

ZOHODESK.get("ticket.subject").then(function(response){ //response returns the subject of the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.subject": "Faulty phone charging port", "status": "success" }

Response:




Get ticket description: Fetch the description of the ticket.  

ZOHODESK.get("ticket.description").then(function(response){ //response returns the description of the ticket }) .catch(function(err){ // Error handling })

Request:



{ "ticket.description": "The charging port of the phone has stopped working. Customer requires a replacement.", "status": "success" }

Response:





Get ticket status: Fetch the status of the ticket.  

ZOHODESK.get("ticket.status").then(function(response){ //response returns the status of the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.status": "Open", "status": "success" }

Response:





Get ticket dueDate: Fetch the due date of the ticket.  

ZOHODESK.get("ticket.dueDate").then(function(response){ //response returns the dueDate of the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.dueDate": "21/02/2018 12:00 PM", "status": "success" }

Response:





Get ticket thread count: Fetch the thread count of the ticket.  

ZOHODESK.get("ticket.threadCount").then(function(response){ //response returns the thread count of the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.threadCount": "1", "status": "success" }

Response:





Get ticket spam status: Returns whether a ticket is spam or not.  

ZOHODESK.get("ticket.isSpam").then(function(response){ //response returns a Boolean value that states whether the ticket is spam or not }).catch(function(err){ // Error handling })

Request:



{ "ticket.isSpam": "false", "status": "success" }

Response:





Get time of ticket creation: Fetch the time the ticket was created.   

ZOHODESK.get("ticket.createdTime").then(function(response){ //response returns the time the ticket was created }).catch(function(err){ // Error handling })

Request:



{ "ticket.createdTime": "29/01/2018 11:55 PM", "status": "success" }

Response:





Get time of ticket modification: Fetch the time the ticket was modified.   

ZOHODESK.get("ticket.modifiedTime").then(function(response){ //response returns the time the ticket was modified }).catch(function(err){ // Error handling })

Request:



{ "ticket.modifiedTime": "31/01/2018 11:27 AM", "status": "success" }

Response:





Get ticket owner: Fetch the agent to whom the ticket is assigned.    

ZOHODESK.get("ticket.owner").then(function(response){ //response returns the name of the agent assigned to resolve the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.owner": "Agent Name", "status": "success" }

Response:





Get ticket ID: Fetch the ID of the ticket.    

ZOHODESK.get("ticket.id").then(function(response){ //response returns the ID of the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.id": "12345678901234567", "status": "success" }

Response:





Get ticket comments: Fetch the comments recorded on the ticket.    

ZOHODESK.get("ticket.comments").then(function(response){ //response returns comments recorded on the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.comments": { "data": [ { "modifiedTime": null, "encodedContent": "Has anyone else faced this problem before?", "commentedTime": "2018-01-08T17:25:46.000Z", "isPublic": false, "id": "2000000011023", "content": "Has anyone else faced this problem before?", "commenterId": "2000000009148" }, { "modifiedTime": null, "encodedContent": "I would like some advice ASAP.", "commentedTime": "2018-01-08T17:25:57.000Z", "isPublic": true, "id": "2000000011027", "content": "I would like some advice ASAP.", "commenterId": "1234567890123" } ] }, "status": "success" }

Response:























Set ticket comment: Asynchronously add a comment to the ticket in the product UI.   

ZOHODESK.set("ticket.comment",{"comment":"test comment test"}).then(function(response){ }).catch(function(err){ // Error handling })

Request:



Get ticket attachments: Fetch file attachments from the ticket.    

ZOHODESK.get("ticket.attachments").then(function(response){ //response returns the attachments in the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.attachments": { "data": [ { "size": "366365", "name": "pro.jpg", "creatorId": "1234567890123", "isPublic": false, "createdTime": "2018-01-08T17:29:52.000Z", "id": "1234567890123" }      ] }, "status": "success" }

Response:














Get account name from ticket: Fetch the account associated with the ticket.     

ZOHODESK.get("ticket.accountName").then(function(response){ //response returns the name of the account associated with the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.accountName": "Account Name", "status": "success" }

Response:





Get phone number from ticket: Fetch the phone number associated with the ticket.     

ZOHODESK.get("ticket.phone").then(function(response){ //response returns the phone number associated with the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.phone": "1234567890", "status": "success" }

Response:





Set phone number in ticket: Asynchronously set the phone number to the ticket in the product UI.      

ZOHODESK.set("ticket.phone",{"phone":"1234567890"}).then(function(response){ }).catch(function(err){ // Error handling })

Request:





Get product name from ticket: Fetch the product name from the ticket.      

ZOHODESK.get("ticket.productName").then(function(response){ //response returns the name of the product associated with the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.productName": "Product Name", "status": "success" }

Response:





Get comment count from ticket: Fetch the number of comments recorded on the ticket.      

ZOHODESK.get("ticket.commentCount").then(function(response){ //response returns the number of comments recorded on the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.commentCount": "2", "status": "success" }

Response:





Get ticket priority: Fetch the priority of the ticket.       

ZOHODESK.get("ticket.priority").then(function(response){ //response returns the priority status of the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.priority": "High", "status": "success" }

Response:





Set ticket priority: Asynchronously set the priority of the ticket in the product UI.        

ZOHODESK.set("ticket.priority",{"priority":"High"}).then(function(response){ }).catch(function(err){ // Error handling })

Request:




Get ticket channel: Fetch information on the channel through which the ticket originated.         

ZOHODESK.get("ticket.channel").then(function(response){ //response returns the channel through which the ticket originated }).catch(function(err){ // Error handling })

Request:



{ "ticket.channel": "Phone", "status": "success" }

Response:





Get ticket classification: Fetch ticket type.        

ZOHODESK.get("ticket.classification").then(function(response){ //response returns the classification of the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.classification": "Question", "status": "success" }

Response:





Set ticket classification: Asynchronously set the type for the ticket: Question, Problem, Features, and Others, on the product UI.        

ZOHODESK.set("ticket.classification",{"classification":"Problem"}).then(function(response){     }).catch(function(err){ // Error handling })

Request:





Get ticket category: Fetch the category of the ticket.         

ZOHODESK.get("ticket.category").then(function(response){ //response returns the category of the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.category": "Defects", "status": "success" }

Response:





Get ticket subcategory: Fetch the sub-category under which the ticket was created.        

ZOHODESK.get("ticket.subCategory").then(function(response){ //response returns the sub-category of the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.subCategory": "Sub Defects", "status": "success" }

Response:





Get contact name in ticket: Fetch the name of the contact who sent the ticket.         

ZOHODESK.get("ticket.contactName").then(function(response){ //response returns the contact name in the ticket }).catch(function(err){ // Error handling })

Request:



{ "ticket.contactName": "Contact Name", "status": "success" }

Response:





Get customFields: Fetch the custom fields in the ticket and their corresponding values.         

ZOHODESK.get("ticket.customFields").then(function(response){ //response returns the custom fields in the ticket along with their values         }).catch(function(err){  //Error handling          })

Request:



{ "ticket.customFields": { "SecondaryContact":"1234567890" }, "status": "success" }

Response:







Get thread: Fetch a single thread from a ticket.       

ZOHODESK.get('ticket.thread', {threadId : "12345678901234567"}).then(function(res){ //response returns the thread specified }).catch(function(err){ })

Request:



{ "ticket.thread": { "channel": "EMAIL", "status": "SUCCESS", "summary": "Testing", "attachments": [ ], "author": { "name": "Firstname Lastname", "email": "emailID@domainname.com", "type": "AGENT", "photoURL": "https://desk1.portalname.com/api/v1/agent/12345678901234567/photo" }, "visibility": "public", "direction": "out", "createdTime": "2018-03-22T09:55:50.962Z", "actions": [ ], "id": "12345678901234567", "hasAttach": false, "content": "<meta /><div><div style=\"font-size: 13px; font-family: Arial, Helvetica, Verdana, sans-serif\"><div><blockquote style=\"border-left: 1px dotted rgb(229, 229, 229); margin-left: 5px; padding-left: 5px\"><div style=\"padding-top: 10px\"><div>Testing</div></div></blockquote></div></div><div><meta itemprop=\"zdeskTicket\" content=\"263f10fd68b0cc315920245c7c0aa2b93fdf0a3db99826a12352285b65f8e5fa097eaa794b5aa33edccdd6abe429590343e7d3e340e4287da6a62c59c437fb11\" /></div><br /><div>", "cc": "support@portalname.domainname.com", "responderId": "12345678901234567", "bcc": "", "to": "emailID@domainname.com", "fromEmailAddress": "\"portalname\"", "isForward": false }, "status": "success" }

Response:































Get threads: List a specific number of threads from a ticket, based on the limit defined.        

ZOHODESK.get('ticket.threads', {from : 0, limit : 10}).then(function(res){ //response returns the list of threads }).catch(function(err){ }) 

Request:

Note: from refers to the index number starting from which the resources must be fetched. limit refers to the number of resources to fetch, and its value cannot exceed 99. Either of the two parameters must be included in the API request.

{"ticket.threads" : { "data": [ { "channel": "EMAIL", "status": "DRAFT", "summary": "Some PATCHED draft thread", "author": { "name": "Author Name", "email": "username@domainname.com", "type": "AGENT", "photoURL": "https://desk.portalname.com/api/v1/agent/12345678901234567/photo" }, "visibility": "public", "direction": "out", "createdTime": "2018-02-05T06:18:58.522Z", "actions": [ { "method": "DELETE", "rel": "delete", "href": "https://desk.portalname.com/api/v1/tickets/12345678901234567/draftReply/12345678901234567" }, { "method": "POST", "rel": "send", "href": "https://desk.portalname.com/api/v1/tickets/12345678901234567/sendDraft?draftThreadId=12345678901234567" } ], "id": "12345678901234567", "hasAttach": false, "cc": "", "responderId": "12345678901234567", "bcc": "", "to": "example2@example.com", "fromEmailAddress": "\"portalname\"", "isForward": false }, { "channel": "EMAIL", "status": "SUCCESS", "summary": "Some PATCHED draft thread", "attachments": [], "author": { "name": "Author Name", "email": "username@domainname.com", "type": "AGENT", "photoURL": "https://desk.portalname.com/api/v1/agent/12345678901234567/photo" }, "visibility": "public", "direction": "out", "createdTime": "2018-03-23T11:08:10.433Z", "actions": [], "id": "12345678901234567", "hasAttach": false, "content": "<meta /><div>Some PATCHED draft thread<div id=\"ZDeskInteg\"><meta itemprop=\"zdeskTicket\" content=\"5daa425305ccbc60f58f122d41df729d0ced47a219d557d5da94cd732b9ba63853aa129b5170d1c4c7ac344c755fac0a7f36480e51912dd365d982aa8b5a18f1\" /></div><br /></div>", "cc": "", "responderId": "12345678901234567", "bcc": "", "to": "example2@example.com", "fromEmailAddress": "\"portalname\"", "isForward": false }, { "channel": "FEEDBACK", "status": "SUCCESS", "summary": "rated response from . Wow! You just made our day!\" That's feedback content \"", "author": { "name": "Author Name", "email": "\"Author Name\"", "type": "END_USER", "photoURL": "https://domainname.test.co.in/api/v1/portalUser/12345678901234567/photo" }, "visibility": "public", "direction": "in", "createdTime": "2018-02-07T07:07:45.133Z", "actions": [], "id": "12345678901234567", "hasAttach": false }, { "channel": "WEB", "status": "SUCCESS", "summary": "", "author": { "name": "Author Name", "email": "username@domainname.com", "type": "END_USER", "photoURL": "https://domainname.test.co.in/api/v1/portalUser/12345678901234567/photo" }, "visibility": "public", "direction": "in", "createdTime": "2017-11-21T20:45:13.383Z", "actions": [], "id": "12345678901234567", "hasAttach": false }, { "channel": "CUSTOMERPORTAL", "status": "SUCCESS", "summary": "reply for a tickk", "author": { "name": "Author Name", "email": "username@domainname.com", "type": "END_USER", "photoURL": "https://domainname.test.co.in/api/v1/portalUser/12345678901234567/photo" }, "visibility": "public", "direction": "in", "createdTime": "2018-01-18T14:21:12.305Z", "actions": [], "id": "12345678901234567", "hasAttach": false, "fromEmailAddress": "username@domainname.com" }, { "channel": "FORUMS", "status": "SUCCESS", "summary": "xxx", "author": { "name": "Author Name", "email": "username@domainname.com", "type": "END_USER", "photoURL": "https://desk.portalname.com/api/v1/portalUser/12345678901234567/photo" }, "visibility": "public", "direction": "in", "createdTime": "2017-04-12T11:51:36.352Z", "actions": [], "id": "12345678901234567", "hasAttach": false, "fromEmailAddress": "username@domainname.com" }, { "channel": "FACEBOOK", "status": "SUCCESS", "summary": "Testing", "author": { "name": "Author Name", "email": "username@domainname.com", "type": "AGENT", "photoURL": "https://domainname.test.co.in/api/v1/agent/12345678901234567/photo" }, "visibility": "public", "direction": "out", "createdTime": "2017-11-27T06:19:12.383Z", "actions": [], "id": "12345678901234567", "hasAttach": false, "responderId": "12345678901234567", "facebookProfile": "FB Profile" }, { "channel": "FACEBOOK", "status": "SUCCESS", "summary": "Hi! Please share my service schedule.", "author": { "name": "Author Name", "email": "", "type": "END_USER", "photoURL": null }, "visibility": "public", "direction": "in", "createdTime": "2017-11-27T06:19:11.973Z", "actions": [], "id": "12345678901234567", "hasAttach": false, "facebookProfile": "FB Profile Name" }, { "channel": "FACEBOOK", "status": "DRAFT", "summary": "My Facebok Draft Thread", "author": { "name": "Author Name", "email": "username@domainname.com", "type": "AGENT", "photoURL": "https://domainname.test.co.in/api/v1/agent/12345678901234567/photo" }, "visibility": "public", "direction": "out", "createdTime": "2017-12-01T09:53:37.557Z", "actions": [ { "method": "DELETE", "rel": "delete", "href": "https://domainname.test.co.in/api/v1/tickets/12345678901234567/draftReply/12345678901234567" }, { "method": "POST", "rel": "send", "href": "https://domainname.test.co.in/api/v1/tickets/12345678901234567/sendDraft?draftThreadId=12345678901234567" } ], "id": "12345678901234567", "hasAttach": false, "responderId": "12345678901234567", "facebookProfile": "FB Profile Name" }, { "channel": "TWITTER", "status": "SUCCESS", "summary": "My refrigerator stopped working", "author": { "name": "Author Name", "email": "username@domainname.com", "type": "AGENT", "photoURL": "https://rebrand-support.test.co.in/api/v1/agent/12345678901234567/photo" }, "visibility": "public", "direction": "out", "createdTime": "2017-12-01T08:14:47.758Z", "actions": [], "id": "12345678901234567", "hasAttach": false, "responderId": "12345678901234567", "twitterProfile": "Twitter Handle" }, { "channel": "TWITTER", "status": "SUCCESS", "summary": "I'm facing the same issue.", "author": { "name": "Author Name", "email": "", "type": "END_USER", "photoURL": null }, "visibility": "public", "direction": "in", "createdTime": "2017-11-27T06:18:25.547Z", "actions": [], "id": "12345678901234567", "hasAttach": false, "twitterProfile": "Twitter Handle" }, { "channel": "ONLINE_CHAT", "status": "SUCCESS", "summary": "", "author": { "name": "Author Name", "email": "Author", "type": "END_USER", "photoURL": null }, "visibility": "public", "createdTime": "2017-10-13T07:34:13.333Z", "direction": "in", "actions": [], "id": "12345678901234567", "hasAttach": false, "fromEmailAddress": "Author" }, { "channel": "OFFLINE_CHAT", "status": "SUCCESS", "summary": "", "author": { "name": "Author Name", "email": "Author", "type": "END_USER", "photoURL": null }, "visibility": "public", "createdTime": "2017-10-13T07:34:13.333Z", "direction": "in", "actions": [], "id": "12345678901234567", "hasAttach": false, "fromEmailAddress": "Author" }, { "channel": "ONLINE_CHAT", "status": "SUCCESS", "summary": "", "attachments": [], "author": { "name": "Author Name", "email": "Author", "type": "END_USER", "photoURL": null }, "visibility": "public", "createdTime": "2017-10-13T07:34:13.333Z", "direction": "in", "actions": [], "id": "98765432109876543", "hasAttach": false, "content": "<table width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"border-top: 1px solid rgb(245, 245, 245)\"><tbody><tr><td style=\"background-color: rgb(251, 251, 251); width: 175px; max-width: 250px !important; min-width: 175px !important; overflow: hidden\"><div style=\"font-size: 12px; font-family: Arial, Helvetica, sans-serif, Tahoma; color: rgb(51, 51, 51); padding-right: 10px; text-align: right\"><i><b>Question</b></i></div></td><td style=\"border-bottom: 1px solid rgb(251, 251, 251); padding: 8px 10px 6px 12px; font-size: 12px; color: rgb(153, 153, 153); text-decoration: none\"><i>Please rate our service.</i></td></tr><tr><td style=\"background-color: rgb(251, 251, 251); width: 175px; max-width: 250px !important; min-width: 175px !important; overflow: hidden; width: 140px\"><div style=\"color: rgb(176, 176, 176); text-align: right; padding-right: 10px; font-weight: bold; font-size: 12px; font-family: Arial, Helvetica, sans-serif, Tahoma\"><div class=\"sender\"><b>Author Name</b></div></div></td><td style=\"border-bottom: 1px solid rgb(251, 251, 251); padding: 8px 10px 6px 12px\"><span style=\"color: rgb(153, 153, 153); float: right; font-family: arial; font-size: 11px; font-style: normal; max-width: 65px; min-width: 50px; visibility: visible; white-space: nowrap\">13 Oct, 1:02 PM </span><span style=\"padding-left: 2px; font: 12px Arial; color: rgb(0, 0, 0); text-decoration: none; float: left\">yes>/span><span style=\"clear: both\"></span></td></tr><tr><td style=\"background-color: rgb(251, 251, 251); width: 175px; max-width: 250px !important; min-width: 175px !important; overflow: hidden\"> </td><td style=\"border-bottom: 1px solid rgb(251, 251, 251); padding: 8px 10px 6px 12px\"><span style=\"color: rgb(153, 153, 153); float: right; font-family: arial; font-size: 11px; font-style: normal; max-width: 65px; min-width: 50px; visibility: visible; white-space: nowrap\">1:03 PM</span><span style=\"padding-left: 2px; font: 12px Arial; color: rgb(0, 0, 0); text-decoration: none; float: left\">its u again??</span><span style=\"clear: both\"> </span></td></tr></tbody></table>", "fromEmailAddress": "Author<username@domainname.com>" } ] } }

Response:



















































































































































































































































































Get ticket conversation: Fetch the entire conservation, including all threads and comments, recorded on a ticket.         

ZOHODESK.get('ticket.conversation').then(function(res){ //response returns the entire conversation recorded on the ticket }).catch(function(err){ })

Request:



{ "ticket.conversation": { "data": [ { "summary": "Testing", "cc": "support@portalname.domainname.com", "bcc": "", "visibility": "public", "author": { "photoURL": "https://desk1.portalname.com/api/v1/agent/12345678901234567/photo", "name": "Agent Name", "type": "AGENT", "email": "emailID@domainname.com" }, "channel": "EMAIL", "type": "thread", "isForward": false, "hasAttach": false, "responderId": "12345678901234567", "createdTime": "2018-03-22T09:55:50.962Z", "id": "98765432109876543", "to": "emailID@domainname.com", "fromEmailAddress": "\"portalname\"", "actions": [ ], "status": "SUCCESS", "direction": "out" }, { "modifiedTime": null, "attachments": [ ], "encodedContent": "test", "commentedTime": "2018-03-21T09:35:55.000Z", "isPublic": false, "id": "12345678901234567", "type": "comment", "content": "test", "commenterId": "98765432109876543" } ] }, "status": "success" }

Response:






































Get ticket contactId: Fetch the contactId recorded on a ticket.         

ZOHODESK.get('ticket.contactId').then(function(res){ //response returns the contactId recorded on the ticket }).catch(function(err){ })

Request:



{ "ticket.contactId": "271540000000358001", "status": "success" }

Response:






































User Object

Listed below are the properties related to the user object:

  1. user
  2. isLightAgent
  3. tzoffset
  4. fullName
  5. email
  6. portals

Get user information: Fetch information of a particular user.          

ZOHODESK.get("user").then(function(response){ // response returns information about a particular user }).catch(function(err){ // Error handling })

Request:



{ "user": { "fullName": "User fullname", "email": "emailID@domainname.com", "isLightAgent": false, "tzoffset": 330, "portals": { "portalname": "Portal Name" } }, "status": "success" }

Response:












Get full name of user Fetch the full name of a particular user.           

ZOHODESK.get("user.fullName").then(function(response){ // response returns the full name of a user }).catch(function(err){ // Error Handling })

Request:



{ "user.fullName": "user full name", "status": "success" }

Response:





Get LightAgent status of user: Return whether the user is a light agent or not.         

ZOHODESK.get("user.isLightAgent").then(function(response){ // response returns the Boolean value that states whether the user is a light agent or not }).catch(function(err){ // Error Handling })

Request:




{ "user.isLightAgent": "false", "status": "success" }

Response:





Get user time offset: Fetch the time offset of a particular user. Time offset refers to the difference in time between the Coordinated Universal Time (UTC) and the current standard time in the user's time zone. The time difference is expressed in minutes.         

ZOHODESK.get("user.tzoffset").then(function(response){ // response returns the time zone offset of the user }).catch(function(err){ // Error Handling })

Request:



{ "user.tzoffset": 330, "status": "success" }

Response:





Get user email: Fetch the email id of the user.        

ZOHODESK.get("user.email").then(function(response){ // response returns the email address of the user }).catch(function(err){ // Error Handling })

Request:



{ "user.email": "emailID@domainname.com", "status": "success" }

Response:





Get user portals: Fetch the portals to which the user belongs.        

ZOHODESK.get("user.portals").then(function(response){ // response returns the portals to which the user belongs }).catch(function(err){ // Error Handling })

Request:



{ "user.portals": { "marketplacemac": "1234567" }, "status": "success" }

Response:







Portal Object

Listed below are the properties related to the portal object:

  1. plan
  2. id
  3. name
  4. customDomainName

Get portal plan: Fetch the Zoho Desk plan used in the portal.      

ZOHODESK.get("portal.plan").then(function(response){ // response returns the current Zoho Desk plan of the portal }).catch(function(err){ // Error Handling })

Request:



{ "portal.plan": "ENTERPRISE EDITION", "status": "success" }

Response:





Get portal ID: Fetch the ID of the portal.     

ZOHODESK.get("portal.id").then(function(response){ // response returns the id of the portal }).catch(function(err){ // Error Handling })

Request:



{ "portal.id": "54516290", "status": "success" }

Response:





Get portal name: Fetch the name of the portal.     

ZOHODESK.get("portal.name").then(function(response){ // response returns the name of the portal }).catch(function(err){ // Error Handling })

Request:



{ "portal.name": "portalname", "status": "success" }

Response:





Get custom domain name of portal: Fetch the custom domain name of the portal.    

ZOHODESK.get("portal.customDomainName").then(function(response){ // response returns the custom domain name of the portal }).catch(function(err){ // Error Handling })

Request:



{ "portal.customDomainName": "https://customdomainname.com",             "status": "success" }

Response:





Department Object

Listed below are the properties related to the department object:

  1. list
  2. info
  3. id

Note: The following APIs work only for users with admin privileges in their portal.

List all departments: List a specific number of departments from the help desk portal.

ZOHODESK.get("department.list").then(function(response){// response returns the list of deparments }).catch(function(err){ // Error Handling })

Request:



{ "department.list": [ { "name": "department name", "depid": "1234567890123", "agents": [ "1234567890" ], "isActive": true, "isPrivate": false } ], "status": "success" }

Response:














Get department information: Fetch information of a particular department.

ZOHODESK.get("department.info", {"id":"1234567890123"}).then(function(response){ // response returns information on the department specified }).catch(function(err){ // Error Handling })

Request:
Note: id is a mandatory parameter in this API request. 


{ "department.info": [ { "name": "department name", "depid": "1234567890123", "agents": [ "1234567890" ], "isActive": true, "isPrivate": false } ], "status": "success" }

Response:













Get ID of current department: Fetch the ID of the department in which the agent is working at the moment.

ZOHODESK.get("department.id").then(function(response){ // response returns the ID of the department in which the agent is currently working }).catch(function(err){ // Error Handling })

Request:



{"department.id":"123456789012345678","status":"success"}

Response:




Data Storage APIs 

Sometimes, the extensions you create might require data storage and retrieval capabilities. To help you in such cases, we provide a data store for extensions to set (store) and get (retrieve) data. The data can be deleted when it is no longer required. 

The following APIs provide database management functionalities to extensions. 

Get data: Fetch data from the connected database. 

ZOHODESK.get('database',{'key': '3', 'queriableValue':'test'}).then(function(response){ //response returns the value, based on the key specified }).catch(function(err){ // Error handling })

Request:
Note: Both key and queriableValue are optional parameters, but at least one of them must be included in this API request. 

{ "database.get": { "data": [ { "value": { "test": "testid" }, "queriableValue": "test", "key": "3" } ] }, "status": "success" }

Response:














Set data: Asynchronously set data in the connected database.  

ZOHODESK.set('database',{'key':'3','value': {'test':'testid'}, 'queriableValue':'test'}).then(function(response){ // response returns the value saved }).catch(function(err){ // Error handling })

Request:
Note: All three parameters--key, value, and queriableValue--must be passed in this API request. The conditions for each parameter are as follows:

{ "database.set": { "value": { "test": "testid" }, "queriableValue": "test", "key": "3" }, "status": "success" }

Response:










Delete data: Delete data from the connected database.   

ZOHODESK.delete('database',{'key':'3'}).then(function(response){ // response returns the status of deletion }).catch(function(err){ // Error handling })

Request:
Note: key is a mandatory parameter in this API request. 


{ "database.delete": { "delete": "success" }, "status": "success }

Response:







However, these APIs come with the following limitations:

Extension APIs 

These APIs fetch or set information related to the extension.

Get extension config variable: Fetch the installation parameters of the extension. In production mode, only those parameters that have the value of the secure key set to false are returned. In development mode, all parameters are returned, irrespective of the value of the secure key.

ZOHODESK.get("extension.config").then(function(response){ // response returns the installation parameters of the extension }).catch(function(err){ // error handling })

Request:



{ "extension.config": [ { "name": "apikey", "mandatory": true, "type": "text", "value": "apikey", "userdefined": true, "secure": true, "default": "TestData" } ], "status": "success" }

Response:














Set value for extension config variable: Dynamically sets the value for a config variable defined in the plugin manifest file. New config variables cannot be defined using this API. 

ZOHODESK.set('extension.config', {name : 'test', value : "value"}).then(function(res){ //response returns the value saved }).catch(function(err){ })

Request:


{ extension.config: { "data": [ { "userDefined": false, "name": "test", "options": "[]", "secure": false, "mandatory": false, "value": "value", "varId": "12345678901234567" } ] }, status: "success" }

Response:













Request API

This API avoid CORS-related issues and successfully run third-party APIs from the extension.

var reqObj= { url : 'http://demo2022863.mockable.io/test', headers : { 'Content-Type' : 'application/json' }, type : 'GET', data : { 'test' : "key" }, postBody : {}, connectionLinkName: "connectionLinkName specified in the connectors section in plugin-manifest file" } ZOHODESK.request( reqObj ).then(function(response){ // Response for the url }).catch(function(err){ // Error handling })

Request













Invoke APIs

Below are some extra APIs that you can use in your extension. 

ROUTE_TO

This command navigates between the multiple subtabs on the ticket detail page. Listed below are the different routes supported:

  1. ticket.suggestedArticles
  2. ticket.timeline
  3. ticket.attachments
  4. ticket.history
  5. ticket.resolution
  6. ticket.approval
  7. ticket.task
  8. ticket.timeEntry
  9. ticket.conversation
  10. ticket.thread
ZOHODESK.invoke('ROUTE_TO','ticket.attachments')

Code format:


INSERT

This command inserts content into the currently opened reply editor.  

ZOHODESK.invoke('INSERT','ticket.replyEditor', {value : "

We have dispatched the book you ordered.

"});

Code format:


Event APIs

You can configure extensions to receive information when an event, such as adding a comment to a ticket or opening a different ticket occurs on the ticket detail page. 

On Adding a Comment

When a user adds a comment to a ticket from UI, the ticket_comment.add event is broadcast along with comment details.

App.instance.on("ticket_comment.add", function(data){ //data gives the comment detail })

Code format:


{ commentId: "219cc630fdfdef9ad7bf564c20e4e7d0", isPublicComment: false, displayTime: "9 Jan 2018 12:25 PM", displayName: "Name", comment: "Test Comment" }

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


On Moving from One Ticket to Another

When the user clicks a different ticket in the All Tickets view on the left pane, the ticket_Shift event is broadcast.  

App.instance.on("ticket_Shift", function(data){ //data provides status of ticket change as true }

Code format:


{ ticket_shifted: true }

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


On Changing the Due Date of a Ticket

App.instance.on('ticket_dueDate.changed', function(data){ //data gives the duedate changed })

When user changes due date of a ticket from UI, the ticket_dueDate.changed event will broadcast with details of the new due date.

{"ticket.dueDate":"08/15/2018 12:00 PM"}

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

On Changing the Priority of a Ticket

App.instance.on('ticket_priority.changed', function(data){ //data gives the priority changed })

When user changes priority of a ticket from UI, the ticket_priority.changed event will broadcast with details of the updated priority value.

{"ticket.priority":"Low"}

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

On Changing the Classification of a Ticket

App.instance.on('ticket_classification.changed', function(data){ //data gives the classification changed })

When user changes classification of a ticket from UI, the ticket_classification.changed event will broadcast with details of the updated classification value.

{"ticket.classification":"Feature"}

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

On Changing the Product Associated with a Ticket

App.instance.on('ticket_productName.changed', function(data){ //data gives the productname changed })

When user changes product associated with a ticket from UI, the ticket_productName.changed event will broadcast with details of the updated product.

{"ticket.productName":"Finance"}

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

On Changing the Phone Number in a Ticket

App.instance.on('ticket_phone.changed', function(data){ //data gives the phone changed }) 

When user changes phone number of a ticket from UI, the ticket_phone.changed event will broadcast with details of the new phone number.

{"ticket.phone":"1 888 900 9646"}

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

On Changing the Status of a Ticket

App.instance.on('ticket_status.changed', function(data){ //data gives the status changed }) 

When user changes status of a ticket from UI, the ticket_status.changed event will broadcast with details of the updated status.

{"ticket.status":"On Hold"}

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

On Changing the Spam Status of a Ticket

App.instance.on('ticket_isSpam.changed', function(data){ //data gives the spam status changed }) 

When user changes spam status of a ticket from UI, the ticket_isSpam.changed event will broadcast with details of the updated status.

{"ticket.isSpam":"true"}

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

Hook APIs

Event Hooks help developers to introduce their own middlewares in the execution flow of certain UI actions.

Like events, developers need to subscribe to these event hooks to control the execution of UI action by allowing or terminating the flow.

For instance, let us consider that you are creating an extension for a bug-tracking software. The extension can record Zoho Desk tickets as issues in the bug-tracking software.

Now say a support agent tries to close a Zoho Desk ticket that has been recorded as an issue in the bug-tracking software. If the issue is not resolved in the third-party tool, but the ticket is closed in Zoho Desk, it would lead to a mismatch of status, causing confusion.

To prevent such a scenario, you can configure the extension to subscribe to the Zoho Desk hook defined for the close ticket event. With this hook in place, you can further configure your extension such that when an agent tries to close the ticket, the extension checks if the corresponding issue in the third-party tool is resolved. If it is resolved, the extension can give a go-ahead to 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 events:


Extensions can pause the outcome of events by passing either boolean values or promises in the event handler.

If the boolean value TRUE is passed, the event will be executed

If the boolean value FALSE is passed, the event will be terminated.

Similarly, if the extension resolves the promise, the event will be executed, and if the extension rejects the promise, the event will be terminated. The reason for terminating the event can be displayed as an error message to end-users.

Extensions with subscriptions to hooks must respond within 30 seconds of the event getting triggered. If this time is exceeded, control provided to 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. If even one extension passes FALSE or rejects the promise, the event will be terminated.


App.instance.on('ticket.reOpen', function(){  //return promise or boolean })

Hook to Use When a Ticket is Reopened





App.instance.on('ticket.close', function(){          })

Hook to Use When a Ticket is Closed




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

Hook to Use When a Ticket Comment is Made


{ticketId: "26811000000760569", content: "asdsad", isPublic: false}

Response:




App.instance.on('ticket.comment.edit', function(data){                   })

Hook to Use When a Ticket Comment is Edited


{ "commentId":"77ff742bd3193c0278955a2b0c66c43b42acf79f93f757e8", "oldContent":"test", "newContent":"testre", "ticketId":"26811000000760569" } 

Response:










App.instance.on('ticket.reply', function(data){          })

Hook to Use When a Ticket Response is Sent


{ "toAddress":"support@zohodesk.com", "ticketId":"26811000000760569", "ticketSubject":"[## 159 ##] sd", "threadId":"", "fromAdddress":"\"mpdemo\"", "ccAddress":"support@mpdemo.localzohodesk.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" }

Response:















App.instance.on('ticket.replyAndClose', function(data){            })

Hook to Use When a Ticket Response is Sent and the Ticket is Closed


{ "toAddress":"support@zohodesk.com", "ticketId":"26811000000760569", "ticketSubject":"[## 159 ##] sd", "threadId":"26811000000783105", "fromAdddress":"\"mpdemo\"", "ccAddress":"support@mpdemo.localzohodesk.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" }

Response:






















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

Hook to Use When the Status of a Ticket is Changed


{ ticket.oldStatus: "On Hold",  ticket.newStatus: "Escalated" }

Response:




Multi-Widget Support

.... "widgets": [ { "location": "desk.ticket.detail.rightpanel", "name": "Client SDK", "url": "/app/app-iframe-view.html", "logo" : "./app/img/1.png", "icon" : "./app/img/2.png" }, { "location": "desk.ticket.detail.subtab", "name": "Client SDK", "url": "/app/app-iframe-view.html", "logo" : "./app/img/1.png", "icon" : "./app/img/1.png" } ] }, ....

As mentioned earlier, users can access an extension through more than one widget on the Zoho Desk UI. Adding multiple widgets for your extension is a simple task. All that you need to do is include the properties of each widget, separated by comma, as shown in the right panel. 

Inter-Widget Communication

In some cases where an extension has multiple widgets, communication between each widget becomes crucial. This is made possible through inter-widget communication.

App.instance.getWidgets().then(function(widgets) { siblingwidgetId = widgets[0].widgetID var siblingWidget = App.instance.getWidgetInstance(siblingwidgetId); siblingWidget.emit('event', { from: Math.random() }); });

For instance, let us say an extension has two widgets: one at desk.ticket.detail.rightpanel and the other at desk.ticket.datil.subtab. Data from the widget on the right panel needs to be sent to the widget on the subtab. This requirement is achieved through the following code snippet:

Code Explanation

App.instance.getWidgets() returns the array of sibling widgets of the extension.

siblingwidgetId = widgets[0].widgetID gets the widgetID of the widget on the subtab. Here, widgets will have only one element. Therefore, iteration is not required. 

var siblingWidget = App.instance.getWidgetInstance(siblingwidgetId) returns the whole instance of the widget on the subtab.

siblingWidget.emit('event', {from : Math.random()}) sends the event response with data to the widget on the subtab. 

App.instance.on('event', function(data){ // data({from : "randomnumber"}) sent from other widget });

To enable the widget on the subtab to receive the event sent from the widget on the right panel, use the following code snippet:

Besides the main widgets, you can also display information or fetch user input through modal boxes. Modal boxes are UI elements in which users must perform a particular action as part of the overall process. As a result, users will be able to continue using the app on the main window only after performing the said action on the modal box. 

To configure a modal box in your extension, write the necessary code in the modal.html file in the project folder, and reference this file where required in your extension. 

App.instance.modal({ url: '/app/modal.html', title: "Modal box" }).then(function(modalInfo) { var modalInstance = App.instance.getWidgetInstance(modalInfo.widgetID); modalInstance.on('modal.opened', function(data) { console.log('modal opened ++++++++++++++++++') }); }).catch(function(err) { console.log(err, "Modal error"); })

Below is a sample code for a widget that invokes a modal box. 










You can configure a modal box to appear in a smaller size when invoked first and then expand to display more information. You can make this possible through the resize command.

 
ZOHODESK.invoke('RESIZE');

To include this resizing option in the modal box, use the following command in the modal.html file:



















Authorizations

An extension user may need to authorize the Desk or Third party services for the developer to perform the authorized actions such as calling the APIs.

Available inbuilt authentication mode is



Connections

                    
Sample of Manifest with connections
{ ..., "connectors":[ { "connectionLinkName" : "test216", "connectionName" : "test", "serviceName" : "jira", "userAccess" : true, "isUserDefinedService" : false }, { "connectionLinkName" : "myconni", "connectionName" : "myconni", "serviceName" : "zlabs_integration", "userAccess" : true, "isUserDefinedService" : false, "scope" : ["ZohoCliq.Channels.All"] } ] ... }

Connections can be used for the authentication of Zoho and External Services which provides the simplified solution where the authentication process (such as OAuth Client registration, redirection) is handled internally.

Developers can create a connection in the Sigma for the existing & request for new services.

Connection Configuration JSON

After successfully adding the connection in the external Sigma , obtain the Connection Configuration JSON details of the connection and speficy it in the manifest's connectors property. Value for the connectors property should be an array of obtained Connection Configuration JSONs.














Desk Invoke API

Desk's invoke API acts as a proxy between the extension and the External Services or Desk for calling the APIs.

With Desk Invoke API,


                    
INVOKE API REQUEST FORMAT:
URL : api/v1/invoke RequestMethod : POST QueryParams : orgId RequestHeaders : HASH Content-Type : application/x-www-form-urlencoded RequestBody :     #INVOKE_API_REQUEST_PAYLOAD.
INVOKE API RESPONSE FORMAT:
ResponseCode : 200 Content-Type : application/json Response : #INVOKE_API_RESPONSE_OBJECT

Using Desk Invoke API

The query parameters such as orgId , securityContext and headerparam HASH are required for calling Invoke API . orgId & securityContext will be provided by desk during the Platform event callbacks. HASH should be generated using the SECRET and the Request URL inputs. Refer Generating Hash for Invoke API

#INVOKE_API_REQUEST_PAYLOAD

Field Required Type Description
securityContext yes string An Extension specific encrypted token that is used for identifying & authenticating the extension while calling the invoke API.
SecurityContext can be obtained during Platform event callbacks
requestURL yes string URL to be invoked.
requestType yes string HTTP method for invoking the
requestURL
For example,
  • GET
  • POST
  • PATCH
  • PUT
  • DELETE
postBody yes string Payload or body to be sent while invoking the requestURL.
headers yes string Headers to be sent while invoking the requestURL.
queryParams yes string QueryParams to be sent while invoking the requestURL.
connectionLinkName yes string If the authorization needs to be applied from the connections while invoking the requestURL, specify the unique connection name provided by the DRE.

                    

Sample of Invoke API HASH Generation

If manifest.secret => "my_secret_key_238392" For Sample Invoke API Inputs => requestURL = https://api.google.com/test requestType = POST queryParams = {} postBody = {} headers = {"Authorization":"Zoho-oauthtoken ${deskToken}","orgId":376723} connectionLinkName = myConnectionLinkName (*ignore what you don't need)' Hash generation would be => let stringToHash = 'requestURL=https://api.google.com/test&requestType=POST&queryParams={}&postBody={}&headers={"Authorization":"Zoho-oauthtoken ${deskToken}","orgId":376723}&connectionLinkName=myConnectionLinkName' then, generated HASH = hmac_sha256 ( stringToHash, "my_secret_key_238392" ); *Note: While generating HASH, only include the parameters which will be used in the
invoke API. Order of the fields while generating hash should be exactly
1.requestURL, 2.requestType, 3.queryParams, 4.postBody, 5.headers, 6.connectionLinkName and you can ignore the fields which you don't send.

Generate Hash for Invoke API

To call invoke API, HASH is mandatory. Hash is used to verify the invoke API call was originally made by the extension developer. HASH is an HMAC sha256 encrypted string with the key as app's secret (manifest.secret) provided in the manifest and the input as invokeAPI's payload.

Desk also will generate the HASH with the shared app's secret with the provided payload in the invoke API and will match the provided HASH in the desk invoke API's header. If the HASH does not match with the desk generated HASH, then the invoke API will not be processed.

The same process of authentication can be used by the extension during the extension callback events to verify the callback was originally sent by the desk.









Specifying Placeholders in Invoke API

You can specify configParams, authentication details as placeholders while calling the desk invoke API. The placeholders will be replaced with the original values before sending the request to requestURL.

Supported placeholders

Placeholder Syntax Location(s) Description
InstallationId {{PROPERTY}} requestURL,
queryParams,
requestHeaders
eg: https://desk.zoho.com
/api/v1/installations/
{{installationId}}/storage
Config
Params
{{PROPERTY}} requestURL,
queryParams,
requestHeaders
Name of
Config Param
eg : {{jiraAuthKey}}
Auth
details
${PROPERTY} requestHeaders
Refer Authentication
property name

eg : ${deskToken}

                    
Sample of Specifying Connection in invokeAPI payload ... requestURL = https://api.google.com/test connectionLinkName = googleConnection ... Sample of Specifying Zoho OAuth in invokeAPI payload ... requestURL = https://api.google.com/test headers = {"Authorization":"Zoho-oauthtoken ${deskToken}"} ... Sample of Specifying OAuth in invokeAPI payload ... requestURL = https://api.google.com/test headers = {"Authorization":"Bearer ${googleToken}"} ...

Specifying Authentication Details in Invoke API

Invoke API applies the authentication details specified while calling the API. If the extension uses one of the Provided Authorization Methods, find the below table for specifying the authentication details while calling the invoke API.

Auth Type Where to specify
( Parameter Name )
Description
connections payload (connectionLinkName) Name of the connectionLinkName has to be given in the query parameter
connectionLinkName while calling the invoke API.

                    
Sample Response : { "responseHeaders" : { "Cache-Control" : "private,no-cache,no-store,max-age=0,must-revalidate", "Set-Cookie" : "drecn=9e1f7200-bcdf-426d-9628-79ff4e9241c8; Path=/; Secure", "Vary" : "Accept-Encoding", "Expires" : "Thu, 01 Jan 1970 00:00:00 GMT", "X-XSS-Protection" : "1", "Content-Type" : "application/json;charset=utf-8" }, "response" : "{\"data\":[{\"ticketNumber\":\"176\",\"customerResponseTime\":\"2014-03-22T05:05:08.471Z\",\"productId\":null,\"contactId\":\"1892000000045028\",\"subject\":\"from forum\",\"dueDate\":\"2016-06-01T14:04:07.000Z\",\"departmentId\":\"1892000000006907\",\"channel\":\"Forums\",\"threadCount\":\"72\",\"priority\":\"High\",\"assigneeId\":\"1892000000056007\",\"closedTime\":null,\"commentCount\":\"0\",\"phone\":null,\"contact\":{\"firstName\":\"\",\"lastName\":\"as\",\"phone\":null,\"id\":\"1892000000045028\",\"type\":null,\"email\":\"manojkumar.s+4444@zohocorp.com\",\"account\":{\"website\":\"qwe.com\",\"accountName\":\"Man_Account\",\"id\":\"1892000000980421\"}},\"createdTime\":\"2014-03-06T09:49:50.000Z\",\"id\":\"1892000000094004\",\"email\":\"example@example.com\",\"status\":\"Open\"}]}", "statusCode" : "200" }

#INVOKE_API_RESPONSE_OBJECT

Field Type Description
statusCode integer Status Code from the requestURL by the invoke API request.
response string Response from the requestURL by the invoke API request.
responseHeaders string Response headers sent by the requestURL.

Extension Data Specific APIs

The following are the APIs available to the extension that can be accessed using Desk Invoke API. InstallationId wont be provided to the developers, instead he has to use installationId placeholder in Invoke API to call the apis.

Storage API

Extensions can make use of Storage API to access & modify data from the extension's DB storage. The available storage API 's are,

                    
#STORAGE_DATA_OBJECT { "key" : "color-red-data-id-278783", "queriableValue" : "color-red-datas", "value" : { "myResult" : "Diluted solution for red color experiment 128ml " } }

#STORAGE_DATA_OBJECT

Every storage object has the following properties.

Field Type Description
key string Key for the value which can be used to lookup.
value JSONObject Specifies the value that needs to be stored for the given key.
queriableValue string Used to group the multiple storage data. Specifies a common lookup group of the given key-value pair which will be useful for lookup from the database.

                    
Add data to storage: Request Format:
URL : https://desk.zoho.com/api/v1/installedExtensions/{{installationId}}/storage OAuth Scope : Desk.extensions.CREATE RequestMethod : POST RequestHeaders : orgId, Authorization Content-Type : application/json RequestBody :     #STORAGE_DATA_OBJECT.
Response Format:
ResponseCode : 200 Content-Type : application/json Response : #STORAGE_DATA_OBJECT

Add data to storage

Use this API to add data to the extension storage.


















                    
Get data from storage: Request Format:
URL : https://desk.zoho.com/api/v1/installedExtensions/{{installationId}}/storage OAuth Scope : Desk.extensions.READ RequestMethod : GET RequestHeaders : orgId, Authorization QueryParams : key, queriableValue, from, limit
Response Format:
ResponseCode : 200 Content-Type : application/json Response : JSONObject with data property containing the result as array of #STORAGE_DATA_OBJECT.

Get data from storage

Use this API to get data from the extension storage with the matching criteria.


















                    
Delete data from storage: Request Format:
URL : https://desk.zoho.com/api/v1/installedExtensions/{{installationId}}/storage OAuth Scope : Desk.extensions.DELETE RequestMethod : DELETE RequestHeaders : orgId, Authorization QueryParams : key
Response Format:
ResponseCode : 200

Delete data from storage

Use this API to delete data from the extension storage specifying the key.


















Configuration Param API

Extensions can make use of Configuration Param API to access & modify the extension's config params. The available storage API's are,

                    
#CONFIG_PARAM_API_REQUEST_OBJECT { "variables" : [ { "name" : "configParam1", "value" : "value for the configparam 1" }, { "name" : "configParam3", "value" : "value for the configparam 3" } ] } #CONFIG_PARAM_API_RESPONSE_OBJECT { "data" : [ { "defaultValue" : "default1", "userDefined" : true, "name" : "variable1", "options" : "[op1, opt2]", "secure" : true, "type" : "text", "mandatory" : false, "value" : "testing", "varId" : "4000000011017" } ] }

#CONFIG_PARAM_API_REQUEST_OBJECT

Field Type Description
variables JSONArray <#CONFIG_PARAM> Array of config params to be saved in desk

#CONFIG_PARAM

Field Type Description
name string Name of the config param
value string Value of the config param























                    
Add config params: Request Format:
URL : https://desk.zoho.com/api/v1/installedExtensions/{{installationId}}/configParams OAuth Scope : Desk.extensions.CREATE RequestMethod : POST RequestHeaders : orgId, Authorization Content-Type : application/json RequestBody :     #CONFIG_PARAM_API_REQUEST_OBJECT.
Response Format:
ResponseCode : 200 Content-Type : application/json Response : #CONFIG_PARAM_API_RESPONSE_OBJECT

Add or update config params

Use this API to add or update config params.















                    
Get config params: Request Format:
URL : https://desk.zoho.com/api/v1/installedExtensions/{{installationId}}/configParams OAuth Scope : Desk.extensions.READ RequestMethod : GET RequestHeaders : orgId, Authorization
Response Format:
ResponseCode : 200 Content-Type : application/json Response : #CONFIG_PARAM_API_RESPONSE_OBJECT

Get config params

Use this API to get extension's config params.
















Extension Log APIs

Extensions can make use of Log API to log the extension processes. Logs can be viewed on customer's support portal.

Logs will expire after 3days.

Path to Logs:

Parent Logs List:

Sub Logs List:

During development, logs are loaded in the developer's portal.

The param - reference is a random string that is used to group multiple logs. If multiple logs are grouped using reference, the first log will be the parent log and the rest will be sub logs. Developer can group a maximum of 10 logs using a reference. Means, a parent log can have 9 sub logs as maximum.

If the logs are created without reference, they are considered as parent logs.

The available APIs for Logs are,

                    
#EXTENSION_LOG_OBJECT { "reference" : "1d8bd6c8-5424-11e8-9c2d-fa7ae01bbebc", "description" : "jira issue is created with id = 10223", "installationId" : "112343231355", "title" : "created a jira issue" } #LOG_API_RESPONSE_OBJECT { "parentLogId" : "4000000022003", "extensionName" : "Jira", "description" : "jira issue is created with id = 10223", "logId" : "4000000023001", "installationId" : "112343231355", "title" : "create jira issue", "logTime" : "2018-05-10T07:33:13.000Z" }

#EXTENSION_LOG_OBJECT

Field Type Description
reference string a UUID string used to group multiple logs. Logs having the same reference key will be stored as a single log.
description string Log message.
installationId long The id of the installed extension
title string Title of the log















                    
Add a log: Request Format:
URL : api/v1/extensionLogs OAuth Scope : Desk.extensions.CREATE RequestMethod : POST RequestHeaders : orgId, Authorization Content-Type : application/json RequestBody :     #EXTENSION_LOG_OBJECT.
Response Format:
ResponseCode : 200 Content-Type : application/json Response : #LOG_API_RESPONSE_OBJECT

Add a Log

Use this API to add extension logs.















Platform Event Callbacks

                    

plugin-manifest.json

... "callbackListener":{ "onZohoAuthorise": "https://zohodeskapp.com/authorized.php", "onConfigParamAdd": "https://zohodeskapp.com/adjustfiler.php", "onUninstall": "https://zohodeskapp.com/revokeWebhook.php" }, ...

Marketplace supports callbacks for extension's events. Developer can subscribe to the supported events and when the extension event is triggered, callbacks are invoked.

Supported Platform Event Callback's are,

Subscribing to the Events

Extension manifest's callbackListener property is used for declaring the callbacks for the extension's events. To subscribe to an event, specify the callback URL in the manifest for the callbackListener's respective event.

                    
Event Callback Request
URL : manifest.callbackListener.{eventName} RequestMethod : POST RequestHeaders : HASH Content-Type : application/json RequestBody :     JSONObject in #EVENT_CALLBACK_PAYLOAD_FORMAT format.
EVENT_CALLBACK_URL https://zohodeskapp.com/authorized.php
EVENT_CALLBACK_HEADERS { "HASH" : "xxxxxxxxx" }
EVENT_CALLBACK_PAYLOAD_FORMAT { "event" : "onZohoAuthorise", "orgId" : 387238, "securityContext" : "2398deio3qwnx3c9xwi3nc3njkh9jfico" }

Event Callback Request

Whenever the extension event occurs, the callback URL is triggered with security parameters which can be used to call the authenticated APIs.

#EVENT_CALLBACK_PAYLOAD_FORMAT

Field Description
event Name of the event triggered the callback.

Possible values are,
  • onInstall
  • onZohoAuthorise
  • onTPAAuthorise
  • onTPARevoke
  • onUpdate
  • onConfigParamAdd
  • onConfigParamDelete
  • onUninstall
orgId orgId of the user's organisation.
securityContext securityContext which can be used for calling Desk's invoke API.


Events


onInstall

onInstall will be triggered when the customer installs an extension. The developer can subscribe to the event with the URL and perform his logic. 

Example usage

Creating a Third party resource once the customer installs the extension.


onZohoAuthorise

Once the end user installs the extension, he needs to authorize the desk & TPA (say jira). onZohoAuthorise will be triggered when the customer authorize the DESK. The developer can subscribe to the event with the URL and perform his logic. 

Example usecase

Creating a Zoho Desk webhook once the desk authorization is completed.


onTPAAuthorise

The event will be triggered once the user authorize the Third party app. 

Example usecase

Subscribing or creating a webhook in third party service.


onTPARevoke

The event will be triggered once the user revoke the Third party app authorization.

Example usecase

Revoking or deleting the added webhooks in the third party services.


onUpdate

The event will be triggered when the user upgrade the extension.

Example usecase

Adding or Updating config params.


onConfigParamAdd

The event will be triggered when the customer adds configuration params in the extension. 

Example usecase

In jira extension, when customer adds configuration params such as domain, jiraAuthKey , developer shall create a jira webhook against the jira account.


onConfigParamDelete

The event will be triggered when the customer adds configuration params in the extension. 

Example usecase

In jira extension, when customer deletes configuration params such as domain, jiraAuthKey , developer shall delete jira webhook in the jira account.


onUninstall

The event is triggered when customer uninstall the extension.

Example usecase

Operations to be performed during un-installation such as deleting webhooks.

Introduction

Channel Integration lets you to sync data such as tickets, threads and contacts between Zoho Desk and External Services. It also lets the agents to send replies to the queries on external platforms directly from the desk. Channel Integration can be achieved by ZohoDesk Marketplace app which must act as a bridge between External Service and the Zoho Desk.

Marketplace App with Channel Integration:

Marketplace App which supports the channel integration is responsible for

Configuration

Requirements:

                    

Resources.json

... "channel": { "channelLogoPath" : "/app/img/youtube_logo.png", "acceptAttachments" : false, "updateRecords" : true, "contentTypes" : ["text/plain","text/html"], "redirectUrl" : "https://zohodeskapp.example.com/youtube/handleRedirect", "includeQuotedMessage": false, "sync": { "push": "https://zohodeskapp.example.com/youtube/handlePull", "pull": "https://zohodeskapp.example.com/youtube/handlePush" } } ...

Resources.json

Channel configuration in the Resources.json specifies the settings of the channel and the endpoints to pull or push the data for syncing. The params to be specified in the manifest are listed below.

#CHANNEL_INTEGRATION_CONFIGURATION

Field Required Type Description
sync yes JSONObject SYNC_OBJECT Sync property of the channel defines the endpoints that are used for handling data sync.
channelLogoPath yes string (URL) Relative Path of the channel's logo image in the app directory.

Logo Specification:
image-format : png, jpg
max-size : 500kb
redirectUrl string (URL) URL which can redirect the user to the external resource of an entity. Refer Source Redirection
updateRecords boolean Specifies whether the replies of this channel can be updated.
Default : false
acceptAttachments boolean Specifies whether attachments can be added to the replies of this channel.
Default : false
includeQuotedMessage boolean Specifies whether it is advisable to add the previous replies as quoted to the replies while replying for this channel.
Default : false
contentTypes JSONArray <string> Specifies the allowed/supported content types (MIME types) for the replies of this channel.
Default : text/plain

Allowed Values :
  • text/plain
  • text/html


Sync Object

Field Required Type Description
push yes string (URL) An endpoint to accept the replies from the desk by agent to process them and update in the external service.
Refer Push Request from Desk
pull string (URL) An endpoint which supplies updated external data when desk periodically requests.
Refer Pull Request from Desk

Sync (Data Transfer)

Desk offers the below methods to perform two-way data syncing between Desk & External Service.


Overriding read-only fields

An important usage of the syncing via channel integration is, some of the read-only system-computed fields such as createdTime, modifiedTime and direction of the thread can be specified and overridden with the original value in the external service.

                    
#SYNC_RESPONSE_OBJECT
{ "channelState":"{\"my_pending_data\":\"298092,289782,2767\"}", "data":{ "tickets": [ #SYNC_TICKET_OBJECT, #SYNC_TICKET_OBJECT, #SYNC_TICKET_OBJECT, ... max 1000 ], "threads": [ #SYNC_THREAD_OBJECT, #SYNC_THREAD_OBJECT, #SYNC_THREAD_OBJECT, ... max 1000 ] } }

#SYNC_RESPONSE_OBJECT

Desk accepts data to be synced in the below format during pull-request from desk and push-data to desk operation.

Field Required Type Description
channelState string Value to be stored in the app's channelState configParam. Can be used to store the state of the channel sync progress.
Refer Channel State
data yes JSONObject <#SYNC_DATA_OBJECT> Contains the data to be imported from the external service, converted to desk compatible format. Supported properties are,
  • tickets
  • threads

#SYNC_DATA_OBJECT

Data format to be specified in the sync response's data property.

Field Required Type Description
tickets JSONArray (1000) <SYNC_TICKET_OBJECT> Array of ticket properties to be imported.
threads JSONArray (1000) <SYNC_THREAD_OBJECT> Array of thread properties to be imported.
                    
Example of #SYNC_TICKET_OBJECT
{ "data":{ "tickets":[ { "extId":"whatsapp:+919994411345", "subject":"How to reset the configuration?", "createdTime":"2018-09-10T13:34:26.000Z", "actor": #SYNC_ACTOR_OBJECT, "extra": #SYNC_EXTRA_OBJECT }, ... ], "threads":[] } }

Create or Update Tickets in Desk

Channel integration lets you create and update tickets for external resources. To import tickets to the desk, the details of the ticket has to be given in the #SYNC_TICKET_OBJECT format to the pull-request from desk & push-data to desk response's data.tickets property.

#SYNC_TICKET_OBJECT

Field Required Type Description
extId yes string Unique ID of the ticket in the external service. Refer External ID
actor yes JSONObject #SYNC_ACTOR_OBJECT Details about the author.
subject yes string (255) Details about the author.
extra yes JSONObject #SYNC_EXTRA_OBJECT Extra information about the ticket for the extension.
createdTime Timestamp ISO Format Created time of the ticket.
email string EMail ID of the ticket.
phone string Phone number of the ticket.
description string Description of the ticket.
status string Status of the ticket.
category string Ticket category.
subCategory string Ticket sub category.
resolution string Resolution of the ticket.
dueDate Timestamp ISO Format Due date for resolving the ticket.
priority string Priority of the ticket.
classification string Classification of the ticket.
customFields JSONObject Custom fields in the ticket.
assigneeId long ID of the agent to whom the ticket is assigned.
teamId long ID of the team assigned to resolve the ticket.
productId long Product to which the ticket is mapped.

External ID

ID of the resource on external service. External ID is the value which acts as a unique identifier of the resource on the external resource. The extId property plays a significant role in identifying the corresponding desk entity of the external resource during sync. If there is no entity found for the given extId of the channel in the desk, a new entity is created otherwise the existing entity which has the extId of the channel is updated. Allowed characters : A-Z a-z 0-9 @ $ & + : . { } ( ) # - _ +

For example

If we consider a Facebook post, every Facebook post has a unique ID (extId). When an external resource of Facebook service with extId "298393" is submitted to create a ticket in desk, if any existing ticket with Facebook channel and the same extId found then the ticket is updated with given data, otherwise a new ticket is created with facebook channel and given extId.


Adding External Attachments

If any external attachments need to be added to the tickets/threads, it has to be given as a JSON array of URLs which will be downloaded and attached to the tickets. The downloading of the attachments and adding to the tickets are asynchronous so that failure of attachments does not interrupt the process of adding/updating the tickets or threads. 

For example

A facebook comment may contain images that you want to add as a thread.


                    
Example of #SYNC_THREAD_OBJECT
{ "data":{ "threads":[ { "extId":"SMa8974b1b935d957ffd9", "extParentId":"+123456789", "createdTime":"2018-09-10T11:54:03.000Z", "content":"What surprised", "direction":"in", "from":"+00032882", "to":["+00000273637"], "canReply":true, "extra": #SYNC_EXTRA_OBJECT, "actor": #SYNC_ACTOR_OBJECT }, ... ], "tickets":[ { "extId":"+123456789", "subject":"Knew he didnt know, i never knew a lot too!", "createdTime":"2018-09-07T14:02:27.000Z", "extra": #SYNC_EXTRA_OBJECT, "actor": #SYNC_ACTOR_OBJECT }, ... ] } }

Create or Update Threads in Desk

Channel integration lets you to add threads or replies from the external service. To import threads, provide the threads details in the #SYNC_THREAD_OBJECT format to the pull-request from desk & push-data to desk response's data.tickets property.

#SYNC_THREAD_OBJECT

Field Required Type Description
extId yes string Unique ID of the thread in the external service. Refer External ID
extParentId yes string Parent Entity's ID of the thread in the external service.
Threads having the same parentId will be grouped under the same ticket which's extId is equal to the parentId. Refer External Parent ID
actor yes JSONObject #SYNC_ACTOR_OBJECT Details about the author.
content yes string Content of the thread.
direction string Specifies the direction of the thread. Supported Values are
  • in (incoming thread)
  • out (outgoing thread)
extra JSONObject #SYNC_EXTRA_OBJECT Extra information about the thread for the extension.
attachmentUrls JSONArray <string> Urls of the attachments to be added to the thread. Refer Adding External Attachments
createdTime Timestamp ISO Format Created time of the thread.
modifiedTime Timestamp ISO Format Modified time of the thread.
canReply boolean Specifies whether replies can be added to this thread.
contentType string Content-Type of the content of the thread. Supported Values are
  • text/plain
  • text/html
from string From address of the thread.
Default : Channel Name
to JSONArray <string> Direct Recipients of the thread
cc JSONArray <string> cc'ed Recipients of the thread
bcc JSONArray <string> bcc'ed Recipients of the thread


External Parent ID

This constraint is used for grouping multiple threads under a single ticket. extParentId contains the parent ID of the external resource in external service. For a thread external resource, extParentId is useful for identifying the correct ticket that the reply has to be added in the desk. Whenever an extParentId for a thread resource is given, a thread is added for the ticket that has the given extParentId as extId. If none of the ticket's extId matches the given extParentId then the thread will not be added.

For example

If we consider a facebook-post-comment, extParentId will be the ID of the facebook post. So that comments having the same externalaParentId will be grouped under the same ticket which has the extId.


                    
SYNC ACTOR OBJECT Sample:
{ "name" :"John Snow", "displayName" :"John Snow @johnstark", "email" :"john@gmail.com", "phone" :"+918637436803", "extId" :"39jdiwkndw3ninj", "photoURL" :"https://example.com/profile/39jninj/photo.jpg" }

#ACTOR_OBJECT

Actor object defines the properties of the author of the resource (i.e by whom this resource was made) on the external service.
Field Required Type Description
extId yes string Unique ID of the person in the external service. Used for contacts profiles sync.
name yes string Name of the person in the external service.
displayName string Name to display on contact's detail page. If not provided, defaults to name
email string Email ID of the author on external service. If provided, this profile will be added under the contact who have the same Email Id.
phone string Phone number of the author on external service. If provided, this profile will be added under the contact who have the same Phone Number.
photoURL string <URL> URL of the author's photo on external service.


Providing app specific custom data for resources

Apps may need to store some extra details about a particular resource in addition to the desk supported standard fields of an entity.
Place holders can be given for any of the Extra-Object's properties which will be replaced with the original values of the respective entities. For supported placeholders refer Desk Resource Template Placeholders

Example

An app developer may need to store likes and share counts of a facebook post which needs to be added as a ticket or thread in the desk so that the app may show that information in the desk widgets or may need to show some actions prior to that information.


                    
SYNC EXTRA OBJECT Sample:
{ "key" :"Post-{{ticket.id}}-Comment-{{thread.id}}-Details", "queriableValue" :"Post-{{ticket.id}}-Details" "value" :{ "likes":3809, "comments":453 } }

#SYNC_EXTRA_OBJECT

Field Required Type Description
key yes string Value for the key property can be a templated string with supported placeholders.
Key for which the given value has to be stored in the DB storage. Refer extra.key
value yes JSONObject Specifies the value that needs to be stored for the given template key.
queriableValue yes string Specifies a common lookup group of the given key-value pair which will be useful for lookup from the database.

Desk Resource Template Placeholders

Developer may not know the values of the resource but might want to make use of it to store some information in extension's DB storage. So developers can make use of place holders which are replaced with original values after updating the resource.

Format for the templated string with placeholder - {{PLACEHOLDER}}

Supported Placeholders are,


extra.key

Value for the key property is a string which can be a template key. Key for which the given value has to be stored in the DB storage.

For Example,

When a like count of a post has to be stored in the extension's db storage so that an app can re-access them, the external resource response's extra.key can be specified as 
facebook_comment_{{thread.id}}_data.
 When the external resource is processed and generated as a desk thread with threadId 2980928, the key will be replaced as facebook_comment_2980928_data and the value given in the extra.value will be stored for the key facebook_comment_2980928_data in that app's DB storage. Later the app can lookup the DB storage from the widget with the key facebook_comment_2980928_data to get the value of the likes count.

                    
PULL REQUEST FORMAT:
URL : channel.sync.pull RequestMethod : POST QueryParams : securityContext, orgId Content-Type : application/json RequestBody :     All the non-secure configParams (CONFIG_PARAMS_OBJECT).
CONFIG_PARAMS_OBJECT: { "channelState" : "{\"my_last_fetch_time\":\"Jan 11\"}", "myConfigParam1" : "My value for configParam", "myConfigParam2" : "My value for configParam2", "myConfigParam3" : "My value for configParam3" } PULL REQUEST RESPONSE FORMAT:
ResponseCode : 200 Content-Type : application/json Response : Updated Data to be added in desk in the #SYNC_RESPONSE_OBJECT format.

Pull Request from Desk

Desk periodically invokes a request to the endpoint specified in the
extension manifest's channel.sync.pull property. Pull requests are made every 4 minutes from the desk. You can send the new/ updated data of tickets and threads that needs to be updated in desk in the specified #SYNC_RESPONSE_OBJECT format.

*Tip : You might need to call the external service's API to get new data which has to be sent to the desk. Call the external service's API through Desk Invoke API so that authentications are handled automatically (e.g Using connections) .

*Tip : You might want to keep track of the syncing status of the data you transferred (e.g number of tickets remaining to be sync). Make use of channelState property, to store the tracking data during pull, push & import requests.

                    
PUSH REQUEST FORMAT:
URL : channel.sync.push RequestMethod : POST QueryParams : securityContext, orgId Content-Type : application/json RequestBody :     JSONObject containing reply & configParams in #PUSH_REPLY_PAYLOAD_FORMAT format.
PUSH REQUEST RESPONSE FORMAT:
ResponseCode : 200 Content-Type : application/json Response : Data to be updated in desk in #PUSH_REPLY_RESPONSE_OBJECT format.

Push Reply from Desk

When an agent replies from the desk for a ticket or thread created by channel integration, the reply is pushed to the Resources.channel.sync.push endpoint of the app which needs to be delivered to the external service so that the reply will be processed further. The push endpoint given in the manifest is responsible for handling the desk reply, delivering it to the external service and submit the status & response of the processed reply to the desk back.
The Progress of an agent reply from the desk for a ticket or thread created via channel integration are,


*Tip : To send the received agent reply to other service, you may need to call other service's APIs. Use Desk Invoke API to call the external service's API, so that authentications are handled automatically (e.g Using connections) .

                    
Sample of #PUSH_REPLY_PAYLOAD_FORMAT:
{ "configParams" :{ "channelState" : "{\"my_last_fetch_time\":\"Jan 11\"}", "myConfigParam1" : "My value for configParam", "myConfigParam2" : "My value for configParam2", "myConfigParam3" : "My value for configParam3" }, "resource":{ "extParentId" : "1276576533", "replyToExtId" : "3298dniuniu3", "id" : 287189379819, "content" : "Hi customer, thanks for writing to us", "summary" : "Hi customer, thanks for writing to us", "contentType" : "text/plain", "hasAttach" : false, "attachments" : [], "author" : { "name" : "John Snow", "email" : "john.snow@example.com", "type" : "AGENT", "photoURL" : "https://desk.zoho.com/api/v1/agents/387829/photo?orgId=28732" } } }

#PUSH_REPLY_PAYLOAD_FORMAT

Field Type Description
configParams JSONObject CONFIG_PARAMS_OBJECT All the non-secure config params belongs to the app.
resource JSONObject DESK_REPLY_THREAD_OBJECT Details of the Agent's reply to be sent to the external service.

#DESK_REPLY_THREAD_OBJECT

Field Type Description
extParentId string External Id of the thread's ticket. (i.e) Parent Entity's ID of the thread in the external service.
replyToExtId string Add the reply to this message in the External Service. External Id of the thread to which the reply has to be added.
id long ID of the thread.
author JSONObject Details about the author with the following properties.
  • name
  • email
  • type
  • photoURL
content string Content of the thread.
contentType string Content-Type of the content of the thread. Possible Values are
  • text/plain
  • text/html
summary string Summary of the thread.
hasAttach boolean Specifies whether thread has attachments.
attachments JSONArray <JSONObject> Attachments in the thread. Each attachment object contains properties:
  • id
  • name
  • size
  • href
attachmentCount integer Count of the attachments
createdTime Timestamp ISO Format URL to get the full content of the thread.

#PUSH_REPLY_RESPONSE_OBJECT

Field Required Type Description
extId yes string Unique ID of the thread added in the external service.
extra JSONObject #SYNC_EXTRA_OBJECT Extra information about the thread for the extension.
canReply boolean Specifies whether replies can be added to this thread.
from string From address of the thread.
Default : Channel Name
to JSONArray <string> Direct Recipients of the thread

Push-Data to Desk

                    
PUSH REQUEST FORMAT:
API Endpoint : /api/v1/channels/{{installationId}}/import RequestMethod : POST Content-Type : application/json RequestBody :     JSONObject in the #SYNC_RESPONSE_OBJECT format.

Whenever you need to manually push tickets & threads to the desk, you can use the following API. This API expects the payload in the #SYNC_RESPONSE_OBJECT format.

To call desk's "channels/import" API, orgId and securityContext params are mandatory. You may obtain these details during any of the extension event callbacks & store them so that you can reuse them during the channels/import API call.

*Tip : Call the channels/import API through Desk Invoke API so that authentications are handled automatically (e.g Using connections) .

For Example:

You may have subscribed to the events such as onConfigParamAdd, onDeskAuthorize or onTPAAuthorise in the manifest's callBacks. Which will make request to the callback URL mentioned in the manifest with the securityContext & orgId during the respective events. During this event, you may subscribe to another service. The given orgId, securityContext can be stored or can be added in the subscription URL (e.g queryParams ) so that whenever the external service requests you with respect to the subscription, you can obtain the orgId and the securityContext from the URL and use it to call the desk "channels/import"  API.

Channel State

About

Channel State represents the channelState non-secure configParam which is automatically added for the channel type of marketplace apps during installation. Channel State can be used as the current status of the channel's syncing process such as last fetched item, remaining API limit in the external service, pending or next set of entities to be fetched and so on.

The value of the channelState param can be any string which will be updated in the app's channelState configParam during the pull & push requests. If you don't want to update the channelState of the app in ZohoDesk, then exclude the channelState key while sending the sync request response.



                    
Source Redirection:
URL : channel.redirectUrl RequestMethod : GET QueryParams : entity, id, parentId

Source Redirection

The redirectUrl endpoint specified in the Manifest's channel param is responsible for redirecting the user to the external resource of an entity when requested by the desk.

Source Redirection Query Params

Field Description
entity Type of the entity to be redirected.
Supported Values are
  • ticket
  • thread
  • user_profile
id External Id of the entity in the external service.
parentId External Parent Id of the entity. In case of threads, parentId contains the extId of the ticket.

For example:

When an agent views a ticket, a hyperlink to the external resource will be shown to the agent. On clicking the hyperlink, the agent is redirected the redirectUrl in the manifest with the extId of the resource and the type of the entity as queryParams of the Url. The redirect URL should parse the entity type and id of the resource and redirect the user to the external service which contains the resource.

Extension webhook

                    

resource.json

... "webhook":{ "url": "https://demowebhook.com/callbackurl", "name": "Demo extension webhook", "description": "Demo extension webhook listen to ticket events", "subscriptions":{ "Ticket_Add":null, "Ticket_Update":{ "includePrevState":true }, "Ticket_Thread_Add":null, "Ticket_Comment_Add":null, "Ticket_Comment_Update":null } "ignoreSourceId":"2df92c1a-973a-48f5-95b7-5792c68b9c36" }, ...
Sample of Manifest entry with Zoho Authorization - using Connnections (Desk.events.ALL scope should be included in connection scope list)
{ ..., "zohoAuthorisation" : { "type" : "connectors", "connectionLinkName" : "test12345", "connectionName" : "Webhook zoho desk", "serviceName" : "Zoho Oauth", "userAccess" : true, "scope" : ["Desk.events.ALL"], "isUserDefinedService" : false }, ... }

Marketplace supports Extension Webhook , which will allow a market place app to create extension specific desk webhooks. Extension webhook access is restricted to the extension app alone so that the normal user can't make any changes on these webhooks.

An extension developer can specify the webhook details in resource.json file. Extension webhooks will be created on authorising the extension and will be deleted on uninstalling the extension. We will apply department filtering on webhook events based on the departments choosed in the extesnion configuration.

On subscribing extension webhook we will append the orgId and securityContext to the webhook callback url. You can use this information to make API calls using Desk's invoke API.

Note:Extension webhook is allowed only for org based extensions

How to handle Authorisation

To use extension webhook you should include the webhook specific scope Desk.events.ALL in the authorisation scope list and the authorisation should be defined using the key zohoAuthorisationin plugin-manifest file.



#WEBHOOK_FIELDS

(Refer Webhook's Attribute section to know more about webhook fileds.We will apply department filtering on webhook events based on the departments choosed in the extesnion configuration. Kindly ignore the departmentIds key while giving the subscription details in resource.json file.)

Field Required Type Description
url yes string Server endpoint to which event information must be sent.
name yes string Name of the webhook.
description no string Description of the webhook
ignoreSourceId yes string Client ID exempted from triggering webhooks. The value of this attribute must always be a UUID. For information on how to use this attribute, refer to the Ignoring Webhook Events section.

subscriptions yes JSONObject Events that you want to subscribe to. To know about the supported events and its payload refer Event Supported.