How Cronofy built the Zendesk Calendar Connector

There needed to be a better way for agents in Zendesk to track the tasks that had been assigned them. Using the Cronofy calendar API we created an app that keeps those assignments synchronized with the agent's calendar.

This post shares our learnings from interacting with the Zendesk API, hopefully to help you with your own Zendesk project.

The Ruby source code for our integration is also available.

When a Ticket is a Task

Tickets are the core entity in the Zendesk domain. Tasks are just Tickets of type : 'task'. This gives them an additional attribute due_at.

We also need to track the status of the ticket to know when it's 'solved'. No need to track a Task if it's solved :)

The last attribute of interest is assignee_id, i.e. the id of the Zendesk user who is currently assigned the ticket.

This means we're looking at monitoring a state transition on all Tickets when

  • the type changes to/from 'task'
  • the assignee_id changes for Tasks
  • the due_at changes
  • the status changes to 'solved'
  • they are created or delete

From this we can decide whether we need to create, update or delete calendar events in the agent's calendars. A scary list, but there's some magic that can help that I will cover below.

But first, how do we know when these occur?

Triggers and Targets

The dark ages of polling web APIs for changes are increasingly behind us. Zendesk has rich support for notifying systems of changes. What's different about them is that this is part of a richer automation system that isn't limited to APIs.

As a developer trying to navigate this for the first time it wasn't what I was used to so took a little bit of navigating.

Triggers describe an action to be performed when a Ticket is created or updated and certain conditions are met. They comprise a rich set of condition filters and a series of actions that should be performed.

The actions are field, value pairs. The typically way you use this is to update a Ticket field when something else happens. However they also support a special field name of 'notification_target' which is used to action Target. You set the field value on the action to an array of the target_id and some optional data.

{
  "actions": [
    {"field": "notification_target", "value": ["32322", "Ticket {{ticket.id}} has been updated."]}
  ]
}

These are the connecting elements between the Triggers and our application.

Targets are pointers to web end points that are set up to receive notifications. Zendesk has kindly built ready made templates for popular services like Twilio and Twitter but also allows you to specify your own. Which is what we need to do.

Bootstrapping Triggers and Targets

Now, you can use the admin interface in Zendesk to set this all up but this is completely unnecessary. The Zendesk API supports all of these operations so you can do it all in code. Especially useful as you need to generate the Target then use the assigned id in the Trigger.

Note of caution, from a permissions perspective you will need perform this under the auspice of an admin user. Standard users can't do this.

Our Target looks something like this:

{
  "type" : "url_target",
  "title" : "Calendar Connector Target",
  "target_url" : "https://zendesk.cronofy.com/webhooks/zendesk/#{user_zendesk_subdomain}",
  "attribute" : "message",
  "method" : "post"
}

and the corresponding Trigger looks something like this:

{
  "title" : "Calendar",
  "actions" : [
    {
      "field" : "notification_target",
      "value": [ target_id, "Ticket {{ticket.id}}" ]
    }
  ],
  "conditions" : {
    "all" : [],
    "any" : [
      {
        "field" : "update_type",
        "operator" : "is",
        "value" : "Create"
      },
      {
        "field" : "update_type",
        "operator" : "is",
        "value" : "Change"
      }
    ]
  }
}

Note the {{ticket.id}} will include the Zendesk field values.

Tracking changes and keeping in sync

So we can be notified of changes when they happen so our attention can turn to mapping those state transitions and manipulating calendars.

Trying to be too precise about deciding what has changed is a recipe for missing things. We could in theory only sync Tickets when we receive a notification about them having changed. But what happens however if our endpoint is down or indeed Zendesk notifications are down?

In both of those scenarios, we'd need a way of resyncing to catch up, as well as a way of performing an initial synch when the user first signs up.

The most robust approach is to treat the push notifications as a flag that something has changed. Our application then retrieves any pending changes and syncs those Tickets.

The Zendesk Search endpoint provides us a means of doing just that by searching for type:ticket updated_at>={last_modified_at}.

The last_modified_at is the last time our system searched for changes so we can be sure we don't miss anything. For the initial sync, we just omit the last_modified.

This is when it should get messy

We have our candidates for change so now need to decide on the operation to be performed. We're actually going to cheat massively here and leverage the idempotency of the Cronofy API.

This removes the need of this app to track the state transitions and just decide based on the current state of the Ticket, what operation should it perform. Cronofy handles whether or not to actually perform that action on the user's calendar.

For example, deciding whether to delete a calendar event is a reasonably involved conditional.

event_deleted = task.status == 'solved' ||
                task.type != 'task' ||
                task.due_at.nil? ||
                task.assignee_id != user.zendesk_user_id.to_i

If nothing has changed on the Ticket that matches, we'll still be sending a DELETE to Cronofy but nothing will happen on the user's calendar if it's either a) never been put there or b) already been deleted.

What this means for your project

There's a lot more we could talk about with this app but I wanted to stay focused on how, as a developer, you should approach receiving notifications from Zendesk to power your integration.

If you want to learn more about the synchronization between distributed systems and some of the patterns you can use, I covered a lot of this in a post about how we built the Evernote Calendar Connector.

The future of the Internet rests in our ability as developers to connect all of the systems our users utilise. There are challenges in doing so but tooling and support is increasing in quality and we should embrace every opportunity to make it happen.

About the author

Adam Bird is Founder & CEO of Cronofy the single calendar API that enables developers to integrate their apps and services with their users' calendars. Cronofy handles protocols, authorization, privacy and synchronization so you don't have to.