Webhooks let your application react to events happening in other services without constantly asking them for updates.

Let’s see OpenAI webhooks in action. Imagine you’re building a customer support bot that uses OpenAI’s models. When a new customer message comes in, your backend receives it. You then send it to OpenAI for processing, perhaps to generate a response. Once OpenAI finishes, it needs to tell your backend that the response is ready. This is where webhooks shine.

Here’s a simplified flow:

  1. User Action: A customer types a message into your app.
  2. Your Backend: Receives the message, processes it, and sends a request to OpenAI’s API (e.g., for text generation).
  3. OpenAI: Processes the request.
  4. OpenAI Webhook: When the task is complete, OpenAI sends an HTTP POST request to a pre-configured URL in your application. This request contains the results.
  5. Your Backend (Webhook Handler): Receives the POST request, verifies it’s from OpenAI, and then uses the data (e.g., the generated response) to update your app, send a notification, or trigger another action.

The "magic" is that OpenAI initiates the communication to you when something interesting happens on its end, rather than your app polling OpenAI repeatedly to check for results. This is far more efficient and enables near real-time reactions.

To set this up, you’ll need two main pieces:

  1. A Publicly Accessible Endpoint on Your Server: This is the URL that OpenAI will send its HTTP POST requests to. It must be accessible from the internet. For local development, tools like ngrok are invaluable for creating temporary public URLs that tunnel to your local machine.
  2. OpenAI Configuration: In your OpenAI account settings or via their API configuration, you’ll specify the URL of your endpoint and the events you want to receive notifications for.

Let’s look at a hypothetical OpenAI API call where you might want a webhook. Suppose you’re using the Assistants API to run a complex analysis.

# Example: Submitting a run to OpenAI Assistants API
from openai import OpenAI

client = OpenAI(api_key="YOUR_API_KEY")

run = client.beta.threads.runs.create(
  thread_id="thread_xxxxxxxxxxxx",
  assistant_id="asst_yyyyyyyyyyyy",
  instructions="Please summarize the user's conversation."
)
print(f"Run created: {run.id}")

When this run completes, OpenAI can send a webhook event. You’d configure your webhook URL in the OpenAI platform. Let’s say your public URL is https://your-app.com/api/webhooks/openai. You’d tell OpenAI to send thread.run.completed events to this URL.

Your webhook handler on https://your-app.com/api/webhooks/openai might look something like this (using Flask for demonstration):

from flask import Flask, request, jsonify
import hmac
import hashlib
import os

app = Flask(__name__)

# IMPORTANT: Store your webhook signing secret securely
# You can find this in your OpenAI API settings.
OPENAI_WEBHOOK_SECRET = os.environ.get("OPENAI_WEBHOOK_SECRET")

@app.route("/api/webhooks/openai", methods=["POST"])
def openai_webhook():
    signature = request.headers.get("openai-signature")
    payload = request.data

    if not signature or not OPENAI_WEBHOOK_SECRET:
        return jsonify({"error": "Missing signature or secret"}), 400

    # Verify the signature to ensure the request is from OpenAI
    try:
        hmac.compare_digest(
            hmac.new(
                OPENAI_WEBHOOK_SECRET.encode("utf-8"),
                payload,
                hashlib.sha256,
            ).hexdigest(),
            signature,
        )
    except Exception as e:
        print(f"Signature verification failed: {e}")
        return jsonify({"error": "Invalid signature"}), 400

    data = request.json
    event_type = data.get("event")

    if event_type == "thread.run.completed":
        run_id = data["data"]["id"]
        thread_id = data["data"]["thread_id"]
        print(f"Run {run_id} for thread {thread_id} completed!")
        # Now you can fetch messages for this run and process them
        # e.g., client.beta.threads.messages.list(thread_id=thread_id)
        return jsonify({"success": True})
    elif event_type == "thread.run.failed":
        run_id = data["data"]["id"]
        thread_id = data["data"]["thread_id"]
        print(f"Run {run_id} for thread {thread_id} failed.")
        return jsonify({"success": True})
    else:
        print(f"Received unhandled event type: {event_type}")
        return jsonify({"success": True})

if __name__ == "__main__":
    # For local testing with ngrok:
    # ngrok http 5000
    # Then set OPENAI_WEBHOOK_SECRET environment variable
    # And update your OpenAI webhook URL to the ngrok URL + /api/webhooks/openai
    app.run(port=5000, debug=True)

The most surprising true thing about webhooks is that they represent a fundamental shift in how distributed systems communicate: from a pull model (where one system constantly asks another "Are you done yet?") to a push model (where the system that produces the result tells the consumer when it’s ready). This is crucial for efficiency, scalability, and responsiveness, especially in asynchronous operations like those involving large language models.

When you receive an event, the event field in the JSON payload tells you what happened. Common events for the Assistants API include thread.run.completed, thread.run.failed, and thread.run.requires_action (which is critical for function calling).

The data field contains the specific details of the event. For a thread.run.completed event, data will include information about the run itself, and you’ll typically then need to fetch the messages from that thread to get the actual output from the assistant.

You’ll encounter challenges with webhook security. The openai-signature header is generated using a secret key that only you and OpenAI should know. This allows your server to verify that the incoming request truly originated from OpenAI and hasn’t been tampered with. Always compare digests using hmac.compare_digest for security.

The next step is to handle thread.run.requires_action events to implement function calling, allowing your assistant to interact with external tools.

Want structured learning?

Take the full Openai-api course →