Tutorial: Using webhooks with help center events
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, jsonify
import logging
# Set up logging
logging.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.jsonify
converts 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 thehandle_article_published(data)
function, passing the received data to it. - If the
event_type
is "zen:event-type:article.unpublished", it calls thehandle_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.
-
Follow the instructions to install and configure ngrok. You will need to create an ngrok account. You can use the free version.
-
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.
-
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.
-
In Zendesk Admin Center, click Apps and integrations in the sidebar, then select Actions and webhooks > Webhooks.
-
Click Create webhook.
-
Select Zendesk events and then select Article published and Article unpublished from the pulldown menu.
-
Click Next.
-
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
. -
Don't select any authentication type for your server.
-
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: off
INFO: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:5000
INFO: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: off
INFO: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:5000
INFO:werkzeug:Press CTRL+C to quit
INFO: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.
- Shut down both the Flask app and ngrok service with Control+C.
- In the webhook_listener.py file, replace
app.run(port=5000)
withapp.run(port=5001)
. The statement is near the end of the file. - Start ngrok with the new port:
ngrok http 5001
. Note the new forwarding URL! - Restart the Flask app.
- 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: off
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:5000
Press CTRL+C to quit
INFO: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: None
INFO: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.