# 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.

{% hint style="info" %}
For easy experimentation, several free services can help you debug webhooks easily. Some of these include: RequestBin, Webhook Inbox and InspectBin.
{% endhint %}

<details>

<summary>Example: A simple webhook receiver (Python)</summary>

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

```python
from fastapi import Request, FastAPI

app = FastAPI()

@app.post("/results")
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"
```

</details>

## 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 at[#setting-up-a-secret-key](#setting-up-a-secret-key "mention"). 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](https://www.devglan.com/online-tools/hmac-sha256-online) online generator uses the same algorithm. Please be aware of the whitespaces inside the payload.

<details>

<summary>Example: Validating webhooks (Python)</summary>

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

```python
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
    ).digest()
    signature = base64.b64encode(digest).decode()
    return signature

@app.post("/results")
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
    else:
        print("Body not validated")
    return "OK"
```

</details>

## 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](mailto:sales@unitary.ai)

## Webhook request payloads

{% tabs %}
{% tab title="Success" %}

```json
{
  "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}"
    }
  }
}
```

{% endtab %}

{% tab title="Error" %}

```json
{
  "data": {
    "job_id": "{JOB_ID}",
    "callback_url": "{CALLBACK_URL}",
    "is_error": true,
    "result": {
      "error": "Failed to decode video."
    }
  }
}
```

{% endtab %}
{% endtabs %}

## 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.

```json
// 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.
