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

# Webhooks

> Subscribe to signed Alterscope event webhooks, verify signatures, and replay missed deliveries.

Webhooks push risk events to your endpoint as they happen — an alternative to holding a WebSocket open. Deliveries are signed so you can verify they came from Alterscope.

## Subscribe

```bash theme={null}
curl -H "Authorization: Bearer $ALTERSCOPE_API_KEY" \
     -H "Content-Type: application/json" \
     -X POST \
     -d '{
       "url": "https://your.app/alterscope-webhook",
       "events": ["peg.depeg.start", "peg.depeg.end", "vault.cap.changed"]
     }' \
  "https://api.alterscope.org/v2/webhooks"
```

On creation, the response returns a `signing_secret` (prefix `whsec_`) **exactly once** — store it in your secrets manager, because you can't retrieve it again. You cannot supply your own secret; the platform generates it. Rotate it via `POST /v2/webhooks/{id}/rotate-secret`. Subscribe to the event types you care about; examples include `peg.depeg.start`, `peg.depeg.end`, and `vault.cap.changed`. See the [Event catalog](/develop/realtime/event-catalog) for every type, when it fires, and a sample payload.

## Verify the signature

Every delivery includes an `Alterscope-Signature` header in the form `t=<unix_ts>,v1=<hex_hmac_sha256>`. The `v1` value is the HMAC-SHA256 of `"<t>.<raw_body>"` (the timestamp, a literal dot, then the raw request body), keyed with your `signing_secret`. Recompute it, compare in constant time, **and** reject deliveries whose timestamp is outside a \~5-minute tolerance — that bounds replay attacks:

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

def verify(raw_body: bytes, header: str, secret: str, tolerance_s: int = 300) -> bool:
    # header looks like: "t=1716902400,v1=abc123..."
    parts = dict(p.split("=", 1) for p in header.split(","))
    t, v1 = parts.get("t"), parts.get("v1")
    if not t or not v1:
        return False

    # Reject stale timestamps to bound replay.
    if abs(time.time() - int(t)) > tolerance_s:
        return False

    signed = f"{t}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)
```

Reject any delivery whose signature doesn't match or whose timestamp is stale.

<Note>
  A legacy `X-Alterscope-Signature` header — a plain HMAC-SHA256 of the raw body (no timestamp prefix) — is **dual-emitted alongside `Alterscope-Signature` during a 60-day transition window** and is deprecated. New integrations should verify `Alterscope-Signature`; migrate any existing checks off the legacy header before the window closes.
</Note>

## Manage subscriptions

| Operation                 | Endpoint                                       |
| ------------------------- | ---------------------------------------------- |
| List / create             | `GET` / `POST` `/v2/webhooks`                  |
| Get / update / delete     | `GET` / `PATCH` / `DELETE` `/v2/webhooks/{id}` |
| Inspect deliveries        | `GET /v2/webhooks/{id}/deliveries`             |
| Send a test event         | `POST /v2/webhooks/{id}/test`                  |
| Rotate the signing secret | `POST /v2/webhooks/{id}/rotate-secret`         |

After rotating, accept both the old and new signature for a short overlap so in-flight deliveries still verify.

## Replay missed events

If your endpoint was down, replay events from the unified feed within your tier's replay window (see [Rate limits](/develop/get-started/rate-limits)):

```bash theme={null}
curl -H "Authorization: Bearer $ALTERSCOPE_API_KEY" \
  "https://api.alterscope.org/v2/events/replay?since_event_id=01J9Z0K3M4N5P6Q7R8S9T0V1W2&limit=100"
```

Signed webhooks are available on the **Team tier and higher**; see [Rate limits](/develop/get-started/rate-limits) for per-tier limits.
