
{% callout type="info" title="Coming soon" %}
Outgoing webhooks are not yet available. There is no dashboard UI yet for setting a webhook endpoint or choosing events, and no events are dispatched. This page describes the planned feature so you know what to expect. We will update this page — including the exact configuration steps — once the feature ships.
{% /callout %}

{% callout type="warning" title="Provisional schema" %}
The webhook payload schemas on this page are **provisional** and may change before general availability. Field names, types, and nesting are subject to revision pending product and engineering sign-off. Treat the JSON examples below as a guide for shape, not a stable contract — pin to a payload version once we publish one.
{% /callout %}

Send ADO Pilot events — review completions, credit alerts, and plan renewals — to your own HTTPS endpoint so you can fan them out to Slack, PagerDuty, an analytics warehouse, or any custom automation.

## Requirements

- **HTTPS only.** ADO Pilot refuses to save plain `http://` URLs, and refuses to deliver to non-HTTPS endpoints even if you bypass the form.
- **Public reachability.** Your endpoint must accept connections from the public internet — ADO Pilot does not support delivering to private or internal network addresses.
- **Five-second response budget.** Your handler must respond `2xx` within 5 seconds. Anything slower is treated as a failure and is not retried.
- **Org-admin role.** Only org admins can save webhook settings or send a test request.

## Configure the endpoint

Webhook configuration isn't exposed in the dashboard yet. Once it ships, you'll configure it under **Settings** then **Notifications**, where you'll set your endpoint and choose which events to deliver (see the table below). We'll publish the exact steps here when that UI is live.

A URL that doesn't parse as `https://` will be rejected rather than saved — see Requirements above.

## Supported events

ADO Pilot will emit five outgoing webhook events once webhook delivery ships. Each event fires once per occurrence — there are no duplicate deliveries for the same trigger.

| Event              | Triggered when                                                                                            |
| ------------------ | --------------------------------------------------------------------------------------------------------- |
| `review.completed` | A pull request review finishes and a `PASS`, `ADVISORY`, or `FAIL` status check has been posted.          |
| `review.failed`    | A review enters a terminal failure state (for example, an internal processing error or quota exhaustion). |
| `credit.low`       | Your organization's review credit (RC) consumption first crosses 80% of the plan's included RCs.          |
| `credit.exhausted` | Your organization runs out of RCs — either the hard-block quota is hit or the auto-overage cap is hit.    |
| `plan.renewed`     | Stripe processes a subscription renewal: RCs reset and a new billing period begins.                       |

## Test your endpoint

A test-send control is planned for the dashboard, but it isn't available yet. Once it ships, triggering it will send a single request to your saved endpoint.

The test payload is intentionally minimal:

```json
{
  "event": "review.completed.test",
  "orgId": "your-org-id",
  "timestamp": "2026-05-05T14:30:00.000Z",
  "test": true
}
```

If your endpoint replies `2xx` within 5 seconds, the dashboard will show **Delivered (HTTP _NNN_)** with the status code your endpoint returned — for example `Delivered (HTTP 200)` or `Delivered (HTTP 204)`. Anything else will surface as an inline error — connection refused, TLS failure, timeout, or an HTTP 4xx/5xx from your handler.

{% callout type="info" title="The test always hits your saved URL" %}
The test always fires against the URL saved for your org, not a value you haven't saved yet — a request that specifies a different URL is rejected outright rather than tested. This prevents the test from being abused as an authenticated probe of arbitrary internal addresses.
{% /callout %}

## Production payloads

Each event below shows a representative JSON body and a field reference. Every field on every payload is **provisional pending PM/eng confirmation** — see the warning at the top of this page.

### `review.completed`

Fires when a review reaches a terminal verdict.

```json
{
  "event": "review.completed",
  "orgId": "org-12345",
  "timestamp": "2026-05-05T14:35:22.000Z",
  "reviewId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "prId": 42,
  "projectId": "ado-proj-guid-or-slug",
  "repositoryId": "ado-repo-guid-or-slug",
  "verdict": "ADVISORY",
  "findingCounts": {
    "blocker": 0,
    "warning": 2,
    "suggestion": 5
  },
  "durationMs": 180000,
  "creditsUsed": 1
}
```

| Field           | Type   | Notes                                                         |
| --------------- | ------ | ------------------------------------------------------------- |
| `event`         | string | Always `"review.completed"`.                                  |
| `orgId`         | string | Your ADO Pilot organization ID.                               |
| `timestamp`     | string | ISO 8601 UTC; emitted when the review was finalized.          |
| `reviewId`      | string | Unique review identifier; correlates to the PR thread in ADO. |
| `prId`          | number | Pull request number in the ADO repository.                    |
| `projectId`     | string | ADO project GUID or slug.                                     |
| `repositoryId`  | string | ADO repository GUID or slug.                                  |
| `verdict`       | string | `"PASS"`, `"ADVISORY"`, or `"FAIL"`.                          |
| `findingCounts` | object | Counts of `blocker`, `warning`, and `suggestion` findings.    |
| `durationMs`    | number | Milliseconds from queued to completed.                        |
| `creditsUsed`   | number | RCs charged for this review.                                  |



### `review.failed`

Fires when a review enters a terminal failure state — for example, an internal processing error, tenant-inactive status, or quota exhaustion.

```json
{
  "event": "review.failed",
  "orgId": "org-12345",
  "timestamp": "2026-05-05T14:40:10.000Z",
  "reviewId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "prId": 42,
  "projectId": "ado-proj-guid-or-slug",
  "repositoryId": "ado-repo-guid-or-slug",
  "errorType": "quota_exceeded",
  "errorMessage": "Review quota exhausted — upgrade your plan to continue.",
  "durationMs": 5000
}
```

| Field          | Type   | Notes                                                                               |
| -------------- | ------ | ----------------------------------------------------------------------------------- |
| `event`        | string | Always `"review.failed"`.                                                           |
| `orgId`        | string | Your organization ID.                                                               |
| `timestamp`    | string | ISO 8601 UTC at the moment of failure.                                              |
| `reviewId`     | string | Unique review identifier.                                                           |
| `prId`         | number | Pull request number.                                                                |
| `projectId`    | string | ADO project GUID or slug.                                                           |
| `repositoryId` | string | ADO repository GUID or slug.                                                        |
| `errorType`    | string | Machine-readable category, e.g. `"quota_exceeded"`, `"auth_error"`, `"rate_limit"`. |
| `errorMessage` | string | Human-readable description; safe to surface in alerts.                              |
| `durationMs`   | number | Milliseconds from queued to failure.                                                |



### `credit.low`

Fires once per billing period the first time RC consumption crosses 80% of the included plan credits.

```json
{
  "event": "credit.low",
  "orgId": "org-12345",
  "timestamp": "2026-05-05T14:45:33.000Z",
  "usagePercent": 82,
  "rcConsumed": 410,
  "rcIncluded": 500,
  "billingPeriod": "2026-05"
}
```

| Field           | Type   | Notes                                                            |
| --------------- | ------ | ---------------------------------------------------------------- |
| `event`         | string | Always `"credit.low"`.                                           |
| `orgId`         | string | Your organization ID.                                            |
| `timestamp`     | string | ISO 8601 UTC at the moment the threshold was crossed.            |
| `usagePercent`  | number | RC consumption as a percentage of `rcIncluded`.                  |
| `rcConsumed`    | number | RCs consumed this period (includes any overage already accrued). |
| `rcIncluded`    | number | RCs included in your current plan tier.                          |
| `billingPeriod` | string | `YYYY-MM` for the current Stripe billing period.                 |



### `credit.exhausted`

Fires when the organization runs out of usable RCs — either the hard-block quota is hit, or the auto-overage cap is reached.

```json
{
  "event": "credit.exhausted",
  "orgId": "org-12345",
  "timestamp": "2026-05-05T14:50:01.000Z",
  "rcConsumed": 500,
  "rcIncluded": 500,
  "overageCapReached": true,
  "overageCap": 100,
  "billingPeriod": "2026-05"
}
```

| Field               | Type    | Notes                                                                            |
| ------------------- | ------- | -------------------------------------------------------------------------------- |
| `event`             | string  | Always `"credit.exhausted"`.                                                     |
| `orgId`             | string  | Your organization ID.                                                            |
| `timestamp`         | string  | ISO 8601 UTC at the moment the limit was hit.                                    |
| `rcConsumed`        | number  | Total RCs used this period.                                                      |
| `rcIncluded`        | number  | RCs included in your plan.                                                       |
| `overageCapReached` | boolean | `true` if the auto-overage cap was hit; `false` if the hard-block quota ran out. |
| `overageCap`        | number  | The cap configured in **Billing**; `0` for hard-block plans.                     |
| `billingPeriod`     | string  | `YYYY-MM`.                                                                       |



### `plan.renewed`

Fires when Stripe processes a subscription renewal — RCs reset, and a new billing period begins.

```json
{
  "event": "plan.renewed",
  "orgId": "org-12345",
  "timestamp": "2026-05-05T15:00:00.000Z",
  "planTier": "team",
  "rcIncluded": 400,
  "billingPeriodStart": "2026-06-05",
  "billingPeriodEnd": "2026-07-05",
  "invoiceUrl": "https://invoice.stripe.com/i/acct_xxx/inv_yyy"
}
```

| Field                | Type   | Notes                                                   |
| -------------------- | ------ | ------------------------------------------------------- |
| `event`              | string | Always `"plan.renewed"`.                                |
| `orgId`              | string | Your organization ID.                                   |
| `timestamp`          | string | ISO 8601 UTC when the renewal was processed.            |
| `planTier`           | string | `"starter"`, `"team"`, `"business"`, or `"enterprise"`. |
| `rcIncluded`         | number | RCs included in the new billing period.                 |
| `billingPeriodStart` | string | ISO `YYYY-MM-DD`; first day of the new period.          |
| `billingPeriodEnd`   | string | ISO `YYYY-MM-DD`; last day of the new period.           |
| `invoiceUrl`         | string | HTTPS link to the Stripe-hosted invoice.                |



## Delivery semantics

- ADO Pilot sends each event **once**. There are no automatic retries on failure.
- If your endpoint is unreachable, slow, or returns a non-`2xx` status, the event is dropped — it is not queued for later replay.
- Monitor your endpoint health and alert on consecutive failures so you notice silently-broken integrations.

{% callout type="warning" title="Authentication is provisional" %}
Webhook payload signing — how you verify that a request actually came from ADO Pilot — is not yet documented for general use. Treat your endpoint as untrusted input until we publish the verification scheme. Network-level allowlisting of ADO Pilot's egress range is the safest interim control; open a [support request](../trust/support-and-contact.md) for the current range.
{% /callout %}

## Troubleshooting

**Saving an endpoint URL fails.** A URL that isn't a well-formed `https://` address will be rejected — paste a full URL that starts with `https://`. (An empty value is allowed and clears the configured endpoint.)

**The test reports a 5-second timeout.** Your endpoint accepted the connection but did not respond in time. Check downstream services your handler depends on; consider returning `2xx` immediately and processing asynchronously.

**The test returns an HTTP 4xx or 5xx.** Your endpoint received the request but rejected it. Inspect your server logs — common causes are missing route handlers, body-parser misconfiguration, or auth middleware blocking unauthenticated requests.

## Summary

Outgoing webhooks will let you forward five ADO Pilot events — `review.completed`, `review.failed`, `credit.low`, `credit.exhausted`, and `plan.renewed` — to any HTTPS endpoint you control, once the feature ships (see the callout at the top of this page for current status). Configuration will live under **Settings** then **Notifications**, and a test-send control will let you verify delivery before you depend on it. Non-HTTPS URLs will be rejected outright. Each event will fire once with a 5-second response budget and no retries, so plan to monitor your endpoint and alert on failures. Payload schemas on this page are **provisional** — pin to a published version once one exists, and watch the changelog for field renames or additions before general availability.


