Webhook vs API: How can you use them?

Recently, I was asked what was the difference between Webhooks and APIs. This was a question I also had a few years ago when I started programming. In this article, I will briefly explain what they are and give a simple tutorial on how you can use them.

What is an API?

API stands for Application Programming Interface. APIs allow applications to talk with each other via a common communication method. There are a lot of different API architectural styles such as REST, SOAP, GraphQL and gRPC. With most APIs, there’s a request followed by a response.

For example, a restaurant might have an application that would make an API request to their server and obtain a list of menu items in the response, then display it for their users. A lot applications out there provide public APIs that you can be use in your personal projects such as YouTube Data API and Google Map API.

What is a Webhook?

Unlike APIs, Webhook is simply an HTTP POST request that is triggered automatically when an event occurs. Basically, webhooks are “user-defined callbacks”.

For example, an application could provide a webhook that will get triggered by another application when new data is received (callback) instead of sending requests at fixed interval to fetch new data (polling).

Tutorial

Send Message Using Slack API with Slack Bot

Slack provides a complete list of REST API methods available to bots. We are going to use the users.list method to list available users and chat.postMessage method to send a message to a user or channel.

1. Navigate to the Custom Integrations page of your Workspace https://<your-workspace-name>.slack.com/apps/manage/custom-integrations and select Bots Slack Custom Integrations Bots

2. Choose a name and add the bot integration. Add bot

3. Save the API Token, we will use it later in Slack API requests for authentication. API token

4. Let’s try out the users.list method using an API client like Postman and click on code to generate code: List Users

# slack-api.py
import requests, json

base_url = "https://slack.com/api"

payload={}
headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Authorization': 'Bearer [your Slack Bot API Token]'
}

# Make GET request and receive response
response = requests.request("GET", f"{base_url}/users.list", headers=headers, data=payload)

# Convert response to a Dict object
response_json = json.loads(response.text)

# Find user by username
username = 'yueh.liu'
user = next((member for member in response_json['members'] if member['name'] == username), None)

# Make sure the user exists
if not user:
    raise Exception(f'User [{username}] was not found')

# Save the user_id
user_id = user['id']

5. Now that we have the User ID, we can try sending a message to that user! We can repeat the previous step with the chat.postMessage method. Make sure to change the request method to POST. Send message to user

You should receive a message like this on Slack
ua-bot

The updated code should look something like this:

# slack-api.py
import requests, json

base_url = "https://slack.com/api"

payload={}
headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Authorization': 'Bearer [your Slack Bot API Token]'
}

# Make GET request and receive response
response = requests.request("GET", f"{base_url}/users.list", headers=headers, data=payload)

# Convert response to a Dict object
response_json = json.loads(response.text)

# Find user by username
username = 'yueh.liu'
user = next((member for member in response_json['members'] if member['name'] == username), None)

# Make sure the user exists
if not user:
    raise Exception(f'User [{username}] was not found')

# Save the user_id
user_id = user['id']

# Set the parameters such as the channel ID (user ID in our case), username for the bot, text message, icon url, etc
# You can also send a JSON payload instead of query parameters, but you would need to change the 'Content-Type' to 'application/json' in the headers
params = f"channel={user_id}&text=Hello Yueh!&username=ua-bot&icon_url=https://some-url-link.jpg"

# Make POST request and receive response
response = requests.request("POST", f"{base_url}/chat.postMessage?{params}", headers=headers, data=payload)

print(response.text)

Send Message Using Slack Incoming Webhooks

Incoming Webhooks are a simple way to post messages from external sources into Slack. They make use of normal HTTP requests with a JSON payload, which includes the message and a few other optional details described later.

For this example, we are going to create a Web Server and integrate an Incoming Webhook. We will trigger the webhook automatically to send a message to a user on Slack whenever the server receives a message.

1. Navigate to the Custom Integrations page of your Workspace https://<your-workspace-name>.slack.com/apps/manage/custom-integrations and select Incoming WebHooks Slack Custom Integrations Incoming Webhooks

2. Choose a channel (or user) to post your messages and add the webhook add-webhook

You should see a message like this on Slack added-integration

3. Save the Webhook url webhook-url

4. Since webhooks work best as callback from a server, let’s write a simple HTTP server that runs on localhost and port 3000. The web server will receive a message on /message path and read the message content from the payload.

# server.py
from http.server import BaseHTTPRequestHandler, HTTPServer
import json

# Define a custom Request Handler
class CustomHandler(BaseHTTPRequestHandler):
    def set_response(self, code, byte_message):
        self.send_response(code)
        self.send_header("Content-type", "text/plain")
        self.end_headers()
        self.wfile.write(byte_message)

    def do_GET(self):
        if self.path == "/":
            self.set_response(200, "I'm alive!!!\n".encode())
            self.wfile.write()
        else:
            self.send_error(404)
        return

    def do_POST(self):
        if self.path == "/message":
            # Get payload
            content_length = int(self.headers["Content-Length"])
            encoded_data = self.rfile.read(content_length)
            data = json.loads(encoded_data.decode("utf-8"))

            if not "message" in data and not data['message']:
                self.send_error(400, "Bad Request", '"message" must be in the payload')
                return

            self.set_response(200, f"Received message: \"{data['message']}\"\n".encode())
        else:
            self.send_error(404)
        return

# Initialize an HTTP server
port = 3000
address = ("", port)
server = HTTPServer(address, CustomHandler)

# Start your server
print(f"Starting Web server on localhost:{port}..")
server.serve_forever()

5. Check if server is running
server-get

Let’s try sending a message
server-post

6. Now that the server is running, let’s integrate the webhook into the code!

# server.py
from http.server import BaseHTTPRequestHandler, HTTPServer
import json, requests

# Define a custom Request Handler
class CustomHandler(BaseHTTPRequestHandler):
    def set_response(self, code, byte_message):
        self.send_response(code)
        self.send_header("Content-type", "text/plain")
        self.end_headers()
        self.wfile.write(byte_message)

    def do_GET(self):
        if self.path == "/":
            self.set_response(200, "I'm alive!!!\n".encode())
            self.wfile.write()
        else:
            self.send_error(404)
        return

    def do_POST(self):
        if self.path == "/message":
            # Get payload
            content_length = int(self.headers["Content-Length"])
            encoded_data = self.rfile.read(content_length)
            data = json.loads(encoded_data.decode("utf-8"))

            if not "message" in data and not data['message']:
                self.send_error(400, "Bad Request", '"message" must be in the payload')
                return

            self.set_response(200, f"Received message: \"{data['message']}\"\n".encode())

            # Trigger the Webhook (make POST request) and we can ignore the response and failure
            try:
                webhook_url = "[Your Slack Webhook Url]"
                headers = { 'Content-Type': 'application/json' }
                payload = "{ \"text\": \"Your server received the following message:\n\n" + data['message'] + "\" }"
                requests.request("POST", webhook_url, headers=headers, data=payload)
            except Exception:
                pass
        else:
            self.send_error(404)
        return

# Initialize an HTTP server
port = 3000
address = ("", port)
server = HTTPServer(address, CustomHandler)

# Start your server
print(f"Starting Web server on localhost:{port}..")
server.serve_forever()

7. Once updated, we can re-send the same message as earlier and you should receive a message like this on Slack:
ua-webhook

Conclusion

An API is a communication method used by applications to talk with other applications. Webhook is a POST request that is triggered automatically when an event happens. Basically, APIs are request-based while webhooks are event-based.

🐢

References