↪️Integrating Webhooks

Webhooks are currently the best and fastest way to access classification results for requests sent to Unitary's API - they allow you to act on the results as soon as they are available.

Whenever a job finishes processing, a POST request will be sent to the callback_url parameter provided in the body of the API request.

For easy experimentation, several free services can help you debug webhooks easily. Some of these include: RequestBin, Webhook Inbox and InspectBin.

Example: A simple webhook receiver (Python)

The following Python (FastAPI) server will listen for results and log the jobs that have at least one policy category detected.

from fastapi import Request, FastAPI

app = FastAPI()

async def get_body(request: Request):
    result = await request.json()
    if result["is_error"] is False:
        policy_categories = result["result"].get("policy_categories", [])
        if len(policy_categories) > 0:
            print("Policy category detected")

    return "OK"

Response Validation

The response payload is validated using the X-Hub-Signature-256 header which requires base64 formatting.

The payload is hashed using the HMAC-SHA256 algorithm with a secret key (or cryptographic key) that is set up following the instructions atSetting up a secret key. If no key is set, the header in question will not be present.

For the error JSON presented above and the secret set as "test_key", the header will have the value "hYOb+67Z15TMwHO678Yd2fQykmBDwPdBa+O7FnXOqOY=".

This online generator uses the same algorithm. Please be aware of the whitespaces inside the payload.

Example: Validating webhooks (Python)

In the following example, the payload is validated according to the secret key that has previously been set up.

from fastapi import Header, Request, FastAPI

import base64
import hashlib
import hmac

SECRET_KEY = "test_key"

app = FastAPI()

def compute_signature(payload: str, secret_key: str) -> str:
    digest = hmac.new(
        secret_key.encode(), msg=payload.encode("utf-8"), digestmod=hashlib.sha256
    signature = base64.b64encode(digest).decode()
    return signature

async def get_body(request: Request, x_hub_signature_256: str | None = Header(default=None)):
    result = await request.body()
    if compute_signature(result.decode(), SECRET_KEY) == x_hub_signature_256:
        print("Body validated")
        # .. do processing
        print("Body not validated")
    return "OK"

Setting up a secret key

You will receive a secret key from Unitary when you get your API Key.

If you still don't have a key or need a new one, please write to support@unitary.ai

Webhook request payloads

  "data": {
    "job_id": "{JOB_ID}",
    "callback_url": "{CALLBACK_URL}",
    "is_error": false,
    "result": {
      "policy_categories": [
          "name": "ARMS_AMMO",
          "description": "Arms & Ammunition",
          "risk_level": "HIGH"
          "name": "DEATH_INJURY_MILITARY",
          "description": "Death, Injury or Military Conflict",
          "risk_level": "MEDIUM"
      "metadata": {
        "width": 720,
        "height": 480,
        "fps": 30,
        "duration": 11.6,
        "seconds_processed": 11
      "url": "{RESOURCE_URL}"

Custom payload data

The callback_url is used as is, so you can append custom data to the request by controlling the query parameters.

For example, you can send back the original file name or the time of the original request.

// with filename
"callback_url": "your.callback.server/?filename=original_file_name.mp4"

// with timestamp
"callback_url": "your.callback.server/?current_timestamp=1687864622"

// with filename and timestamp
"callback_url": "your.callback.server/?current_timestamp=1687864622&filename=original_file_name.mp4"

The additional data will also be part of the payload body.

Last updated