This tutorial is part of a series that builds a Zendesk integration for Slack:

The integration listens for Ticket Created events in Zendesk. When it detects an event with a specific ticket priority, the integration posts a related message to a provided Slack channel.

If you followed along in the series, you should now have a working Zendesk app. The app serves as a user interface (UI) for your integration. In this tutorial, you'll install the integration and finish building the app.

Create a bundle

To run, your integration requires a Zendesk Integration Services (ZIS) bundle. A bundle contains JSON objects that define the logic and resources for an integration.

The bundle for this integration should include JSON objects for:

  • A custom action that gets ticket data
  • A custom action that posts a message to a Slack channel
  • A ZIS flow that defines the integration's logic as states
  • A job spec that runs the above flow when ZIS detects a Ticket Created event

Create a bundle skeleton

To start, create a skeleton file for your bundle.

  1. Create a file named zendesk-to-slack-bundle.json.

  2. Add the following JSON to the file:

    {  "name": "Post Slack message on ticket creation",  "description": "Posts ticket data to Slack channel on ticket creation",  "zis_template_version": "2019-10-14",  "resources": {    "get_zendesk_ticket_http_action": {      "_placeholder_": "Action properties here"    },    "post_message_to_slack_http_action": {      "_placeholder_": "Action properties here"    },    "post_ticket_update_flow": {      "_placeholder_": "Flow properties here"    },    "post_message_to_slack_on_ticket_created_job_spec": {      "_placeholder_": "Job spec properties here"    }  }}

    The JSON contains placeholders for the bundle's action, flow, and job spec objects.

Define the custom actions

Next, define the bundle's custom actions.

  1. In zendesk-to-slack-bundle.json, replace the get_zendesk_ticket_http_action placeholder with the following:

    "get_zendesk_ticket_http_action": {  "type": "ZIS::Action::Http",  "properties": {    "name": "get_zendesk_ticket_http_action",    "definition": {      "method": "GET",      "path": "/api/v2/tickets/{{$.ticketId}}.json",      "headers": [        {          "key": "Authorization",          "value": "Bearer {{$.token}}"        }      ]    }  }},

    When run, the action calls the Zendesk Show Ticket endpoint to get ticket data. To make the call, the action requires two input variables: {{$.ticketId}} and {{$.token}}. You'll set these variables when you define the bundle's flow.

  2. Replace the post_message_to_slack_http_action placeholder with the following:

    "post_message_to_slack_http_action": {  "type": "ZIS::Action::Http",  "properties": {    "name": "post_message_to_slack_http_action",    "definition": {      "method": "POST",      "url": "",      "headers": [        {          "key": "Authorization",          "value": "Bearer {{$.token}}"        }      ],      "requestBody": {        "channel": "{{$.channelId}}",        "blocks": [          {            "type": "section",            "text": {              "type": "mrkdwn",              "text": "{{$.title}}"            },            "fields": [              {                "type": "mrkdwn",                "text": "*Subject*"              },              {                "type": "mrkdwn",                "text": "*Description*"              },              {                "type": "plain_text",                "text": "{{$.ticketSubject}}"              },              {                "type": "plain_text",                "text": "{{$.ticketDescription}}"              }            ]          },          {            "type": "section",            "text": {              "type": "mrkdwn",              "text": "{{$.link}}"            }          }        ]      }    }  }},

    When run, the action calls the Slack API's chat.postMessage method to post a message to a Slack channel. To post the message, the action requires the following input variables:

    • {{$.token}}
    • {{$.channelId}}
    • {{$.title}}
    • {{$.ticketSubject}}
    • {{$.ticketDescription}}
    • {{$.link}}

    You'll set these variables later when you define the bundle's flow.

Define the flow

Next, define a ZIS flow for the bundle.

In zendesk-to-slack-bundle.json, replace the post_ticket_update_flow placeholder with the following JSON. In the JSON, replace {subdomain} with your Zendesk subdomain.

"post_ticket_update_flow": {  "type": "ZIS::Flow",  "properties": {    "name": "post_ticket_update_flow",    "definition": {      "StartAt": "LoadSettings",      "States": {        "LoadSettings": {          "Type": "Action",          "ActionName": "zis:common:action:LoadConfig",          "Parameters": {            "scope": "slackNotification"          },          "ResultPath": "$.settings",          "Next": "IsTicketPriorityMatch"        },        "IsTicketPriorityMatch": {          "Type": "Choice",          "Choices": [            {              "Variable": "$.input.ticket_event.ticket.priority",              "StringEqualsPath": "$.settings.priority",              "Next": "GetZendeskTicket"            }          ],          "Default": "Done"        },        "GetZendeskTicket": {          "Type": "Action",          "ActionName": "zis:{subdomain}_zendesk_to_slack:action:get_zendesk_ticket_http_action",          "Parameters": {            "token.$": "$.connections.zendesk.access_token",            "ticketId.$": "$"          },          "ResultPath": "$.retrievedTicket",          "Next": "PostMessageToSlack"        },        "PostMessageToSlack": {          "Type": "Action",          "ActionName": "zis:{subdomain}_zendesk_to_slack:action:post_message_to_slack_http_action",          "Parameters": {            "token.$": "$.connections.slack.access_token",            "channelId.$": "$",            "ticketSubject.$": "$.retrievedTicket.ticket.subject",            "ticketDescription.$": "$.retrievedTicket.ticket.description",            "title.$": "*_New {{$.input.ticket_event.ticket.priority}} ticket #{{$}}_*",            "link.$": "<{{$.retrievedTicket.ticket.url}}|View in Zendesk>"          },          "ResultPath": "$.postMessageResponse",          "Next": "Done"        },        "Done": {          "Type": "Succeed"        }      }    }  }},

The flow object contains five states:

  • LoadSettings loads the configuration saved using your Zendesk app's config UI. The state makes the config's data available to other states as the "$.settings" object.

  • IsTicketPriorityMatch is a choice state. The state compares two properties:

    • The ticket priority from a Ticket Created event
    • The ticket priority from the config settings

    If the priorities are the same, the flow moves to the GetZendeskTicket state. Otherwise, the flow moves to the Done state.

  • GetZendeskTicket uses the custom get_zendesk_ticket_http_action action. The state gets ticket data for the Ticket Created event. The state makes this ticket data available to following states as the "$.retrievedTicket" object.

  • PostMessageToSlack uses the custom post_message_to_slack_http_action action. The state posts a message to the Slack channel provided in the config settings. The message contains ticket data from the Ticket Created event and the "$.retrievedTicket" object.

  • The Done state ends the flow in a successful state.

Define the job spec

Next, define a job spec for the bundle. The job spec runs the bundle's ZIS flow when it detects a Ticket Created event.

In zendesk-to-slack-bundle.json, replace the placeholder line in the post_message_to_slack_on_ticket_created_job_spec with the following JSON. In the JSON, replace {subdomain} with your Zendesk subdomain.

"post_message_to_slack_on_ticket_creation_job_spec": {  "type": "ZIS::JobSpec",  "properties": {    "name": "post_message_to_slack_on_ticket_creation_job_spec",    "event_source": "support",    "event_type": "ticket.TicketCreated",    "flow_name": "zis:{subdomain}_zendesk_to_slack:flow:post_ticket_update_flow"  }}

Code complete

Your actions, flow, and job spec now make up a complete ZIS bundle. Your zendesk-to-slack-bundle.json file should look as follows:

{  "name": "Post Slack message on ticket creation",  "description": "Posts ticket data to Slack channel on ticket creation",  "zis_template_version": "2019-10-14",  "resources": {    "get_zendesk_ticket_http_action": {      "type": "ZIS::Action::Http",      "properties": {        "name": "get_zendesk_ticket_http_action",        "definition": {          "method": "GET",          "path": "/api/v2/tickets/{{$.ticketId}}.json",          "headers": [            {              "key": "Authorization",              "value": "Bearer {{$.token}}"            }          ]        }      }    },    "post_message_to_slack_http_action": {      "type": "ZIS::Action::Http",      "properties": {        "name": "post_message_to_slack_http_action",        "definition": {          "method": "POST",          "url": "",          "headers": [            {              "key": "Authorization",              "value": "Bearer {{$.token}}"            }          ],          "requestBody": {            "channel": "{{$.channelId}}",            "blocks": [              {                "type": "section",                "text": {                  "type": "mrkdwn",                  "text": "{{$.title}}"                },                "fields": [                  {                    "type": "mrkdwn",                    "text": "*Subject*"                  },                  {                    "type": "mrkdwn",                    "text": "*Description*"                  },                  {                    "type": "plain_text",                    "text": "{{$.ticketSubject}}"                  },                  {                    "type": "plain_text",                    "text": "{{$.ticketDescription}}"                  }                ]              },              {                "type": "section",                "text": {                  "type": "mrkdwn",                  "text": "{{$.link}}"                }              }            ]          }        }      }    },    "post_ticket_update_flow": {      "type": "ZIS::Flow",      "properties": {        "name": "post_ticket_update_flow",        "definition": {          "StartAt": "LoadSettings",          "States": {            "LoadSettings": {              "Type": "Action",              "ActionName": "zis:common:action:LoadConfig",              "Parameters": {                "scope": "slackNotification"              },              "ResultPath": "$.settings",              "Next": "IsTicketPriorityMatch"            },            "IsTicketPriorityMatch": {              "Type": "Choice",              "Choices": [                {                  "Variable": "$.input.ticket_event.ticket.priority",                  "StringEqualsPath": "$.settings.priority",                  "Next": "GetZendeskTicket"                }              ],              "Default": "Done"            },            "GetZendeskTicket": {              "Type": "Action",              "ActionName": "zis:{subdomain}_zendesk_to_slack:action:get_zendesk_ticket_http_action",              "Parameters": {                "token.$": "$.connections.zendesk.access_token",                "ticketId.$": "$"              },              "ResultPath": "$.retrievedTicket",              "Next": "PostMessageToSlack"            },            "PostMessageToSlack": {              "Type": "Action",              "ActionName": "zis:{subdomain}_zendesk_to_slack:action:post_message_to_slack_http_action",              "Parameters": {                "token.$": "$.connections.slack.access_token",                "channelId.$": "$",                "ticketSubject.$": "$.retrievedTicket.ticket.subject",                "ticketDescription.$": "$.retrievedTicket.ticket.description",                "title.$": "*_New {{$.input.ticket_event.ticket.priority}} ticket #{{$}}_*",                "link.$": "<{{$.retrievedTicket.ticket.url}}|View in Zendesk>"              },              "ResultPath": "$.postMessageResponse",              "Next": "Done"            },            "Done": {              "Type": "Succeed"            }          }        }      }    },    "post_message_to_slack_on_ticket_creation_job_spec": {      "type": "ZIS::JobSpec",      "properties": {        "name": "post_message_to_slack_on_ticket_creation_job_spec",        "event_source": "support",        "event_type": "ticket.TicketCreated",        "flow_name": "zis:{subdomain}_zendesk_to_slack:flow:post_ticket_update_flow"      }    }  }}

Don’t forget to replace {subdomain} with your Zendesk subdomain.

Upload the bundle

After creating your bundle file, you can deploy your integration. First, upload the bundle using the Create or Update Bundle endpoint.

  1. In your command-line interface, navigate to the folder that contains your zendesk-to-slack-bundle.json file.

  2. Run:

    curl -X POST https://{subdomain}{subdomain}_zendesk_to_slack/bundles \  -u {email}:{password} \  -H "Content-Type: application/json" \  -i -d @zendesk-to-slack-bundle.json

    If successful, the request returns an empty 200 response.

Add an "Enable integration" button

Next, you'll add an Enable integration button to your Zendesk app. When an admin clicks the button, the app installs the job spec specified in your bundle. ZIS gets the job spec from the bundle you uploaded.

  1. In your Zendesk app's assets folder, open iframe.html. Add the following HTML directly after the <div> tag containing the Connect to Slack button.

    ...    Connect to Slack  </button></div><div class="c-txt u-mb">  <button id="btnEnableIntegration" class="c-btn c-btn--primary">    Enable integration  </button></div>...
  2. In bootstrap.js, add the following:

    ...  fetchConfig(integrationName);
      // Bind button to install the job spec  document    .getElementById("btnEnableIntegration")    .addEventListener("click", function () {      enableIntegration(integrationName);    });...

    The code binds the Enable integration button to the enableIntegration function.

  3. In the assets folder, create a registry.js file. Paste the following into the file:

    // Install the job spec.function enableIntegration(integrationName) {  const jobSpec =    "zis:" +    integrationName +    ":job_spec:post_message_to_slack_on_ticket_creation_job_spec";
      let request = {    type: "POST",    url:      "/api/services/zis/integrations/" +      integrationName +      "/job_specs/installation",    contentType: "application/json",    data: JSON.stringify([{ name: jobSpec }]),  };
      return client.request(request).then(    function () {      console.log("Integration enabled");      client.invoke("notify", "Integration enabled");    },    function (error) {      console.log("Failed to enable flow: ", response);    }  );}

    The code defines the enableIntegration function. When called, enableIntegration uses the Install JobSpec endpoint to install the job spec defined in your bundle.

  4. In iframe.html, add the following:

    ...<!-- Manage configuration settings --><script type="text/javascript" src="config.js"></script>
    <!-- Enable workflow --><script type="text/javascript" src="registry.js"></script>...
  5. Save iframe.html, bootstrap.js, and registry.js. Then refresh the app.

    Your app now displays an Enable integration button.

Install the job spec

Next, use the Enable Integration button to install the job spec.

In your app, click Enable integration. A success notification is displayed.

Test the integration

To test the integration, create a ticket with the "urgent" priority in Zendesk. To create the ticket, use the Zendesk Agent Workspace or run:

curl -X POST https://{subdomain} \  -u {email_address}:{password} \  -H "Content-Type: application/json" \  -d '  {    "ticket": {      "subject": "My printer is on fire!",      "priority": "urgent",      "status": "open",      "comment": { "body": "The smoke is very colorful." }    }  }'

The integration posts a message in the configured Slack channel.

Congratulations! You've completed this tutorial series. You now have a working integration and Zendesk app.

As a next step, you can install your Zendesk app as a private app if wanted. Refer to Uploading and installing a private app.