> ## Documentation Index
> Fetch the complete documentation index at: https://docs.satyakod.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Signed JSON events for alerts, batches and flagged scans, and how to verify them.

Add an endpoint in **Settings → Integrations → Webhooks**, choose its events, and copy the signing
secret. It is shown once. Satyakod then POSTs one JSON request per event.

![Settings → Integrations → Webhooks: adding an endpoint](https://docs.satyakod.com/images/screenshot-placeholder.svg)

Settings → Integrations → Webhooks: adding an endpoint

## Events

| Type | When |
| - | - |
| `alert.created` | A detection rule opened an alert |
| `alert.updated` | An alert changed status or assignee; `outcome` says what resolving it achieved (`stock_seized`, `listing_removed`, `distributor_action`, `legal_action`, `no_action`) |
| `batch.signed` | A batch was signed and released to print |
| `batch.revoked` | A batch, or a range of its serials, was revoked |
| `scan.flagged` | A scan came back suspicious or counterfeit |

## Request

```http theme={null}
POST /your/endpoint
Content-Type: application/json
Satyakod-Event: scan.flagged
Satyakod-Delivery: evt_5f1c…
Satyakod-Signature: t=1792473600,v1=2c4f…

{"id":"evt_5f1c…","type":"scan.flagged","createdAt":"2026-10-20T08:00:00Z","workspaceId":"ws_…",
 "data":{"verdict":"counterfeit","serial":"…","batch":"B-1052","district":"Pune", …}}
```

`data` uses the same field names as the API (camelCase). Alerts and batches include a `url` to
open them in the dashboard.

## Verifying the signature

`v1` is the hex HMAC-SHA256, keyed with your signing secret, of `"<t>.<raw request body>"`.
Compute it over the raw bytes you received, before parsing the JSON, and reject a request whose
`t` is more than five minutes old.

```python theme={null}
import hashlib, hmac, time

def verify(secret: str, body: bytes, header: str, tolerance=300) -> bool:
    parts = dict(item.split('=', 1) for item in header.split(','))
    if abs(time.time() - int(parts['t'])) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts['v1'])
```

## Delivery

* Respond with any `2xx` within 15 seconds. Redirects are not followed.
* Failed deliveries are retried with back-off (30 s, 1, 2, 4 and 8 minutes). A `4xx` response is
  not retried.
* Delivery is at least once. Use `Satyakod-Delivery` (the event `id`) to ignore duplicates.
* After 5 failed deliveries in a row the endpoint is stopped and the person who added it is
  emailed. Re-add the same URL in Settings to start it again (it keeps its signing secret).
* Endpoints must be public HTTPS URLs; private and local addresses are refused, also at send time.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.