> For the complete documentation index, see [llms.txt](https://boundaryai.gitbook.io/boundaryai-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://boundaryai.gitbook.io/boundaryai-docs/api-and-webhooks/webhooks.md).

# Webhooks

Webhooks push events to your systems the moment they happen, so you never poll. Subscriptions are managed in the dashboard under **Integrations Hub → Webhooks** (admin-only): set a target URL, pick the events, and you get an HMAC **signing secret**, shown once (rotate it any time with *Regenerate secret*).

Target URLs must be **HTTPS**, and deliveries are signed so you can prove they came from BAI Analytics.

***

### The events

| Event                | Fires when                                                                                                                                                                                                                            |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `content.pushed`     | An API push landed items in a source.                                                                                                                                                                                                 |
| `series.created`     | A feedback group was created through the API.                                                                                                                                                                                         |
| `survey.created`     | A source was created through the API.                                                                                                                                                                                                 |
| `survey.published`   | A source was published and can receive content.                                                                                                                                                                                       |
| `analysis.completed` | An analysis pass finished; results are readable via the analysis endpoint.                                                                                                                                                            |
| `flag.raised`        | A Custom Monitoring monitor crossed its alert threshold. Fires under the same gate as email/SMS monitor alerts, so it needs an enabled monitor-notification setting with the alerts feature; a subscription alone doesn't trigger it. |
| `report.ready`       | A period-close report subscription produced its report.                                                                                                                                                                               |
| `invites.completed`  | An API-triggered invite distribution finished, with final counts.                                                                                                                                                                     |
| `invite.bounced`     | An API-sent invite bounced or drew a spam complaint.                                                                                                                                                                                  |

Event names use the API's classic vocabulary (`series` = feedback group, `survey` = source, `flag` = monitor); they're wire contracts and stay stable. Full payload schemas and an example for every event are in the API reference's **Webhooks** section.

***

### What a delivery looks like

```
POST <your URL>
Content-Type: application/json
X-Boundary-Event: analysis.completed
X-Boundary-Signature: sha256=8f2ab0...
User-Agent: BoundaryAI-Webhook/1.0

{"event": "analysis.completed",
 "timestamp": "2026-07-12T09:45:12Z",
 "data": {"survey_series_id": 1842, "survey_id": 9021,
          "analysis_id": 55710, "survey_name": "Support tickets (CRM sync)"}}
```

Every delivery is the same envelope: `event`, `timestamp` (ISO-8601 UTC), and a per-event `data` object.

***

### Verifying the signature

`X-Boundary-Signature` is `sha256=` followed by the hex HMAC-SHA256 of the **raw request body**, keyed with your subscription's secret. Compute it over the exact bytes you received, before any JSON parsing, and compare in constant time.

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

```python
import hashlib, hmac

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)
```

{% endtab %}

{% tab title="Node.js" %}

```javascript
const crypto = require("crypto");

function verify(rawBody, signatureHeader, secret) {
  const expected = "sha256=" +
    crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected), Buffer.from(signatureHeader));
}
```

{% endtab %}
{% endtabs %}

Reject anything that doesn't verify. If you rotate the secret, deliveries sign with the new one immediately.

***

### Delivery semantics

* **Respond 2xx within 30 seconds.** Acknowledge first, process after; do the heavy work off the request path.
* **Retries**: a non-2xx response or a timeout is retried up to **3 times**, after 1, 5, and 15 minutes. A subscription that keeps failing accumulates a failure count you can see in the dashboard.
* **Design for at-least-once.** Treat deliveries as idempotent: the `data` payload plus your own state should make redelivery harmless.
* **Ordering is not guaranteed** across events; use the `timestamp` and your own IDs rather than arrival order.

{% hint style="success" %}
The most useful pairing: push with `POST /feedback/push`, then act on `analysis.completed` instead of polling the analysis endpoint. Your integration stays event-driven end to end.
{% endhint %}
