# Custom Webhook Notifications

Deliver Capsule policy-violation notifications to any HTTPS endpoint you control, such as a SOAR playbook, an incident-management tool, or an internal service.

## Overview

When a policy you've linked is violated, Capsule sends an HTTP `POST` with a JSON body to your webhook URL. The body describes the violation - severity, policy, affected entity - with a link back into the Capsule portal.

You can add more than one custom webhook and link each to different policies. For example, high-severity violations can go to your SOAR while everything else goes to a ticketing endpoint.

```
Capsule policy violation
  → Capsule POSTs a JSON event to your HTTPS URL
  → your endpoint returns a 2xx
```

## Prerequisites

Before you begin, ensure you have:

- An **HTTPS endpoint** reachable from the public internet that accepts `POST` requests with a JSON body
- Any **credentials** the endpoint expects, such as a bearer token or API key, which Capsule will send as a request header
- A **Capsule Security** account with admin access


## 1. Prepare the endpoint

Capsule only delivers to URLs that meet these rules. A URL that breaks one is rejected when you save it.

- The URL uses `https://` and is at most 2,048 characters.
- It does not embed credentials (`https://user:password@...`). Send them as a header instead.
- Its host resolves only to public IP addresses. Private, loopback, link-local, and reserved addresses are blocked, and the check runs again on every delivery.


Your endpoint must answer with a **2xx** status. Capsule does not follow redirects, so a `3xx` counts as a failure. Each request times out after 15 seconds.

## 2. Configure the channel in Capsule

1. Log in to the **Capsule Security** portal.
2. Go to **Settings → Notifications**.
3. Click **Add channel** and choose **Custom webhook**.
4. Enter a **display name** (how this channel appears in Capsule) and the **webhook URL**.
5. Optionally, add **headers** to send with every request, such as `Authorization: Bearer <token>`. You can add up to 20 headers.
6. Click **Save & Test**.


### Headers

Use headers to authenticate Capsule to your endpoint. Capsule sets `Content-Type: application/json` itself, so you can't override it, or `Host`, `Content-Length`, `Transfer-Encoding`, or `Connection`. Header names must be unique.

### Treat the URL and header values as secrets

Paths and query strings often carry a token, so Capsule never shows the full URL after you save it. The channel shows only the URL's origin (for example `https://hooks.acme.com/…`). Header values are write-only: Capsule shows the header names but never the values.

When you **Edit** a channel, re-enter the full URL. Leave a header value blank to keep the stored value, or enter a new one to rotate it.

### What the test proves

**Save & Test** sends a test event (`"eventType": "test"`) to your endpoint. A passing test means your endpoint returned a 2xx. Check the receiving system to confirm it handled the event the way you expect.

## 3. Link the channel to policies

A custom webhook only receives notifications for the policies you link to it. On the **policy edit** screen, add the webhook channel to the policy's notification channels - the same way you link any other notification destination. A channel with no linked policies stays idle.

## Payload

Every request is a `POST` with `Content-Type: application/json`, plus any headers you configured.

```json
{
  "source": "capsule",
  "eventId": "6f1c2a9e-4b7d-4e2a-9a51-0c3f8d2e7b14",
  "eventType": "policy_violation",
  "timestamp": "2026-10-28T09:15:00.000Z",
  "title": "Sensitive data shared with an external AI tool",
  "summary": "A prompt containing source code was sent to an unapproved AI tool.",
  "severity": "high",
  "link": "https://<your-capsule-portal>/...",
  "fields": {
    "policyName": "Block source code to external AI",
    "policyId": "8d3e...",
    "entityName": "jane@acme.com",
    "entityId": "a41b...",
    "issueId": "c92f..."
  },
  "pendingSummaries": []
}
```

| Field | Description |
|  --- | --- |
| `source` | Always `capsule`. |
| `eventId` | Unique ID of the notification. It stays the same across automatic retries, so use it to deduplicate. |
| `eventType` | `policy_violation`, `policy_violation_summary`, or `test`. |
| `timestamp` | When the violation occurred, in ISO 8601 UTC. |
| `title` | Short headline of the violation. |
| `summary` | Description of the violation. |
| `severity` | Severity of the violation, or `null`. |
| `link` | URL of the violation in the Capsule portal, or `null`. |
| `fields` | Policy, entity, and issue identifiers. Any of them can be `null`. |
| `pendingSummaries` | Titles of violations held back during the cooldown window. Empty unless `eventType` is a summary. |


### Summaries

To avoid flooding your endpoint, Capsule sends at most one notification per policy and entity per hour. Violations for the same policy and entity that occur within that hour are held back. The next notification after the hour carries their titles in `pendingSummaries` and has `"eventType": "policy_violation_summary"`.

The test event has `"eventType": "test"`, `null` severity, link, and fields, and an empty `pendingSummaries`.

## Troubleshooting

**The URL is rejected when saving.** The URL isn't `https://`, embeds credentials, can't be resolved, or resolves to a private or reserved address. Use a publicly reachable HTTPS URL.

**The test fails with "The webhook rejected the credentials".** Your endpoint returned `401` or `403`. Update the auth header value and test again.

**The test fails with "The webhook rejected the request".** Your endpoint returned `400` or a redirect (`3xx`). Check the URL and headers. If the endpoint redirects, use the final URL instead.

**The channel row shows an Error state.** Capsule flips a channel to **Error** when a delivery fails and records a reason. The usual causes are an endpoint that was removed (`404`), revoked credentials, or an endpoint that is down. Fix the endpoint or **Edit** the channel. Saving a changed URL or headers returns the channel to Active.

**Duplicate URL rejected.** Capsule rejects a URL that another custom webhook channel already uses, including the query string. To change which policies notify an endpoint, edit the policies linked to the existing channel.

**Rate limiting or transient failures.** On a timeout, a network error, a `429`, or a `5xx`, Capsule **retries automatically** up to 3 times with backoff. It honors a `Retry-After` header up to 60 seconds. Persistent failures surface as an **Error** state on the channel row.

## Support

For help with this integration:

- **Email**: support@capsule.security
- **Include**: Your organization ID, the affected channel's display name, and any error shown on the channel row