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

# Webhooks

> Receive each finished screening instead of polling.

Give us an HTTPS address and we send it a signing secret. When a screening
finishes, the API sends `POST` to your address:

```json theme={null}
{
  "id": "evt_9b2f4a1c0d5e4f6a8b7c6d5e4f3a2b1c",
  "type": "screening.completed",
  "api_version": "v1",
  "created_at": "2026-11-03T14:04:11Z",
  "data": {"screening": {"id": "scr_7c9e6679742540de944be07fc1f77c3a", "status": "completed"}}
}
```

`data.screening` is the full screening, as `GET /v1/screenings/{id}` returns it.
The event types are `screening.completed` and `screening.failed`.

## Verify the signature

Each request has a `Verify-Signature` header:

```text theme={null}
Verify-Signature: t=1793455451,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
```

1. Take `t` and the raw request body.
2. Compute the HMAC-SHA256 of `{t}.{body}` with your signing secret, as hex.
3. Accept the request if any `v1` value matches. While we rotate your secret,
   the header carries one `v1` for each valid secret.
4. Reject the request if `t` is more than five minutes old.

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

def verified(header: str, body: bytes, secret: str) -> bool:
    parts = [part.split("=", 1) for part in header.split(",")]
    timestamp = next(value for key, value in parts if key == "t")
    expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + body, hashlib.sha256).hexdigest()
    fresh = abs(time.time() - int(timestamp)) < 300
    return fresh and any(key == "v1" and hmac.compare_digest(value, expected) for key, value in parts)
```

## Retries

Reply with any 2xx status to accept an event. Otherwise the API tries again
after 30 seconds, doubling the wait each time up to an hour, for 24 hours. A
retry sends the same event `id`, so ignore an `id` you have already processed.

Polling always works too, which suits systems such as Salesforce that cannot
easily receive webhooks.


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