This tutorial describes how to create a simple webhook to receive notifications for specific events in your help center, such as when an article is published or unpublished.

Disclaimer: Zendesk provides this script for illustrative purposes only. Zendesk does not support or guarantee the code in the script. Zendesk also can't provide support for third-party technologies such as Python.

What you'll need

  • A Zendesk Support account with admin access. To get a free account for testing, see Getting a trial or sponsored account for development.

  • ngrok, a tunneling service that can connect apps running on your computer to the internet. The tutorial describes how to install ngrok.

  • Flask, a Python web framework for creating applications with minimal code. If you haven't already installed Flask, you can do so by running the following command in your terminal:

    pip install Flask

Creating the webhook endpoint

In this section, we'll create the webhook endpoint in a Flask web application. In your test directory, create a file named webhook_listener.py and copy and paste the following code into it.

from flask import Flask, request, jsonifyimport logging
# Set up logginglogging.basicConfig(level=logging.INFO)
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])def webhook():    # Handle incoming webhook requests.    data = request.json
    # Check the event type using the correct key    if data['type'] == 'zen:event-type:article.published':        handle_article_published(data)    elif data['type'] == 'zen:event-type:article.unpublished':        handle_article_unpublished(data)    else:        logging.info(f'Received webhook data: {data}')        logging.warning(f'Unknown event type: {data["type"]}')
    return jsonify({'status': 'success'}), 200
def handle_article_published(data):    # Logic for when an article is published, such as posting to a Slack channel    logging.info(f'An article has been published: {data}')
def handle_article_unpublished(data):    # Logic for when an article is unpublished.    logging.info(f'An article has been unpublished: {data}')
if __name__ == '__main__':    app.run(port=5000)

How it works

from flask import Flask, request, jsonify

This line imports necessary components from the Flask framework:

  • Flask is the main class used to create a Flask application instance.
  • request accesses incoming request data, such as form data, query parameters, and JSON payloads.
  • jsonifyconverts Python dictionaries or lists into JSON responses.
@app.route('/webhook', methods=['POST'])def webhook():

@app.route defines a route or endpoint for the application. In this case, it specifies that the /webhook endpoint handles HTTP POST requests. The webhook() function handles incoming requests to this endpoint.

if data['type'] == 'zen:event-type:article.published':        handle_article_published(data)    elif data['type'] == 'zen:event-type:article.unpublished':        handle_article_unpublished(data)    else:        logging.info(f'Received webhook data: {data}')        logging.warning(f'Unknown event type: {data["type"]}')

This conditional block checks the value of the event_type key in the incoming JSON data:

  • If the event_type is "zen:event-type:article.published", it calls the handle_article_published(data) function, passing the received data to it.
  • If the event_type is "zen:event-type:article.unpublished", it calls the handle_article_unpublished(data) function, passing the received data to it.
return jsonify({'status': 'success'}), 200

This line sends a JSON response back to the client (the sender of the webhook request). The 200 status code indicates that the request was processed successfully.

def handle_article_published(data):    # Logic for when an article is published, such as posting to a Slack channel    logging.info(f'An article has been published: {data}')
def handle_article_unpublished(data):    # Logic for when an article is unpublished.    logging.info(f'An article has been unpublished: {data}')

These functions handle the logic specifically for when a document is published or unpublished.

if __name__ == '__main__':    app.run(port=5000)

This block starts the Flask development server and specifies that the application should listen for incoming requests on port 5000.

Making your local server accessible to the internet

The Flask application is designed to serve as a webhook receiver, handling incoming HTTP POST requests at the /webhook endpoint. However, if you only run it locally on your computer, it doesn't have a web address where HTTP requests can be sent.

To make a local Flask application accessible from the internet and capable of receiving HTTP requests, you can use a tunneling service to get a public URL that can forward incoming requests to your local server. This tutorial uses the ngrok tunneling service.

  1. Follow the instructions to install and configure ngrok. You will need to create an ngrok account. You can use the free version.

  2. Run ngrok from your terminal window:

    ngrok http 5000

    Warning: ngrok creates a secure tunnel that exposes your local server running on port 5000 to the internet. To stop the ngrok process, press Ctrl+C in the terminal where ngrok is running. This terminates the ngrok process and closes the tunnel.

  3. Save the forwarding URL that ngrok provides. The forwarding URL looks like https://f871-34-197-61-96.grok-free.app. You'll need it when setting up your webhook in Zendesk.

Configuring the webhook in Zendesk

Now create your webhook.

  1. In Zendesk Admin Center, click Apps and integrations in the sidebar, then select Actions and webhooks > Webhooks.

  2. Click Create webhook.

  3. Select Zendesk events and then select Article published and Article unpublished from the pulldown menu.

  4. Click Next.

  5. Enter a descriptive name for this webhook and then enter the forwarding URL provided by ngrok followed by "/webhook". For example, https://f871-34-197-61-96.grok-free.app/webhook.

  6. Don't select any authentication type for your server.

  7. Click Create webhook.

Checking the webhook listener logs

In a new terminal window, start your webhook listening app.

python3 webhook_listener.py

You should see something similar to this:

(venv) cris.p.bacon@C02FM02QMD6V python3 webhook_listener.py * Serving Flask app 'webhook_listener' * Debug mode: offINFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on http://127.0.0.1:5000INFO:werkzeug:Press CTRL+C to quit

Now edit and publish an article in your Zendesk account. Your webhook listener will list the event. For example:

(venv) cris.p.bacon@C02FM02QMD6V python3 webhook_listener.py * Serving Flask app 'webhook_listener' * Debug mode: offINFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on http://127.0.0.1:5000INFO:werkzeug:Press CTRL+C to quitINFO:root:An article has been published: {'account_id': 10992015, 'detail': {'brand_id': '1500000615802', 'id': '28574842865175'}, 'event': {'author_id': '1509756491122', 'category_id': '1500001910262', 'locale': 'en-us', 'section_id': '1500002567322', 'title': 'sdfgdfgdfg'}, 'id': '01JF8NN5SAS1AHWRTQXT4PTBEK', 'subject': 'zen:article:28574842865175', 'time': '2024-12-16T21:23:15.366282786Z', 'type': 'zen:event-type:article.published', 'zendesk_event_version': '2022-11-06'}INFO:werkzeug:127.0.0.1 - - [16/Dec/2024 13:23:15] "POST /webhook HTTP/1.1" 200 -

Troubleshooting

Troubleshooting 403 errors

If you get a 403 Forbidden error when trying to access your app, another process might be using the 5000 port on your computer. To fix this problem, run the Flask app on a different port as follows.

  1. Shut down both the Flask app and ngrok service with Control+C.
  2. In the webhook_listener.py file, replace app.run(port=5000) with app.run(port=5001). The statement is near the end of the file.
  3. Start ngrok with the new port: ngrok http 5001. Note the new forwarding URL!
  4. Restart the Flask app.
  5. Update the webhook in Zendesk with the new forwarding URL.

Troubleshooting the webhook

If your webhook is not revealing any changes, test your webhook manually using Postman or curl to simulate a webhook event. For example:

curl -X POST https://f871-34-197-61-96.grok-free.app/webhook -H "Content-Type: application/json" -d '{"event_type": "document_added", "document_id": "123", "title": "Test Document"}'

The app doesn't handle any event type called "document_added", so you should get the "Unknown event type" response.

If your webhook is functioning correctly, you should get a response similar to this:

(venv) cris.p.bacon@C02FM02QMD6V python3 webhook_listener.py * Serving Flask app 'webhook_listener' * Debug mode: offWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on http://127.0.0.1:5000Press CTRL+C to quitINFO:werkzeug:127.0.0.1 - - [19/Nov/2024 13:56:50] "POST /webhook HTTP/1.1" 200 -INFO:root:Received webhook data: {'event_type': 'document_added', 'document_id': '123', 'title': 'Test Document'}WARNING:root:Unknown event type: NoneINFO:werkzeug:127.0.0.1 - - [19/Nov/2024 14:02:25] "POST /webhook HTTP/1.1" 200 -

If your webhook is still not reacting to events, check the following:

  • Ensure that the webhook is configured correctly and that you have subscribed to the correct events.
  • If you have a firewall or security software running on your device, it might be blocking incoming connections to your local server. Temporary disable your firewall or configure it to allow traffic on port 5000.
  • Check that your Flask application does not have errors. Look at the terminal where you started the Flask app for any error messages.