The first step in creating a channel is to build an integration service for the channel.

An integration service is a web application that you build to connect Zendesk Support to the external system where customers ask for help. An integration service acts as a control center between an external system and Zendesk Support. The service helps the two communicate.

In this tutorial series, the external system is a Gather community, which is a separate product from Zendesk Support. In this article, you'll create an integration service for your new community channel.

Topics covered:

This is the second part of a project to build a channel from scratch:

Disclaimer: Zendesk provides this article for demonstration and instructional purposes only. The channel developed in the article should not be used in a production environment. Zendesk does not provide support for developing a channel. Please post any issue in the comments section below or search for a solution online.

Setting up the web application

For the sake of simplicity, the integration service in this tutorial is built using Bottle, a minimalist Python web framework. To learn more about how Bottle works, see the Bottle docs. The framework includes a local web server you can use for testing.

  1. Create a project folder in your user path.

    Example:

    $ jdoe/projects/channel_tutorial

    Avoid creating the folder in a shared network path such as Google Drive or Microsoft OneDrive. The Python interpreter can have trouble finding modules in these paths.

  2. Create a file named service.py in your project folder.

  3. Paste the following code into the file:

    import osimport jsonimport urllib.parsefrom pathlib import Path
    from bottle import route, run, request, response, template
    @route('/')def show_home():    return '<p>Integration service for a Zendesk Support community channel</p>'
    if os.environ.get('APP_LOCATION') == 'heroku':    run(host="0.0.0.0", port=int(os.environ.get("PORT", 5000)))else:    run(debug=True)

    The route returns the HTML string when a browser requests the page at the url root ('/').

    The last four lines tell the app how to run based on its location. If it's not hosted on Heroku, the app runs on Bottle's local web server. You'll host the app on Heroku in Deploying the channel.

  4. Save the file.

  5. In your command line interface, navigate to your project folder and run the web application using Bottle's built-in local server:

    $ python3 service.py

    After starting, the application runs at http://localhost:8080.

  6. Paste the following url in your browser or in an API client like Postman and run it.

    http://localhost:8080/

    You can also run it as a cURL request in a command line interface:

    curl http://localhost:8080/

    The page should contain the following line:

    Integration service for a Zendesk Support community channel

Channeling new community posts to Support agents

After setting up the web application, you can start adding API endpoints to channel data from the community to Zendesk Support and back.

Every two minutes or so, Zendesk will send an HTTP request to your integration service asking it to pull any new posts from your community and forward them to Zendesk. These requests are known as pull requests or simply pulls.

The pull requests from Zendesk are HTTP requests. That means you need to build an API endpoint for your service to accept, process, and respond to these requests.

In this section, you add an API endpoint called Pull New Posts to handle pull requests from Zendesk. You'll add the processing logic later.

To add the Pull New Posts endpoint

Paste the following endpoint into the service.py file in your project folder and save the file.

@route('/channels/community/pull', method='POST')def pull_new_posts():
    # get topic id    metadata = request.forms['metadata']    metadata = urllib.parse.unquote(metadata)   # URL-decode the JSON value    metadata = json.loads(metadata)             # convert decoded JSON to dict    topic_id = metadata['topic_id']
    # get start time    start_time = ''    if request.forms['state']:        # URL-decode the JSON value        state = urllib.parse.unquote(request.forms['state'])        state = json.loads(state)                # convert decoded JSON to dict        start_time = state['start_time']
    # get new community posts    new_posts = community.get_new_posts(topic_id, start_time)    if 'error' in new_posts:        print(new_posts['error']['text'])    else:        # send new posts to Zendesk        response.status = 201        response.headers['Content-Type'] = 'application/json'        return json.dumps(new_posts)

How it works

The endpoint defines a route for the /channels/community/pull url path. Pull requests from Zendesk are POST requests so the endpoint specifies that it only accepts POST requests:

@route('/channels/community/pull', method='POST')

Next, the pull_new_posts() function defines the actions the endpoint takes.

It starts by getting the topic id from the metadata parameter from the form data that Zendesk sends with the request:

metadata = request.forms['metadata']    metadata = urllib.parse.unquote(metadata)   # URL-decode the JSON value    metadata = json.loads(metadata)             # convert decoded JSON to dict    topic_id = metadata['topic_id']

Zendesk sends information in pull requests as form data. You can retrieve this data from the Bottle framework's request.forms dictionary. Example:

metadata = request.forms['metadata']

The metadata parameter is your own data, which you'll store in Zendesk later in this tutorial. For now, you just need to know that it's where you'll store the id of the community topic you want to track. Example:

metadata = {'topic_id': '234567'}

Zendesk lets you store channel data in its infrastructure so long as the data is stringified. Your initial metadata parameter will be a Python dictionary that specifies a community topic id. To save it in Zendesk, you'll convert the dict to JSON then URL-encode the JSON. The restore the value from the pull request, you reverse the operation: URL-decode the JSON then convert the JSON to a dictionary.

Next, the function determines the start time to use to check for new posts. In case a start time hasn't been set yet, it starts by setting it as an empty string:

start_time = ''

If a start time has been set, you get it from the state parameter from the form data that Zendesk sends with the request:

if request.forms['state']:    # URL-decode the JSON value    state = urllib.parse.unquote(request.forms['state'])    state = json.loads(state)       # convert decoded JSON to dict    start_time = state['start_time']

Like metadata, the state parameter is data you define yourself, which you'll do later in this tutorial. The state property is like a sticky note for tracking data that changes often. Later you'll use it to store a start time, which could change from pull request to pull request. Example:

state = {'start_time': '2020-08-10T16:30:19.036629+00:00'}

Your initial state parameter will be a Python dictionary that specifies a datetime string. To save it in Zendesk, you'll convert it to JSON then URL-encode the JSON. The restore the value from the pull request, the function URL-decodes the JSON then converts the JSON to a dictionary.

Next, the function hands off the task of getting community posts to a function called get_new_posts() in a module called community:

new_posts = community.get_new_posts(topic_id, start_time)

The get_new_posts() function uses the Help Center API to get the posts. You'll define the module and function in the next article but for now you just need to know the function takes two arguments:

  • topic_id - the community topic where customers add posts to ask for help
  • start_time - a datetime to start looking for new posts

If the Help Center API request is unsuccessful, the endpoint responds with information about the error:

if 'error' in new_posts:    print(new_posts['error']['text'])

Otherwise, the endpoint sends the new posts in the response to Zendesk:

else:    response.status = 201    response.headers['Content-Type'] = 'application/json'    return json.dumps(new_posts)

The new_posts variable consists of an external_resources parameter with the new posts and an state parameter with an updated start_time to store in Zendesk.

That's it. Every two minutes or so Zendesk sends a pull request to the endpoint asking for any new posts. In response, your endpoint gets the new posts and sends them to Zendesk. When Zendesk receives the posts, it creates a ticket for each one so agents in Support can answer them.

You'll tell Zendesk about the endpoint in an integration manifest file you'll create later in Creating a channel manifest.

Channeling agent comments back to community posts

After receiving the new community posts from your integration service, Zendesk converts them to tickets for agents to address.

When an agent in Zendesk Support adds a comment to a ticket created from a community post, Zendesk sends the comment to your integration service, asking it to add the comment to the originating post. These requests are known as channelback requests.

The channelback requests from Zendesk are HTTP requests. That means you must build an API endpoint for your service to accept, process, and respond to these requests.

In this section, you add an API endpoint called Channelback Ticket Comment to handle channelback requests from Zendesk. You'll add the processing logic later.

To add the Channelback Ticket Comment endpoint

Paste the following endpoint into the service.py file in your project folder and save the file.

@route('/channels/community/channelback', method='POST')def channelback_ticket_comment():
    # get comment from channelback request    post_id = request.forms['parent_id']    comment = request.forms['message']
    # add the comment to the originating post    external_id = community.create_post_comment(post_id, comment)    if 'error' in external_id:        response.status = 500        return
    # send comment id in response    response.status = 200    response.headers['Content-Type'] = 'application/json'    return json.dumps(external_id)

How it works

The endpoint defines a route for the /channels/community/channelback url path. Channelback requests from Zendesk are POST requests so the endpoint specifies that it only accepts POST requests:

@route('/channels/community/channelback', method='POST')

The channelback_ticket_comment() function does the heavy lifting for the endpoint. It starts by retrieving the agent comment and the post id sent in the channelback request:

def channelback_ticket_comment():    post_id = request.forms['parent_id']    comment = request.forms['message']

Zendesk sends the information as form data. You can retrieve this data from the Bottle framework's request.forms dictionary.

The parent_id property is the id of the originating community post. Zendesk received the id from the Pull New Posts endpoint in your integration service. You'll set this value in the community module you'll build in the next article.

The message property contains the agent's ticket reply.

The Channelback Ticket Comment endpoint hands off the task of creating the post comment to a function called create_post_comment() in the community module:

external_id = community.create_post_comment(post_id, comment)

The create_post_comment() function uses the Help Center API to create the comment. You'll define the module and function in the next article.

If the Help Center API request is unsuccessful, the endpoint responds to the Zendesk channelback request with a 500 status error:

if 'error' in external_id:    response.status = 500            return

Zendesk Support recognizes a status code in the 500-599 range as a failure to correctly process the channelback request and informs the Support agent.

If the comment is successfully created, the function returns the id of the new comment, which is assigned to the external_id variable. The endpoint sends the value in the response:

response.status = 200response.headers['Content-Type'] = 'application/json'return json.dumps(external_id)

The channelback request from Zendesk expects a unique identifier of the resource created -- in this case, a comment id. See Response format.

That's it. When the agent adds a comment to a ticket created from a post, Zendesk sends the comment in a channelback request to your endpoint. The endpoint in turn adds the comment to the original community post, closing the loop.

Next part: Connecting to the community