# Webhooks

> Send AI CodeOps events to Slack, Discord, Teams or your own HTTPS endpoint, verify signed payloads, and call your deploy pipeline when a change merges.

Source: https://aicodeops.dev/docs/webhooks · Last updated: 2026-10-18

AI CodeOps sends webhooks out; it doesn't take them in. You can:

- send workspace events to chat or to your own endpoint, with **notification endpoints**;
- call your deploy pipeline when an AI CodeOps pull request is merged, with **deploy webhooks**.

## No incoming Git webhooks

AI CodeOps doesn't register webhooks on your repositories and has no endpoint for Git host events. It works with GitHub, GitLab, Bitbucket, Gitea and Forgejo through the account you connect, and checks the state of a pull request by asking your Git host. A self-managed Git server needs no inbound webhook either, but AI CodeOps must be able to reach it: use its public https address (a server on a private network works only if the operator allowlists it). See [Connect GitHub](https://aicodeops.dev/docs/git/github) and the other Git guides.

## Notification endpoints

### Add an endpoint

1. Open **Configure → Settings** and find **Notification endpoints**.
2. Choose **Add endpoint**.
3. Fill in:
   - **Name**: for example "On-call Slack channel".
   - **Kind**: Slack, Discord, MS Teams or Generic webhook.
   - **Target URL**: the incoming-webhook URL from your chat tool, or your own endpoint.
   - **Min severity**: info (all), low+, medium+, high+ or critical only.
   - **Events to send**: tick the events you want. None ticked means all events.
   - **Signing secret** (Generic webhook only): at least 8 characters.
4. Choose **Create endpoint**.

Owners, admins and DevOps admins can manage endpoints. See [Roles and permissions](https://aicodeops.dev/docs/roles-and-permissions).

The target URL must be a public HTTPS address. Deliveries to `localhost`, private networks or cloud metadata addresses are refused, and redirects aren't followed.

### Events

| Event              | Kind                       | When                                              |
| ------------------ | -------------------------- | ------------------------------------------------- |
| Deploy failed      | `deployment.failed`        | A deployment failed                               |
| Deploy succeeded   | `deployment.completed`     | A deployment succeeded                            |
| Deploy recorded    | `deployment.recorded`      | Any deployment record, in any status              |
| Incidents & policy | `policy_decision.recorded` | An incident opens or changes, or a policy decides |
| Approval requested | `approval.requested`       | A request is waiting for a decision               |
| Approval decided   | `approval.decided`         | A request was approved or rejected                |
| Run completed      | `workflow_run.completed`   | A session finished                                |
| New finding        | `agent_finding.created`    | A review raised a finding                         |
| Schedule failed    | `schedule.failed`          | A scheduled workflow couldn't run                 |

With no events ticked, the endpoint also receives session and agent-step progress updates.

**Min severity** filters only events that carry a severity, such as findings, incidents and failed schedules. Events without one are always sent.

### Payloads

Slack and Microsoft Teams get `{"text": "<summary>"}`. Discord gets `{"content": "<summary>"}`. The summary is one line, for example:

```text
Deploy prj_01JA2B3C4D5E6F7G8H9J0K1M2R → production @ 3f9c2a1b: failed — The deploy webhook answered HTTP 502.
```

A **Generic webhook** gets the summary and the event as JSON, with `Content-Type: application/json`:

```json
{
  "summary": "Deploy prj_01JA2B3C4D5E6F7G8H9J0K1M2R → production @ 3f9c2a1b: failed — The deploy webhook answered HTTP 502.",
  "event": {
    "kind": "deployment.failed",
    "workspaceID": "wrk_01JA2B3C4D5E6F7G8H9J0K1M2N",
    "entityID": "dep_01JA2B3C4D5E6F7G8H9J0K1M2P",
    "workflowRunID": "wfr_01JA2B3C4D5E6F7G8H9J0K1M2Q",
    "status": "failed",
    "emittedAt": "2026-10-18T09:00:00.000Z",
    "metadata": {
      "projectID": "prj_01JA2B3C4D5E6F7G8H9J0K1M2R",
      "environment": "production",
      "sequence": 3,
      "commitSHA": "3f9c2a1b7d4e5f60718293a4b5c6d7e8f9012345",
      "strategy": "rolling",
      "errorSummary": "The deploy webhook answered HTTP 502."
    }
  }
}
```

Every event has `kind`, `workspaceID`, `entityID` and `emittedAt`. `workflowRunID`, `status` and `metadata` appear when they apply. The event is light: fetch details from the console if you need them.

### Verify the signature

A Generic webhook with a signing secret sends two headers:

```http
X-CodeOps-Signature: sha256=<hex HMAC-SHA256 of the raw body, keyed with your secret>
X-CodeOps-Event: deployment.failed
```

To verify, compute the HMAC-SHA256 of the raw request body with your secret and compare it with the header in constant time. For example, in Node.js:

```js
import { createHmac, timingSafeEqual } from "node:crypto"

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

Use the body bytes exactly as received, before parsing the JSON. The signature has no timestamp, so to drop duplicates, use `kind`, `entityID` and `emittedAt` together; `emittedAt` stays the same when a delivery is retried.

### Test, retries and the delivery log

Each endpoint has **Test**, **Deliveries**, **Disable** and **Delete**.

- **Test** sends a sample event at once, whatever the endpoint's filters.
- **Deliveries** shows each attempt with its status, event kind, HTTP status, attempt number, latency and detail. Delivery history is kept for 30 days.
- A delivery times out after 8 seconds. Network errors, timeouts and HTTP 408, 425, 429, 500, 502, 503 and 504 are retried after 30 seconds, 2 minutes and 8 minutes. Other errors aren't retried.
- After 10 failed deliveries in a row, the endpoint is disabled. Fix it and choose **Enable**.

There is no edit form yet: to change an endpoint, delete it and add it again.

## Deploy webhooks

A deploy webhook calls your pipeline when an AI CodeOps pull request is merged from the console.

1. Open **Release → Deployments** and choose **Deploy targets**.
2. For each project, enter a **Deploy webhook URL**, an **Environment** and an optional **Signing secret**.
3. Save.

On merge, AI CodeOps sends:

```json
{
  "event": "pr_merged",
  "workspaceID": "wrk_01JA2B3C4D5E6F7G8H9J0K1M2N",
  "projectID": "prj_01JA2B3C4D5E6F7G8H9J0K1M2R",
  "repo": "acme/api",
  "prNumber": 42,
  "prUrl": "https://github.com/acme/api/pull/42"
}
```

With a signing secret it adds `X-CodeOps-Signature: sha256=…`, computed the same way as above. The URL must be public HTTPS. A [workspace policy](https://aicodeops.dev/docs/policy-engine) `release.deploy` rule can block automatic deploys to an environment.

## Next steps

- [API](https://aicodeops.dev/docs/api)
- [Policy rules reference](https://aicodeops.dev/docs/policy-engine)
- [Approvals and policies](https://aicodeops.dev/docs/approvals-and-policies)

## Frequently asked questions

### Do I need to add a webhook to my Git repository?

No. AI CodeOps doesn't receive webhooks from Git hosts and doesn't register any on your repositories. It talks to your Git host with your connection's token.

### Are notification payloads signed?

Generic webhooks with a signing secret are signed with HMAC-SHA256 in the X-CodeOps-Signature header. Slack, Discord and Teams messages aren't signed.

### What happens if my endpoint is down?

Failed deliveries are retried after 30 seconds, 2 minutes and 8 minutes. After 10 failures in a row the endpoint is disabled.
