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

# Webhooks

> Receive signed, retried event notifications from Karta, verify signatures, and recover failed endpoints.

Karta can POST event notifications to an HTTPS endpoint you own - subscription
changes, budget thresholds, session and turn lifecycle events, and account
security events - so your systems react
without polling.

## Register an endpoint

Use the CLI:

```bash theme={null}
karta webhooks create prod-events https://example.com/webhooks/karta \
  --event subscription.updated \
  --event usage.budget_exhausted

karta webhooks list
karta webhooks enable <endpoint-id>
karta webhooks delete <endpoint-id>
```

Or use the management SDK:

```typescript theme={null}
import { Karta } from "@karta.sh/sdk";
const karta = new Karta({ apiKey: process.env.KARTA_API_KEY! });

const { webhook_endpoint, secret } = await karta.webhookEndpoints.create({
  name: "prod-events",
  url: "https://example.com/webhooks/karta",
  event_types: ["subscription.updated", "usage.budget_exhausted"],
});
// `secret` (whsec_kt_...) is shown once - use it to verify signatures.

await karta.webhookEndpoints.list();
await karta.webhookEndpoints.delete(id);
```

Each endpoint subscribes to a selected set of event types - you receive only
what you ask for. If you pass no event types, the endpoint receives every
supported event.

## Event types

| Event type                                                              | Fires when                                       |
| ----------------------------------------------------------------------- | ------------------------------------------------ |
| `session.started`                                                       | A new agent session is created.                  |
| `turn.completed`                                                        | An agent turn finishes.                          |
| `turn.failed`                                                           | An agent turn errors.                            |
| `subscription.created`, `subscription.updated`, `subscription.canceled` | Your plan subscription changes.                  |
| `invoice.paid`, `invoice.payment_failed`                                | A billing invoice is paid or fails.              |
| `usage.budget_threshold_reached`, `usage.budget_exhausted`              | Org spend crosses an alert threshold or the cap. |
| `organization.suspended`, `organization.resumed`                        | Your organization is suspended or reactivated.   |
| `api_key.created`, `api_key.revoked`                                    | An API key is minted or revoked.                 |

The API returns the current event-type list from `GET /api/webhook_endpoints`, so
automation can validate subscriptions before creating an endpoint.

## Payload and headers

Each POST has JSON shaped like:

```json theme={null}
{
  "id": "8f06b119-5d8b-4cc8-8b89-e19c8f1e9a36",
  "type": "usage.budget_exhausted",
  "created_at": "2026-07-01T12:00:00Z",
  "organization": { "id": 123, "slug": "acme" },
  "endpoint_id": 456,
  "data": { "threshold": 100, "percent_used": 101 }
}
```

Karta also sends:

| Header             | Meaning                                                            |
| ------------------ | ------------------------------------------------------------------ |
| `Karta-Event-Id`   | Stable id for this delivery. Store it to make handlers idempotent. |
| `Karta-Event-Type` | Same event type as the payload `type`.                             |
| `Karta-Timestamp`  | Unix timestamp used in the signature.                              |
| `Karta-Signature`  | HMAC signature over the timestamp and raw body.                    |

## Verifying signatures

Deliveries are signed with **HMAC-SHA256**. The
`Karta-Signature` header carries `t=<timestamp>,v1=<hexdigest>`, where the
signature is computed over the string `"<timestamp>.<raw_body>"` with your
endpoint's signing secret. Recompute it and compare in constant time before
trusting the payload.

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

def verify(secret: str, raw_body: bytes, signature_header: str) -> bool:
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    signed = parts["t"].encode() + b"." + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
```

<Warning>
  Always verify against the **raw** request body, before any JSON parsing or
  re-serialization - reserializing changes the bytes and breaks the signature.
</Warning>

## Delivery, retries, and auto-disable

| Behavior     | Contract                                                                                              |
| ------------ | ----------------------------------------------------------------------------------------------------- |
| Success      | Any `2xx` response marks the delivery successful.                                                     |
| Retry        | Non-2xx and transport failures retry with exponential backoff.                                        |
| Attempts     | A delivery is attempted up to 8 times.                                                                |
| Auto-disable | An endpoint is disabled after 25 consecutive counted failures by default.                             |
| Recovery     | Fix the destination, then run `karta webhooks enable <endpoint-id>` or re-enable it in the dashboard. |

## SSRF protection

Webhook URLs must be HTTPS public destinations. Private, loopback, link-local,
and internal hostnames are rejected, and destinations are re-checked before
delivery. You cannot point a webhook at internal infrastructure.

<Note>
  Webhooks are **outbound** notifications from Karta to you. They're distinct
  from [sessions](/concepts/sessions-and-participants), which fan messages out
  to session participants.
</Note>
