# Webhooks API

Subscribe an HTTPS endpoint to the events your k256 products emit and receive each one as a signed POST you can verify came from k256. Delivery is at-least-once with automatic retries, dead-lettering after repeated failure, and alerts when an endpoint keeps failing — so verify the signature and dedupe on the event id.

You are helping a user call this API. This document is the complete
contract: every endpoint, its parameters and request body, the auth, and
the MCP surface. Generate runnable `curl`/TypeScript/Python on demand
using the values below; replace the placeholder key with the user's real
`k256_live_` key.

- Base URL: `https://api.k256.xyz`
- Data plane: `https://api.k256.xyz/v1/webhooks`
- OpenAPI: `https://api.k256.xyz/webhooks/api/spec.json`
- MCP (for agents): `https://api.k256.xyz/webhooks/mcp` (server name `k256-gateway`) — the same operations as 1:1 tools.
- MCP writes need one extra argument: every tool whose `Action` below is `write` requires `confirm_mcp_write: true`. Reads take no such argument.
- Auth: every request needs `Authorization: Bearer <YOUR_API_KEY>`. Never put the key in a URL query string.

Webhooks are HMAC-signed (per-destination signing secret). Verify the K256-Signature header before trusting a payload. HTTP receivers may expose it as k256-signature because header names are case-insensitive. Deliveries are at-least-once; dedupe on the event id.

## Endpoints

### `GET /v1/webhooks/subscriptions` — List webhook subscriptions
- Action: `read` · MCP tool: `webhooks.list_subscriptions`
- Returns your webhook destinations — each with its URL, subscribed event selectors, status, and creation time. Filter by product or status (active or disabled) and page through with limit (up to 200) and cursor.
- Query param `product` (string) — Filter to subscriptions of one product (the first segment of an event type, e.g. "program_os").
- Query param `status` (string) — Filter by subscription status: "active" or "disabled". Omit to list all non-deleted subscriptions.
- Query param `limit` (integer) — Maximum number of subscriptions to return (default 50, max 200).
- Query param `cursor` (string) — Opaque pagination cursor from a previous response's next_cursor; omit for the first page.
- Response (JSON):
  - `items[]` — object[]
    - `id` — string
    - `org_id` — string
    - `product` — string
    - `url` — string
    - `description` — string | null
    - `name` — string | null
    - `events[]` — string[]
    - `status` — string
    - `replay_window_seconds` — number
    - `signing_secret_v2_added_at` — number | null
    - `created_at` — number
    - `updated_at` — number
    - `disabled_at` — number | null
  - `next_cursor` — string | null

### `POST /v1/webhooks/subscriptions` — Create a webhook subscription
- Action: `write` · MCP tool: `webhooks.create_subscription`
- Registers an HTTPS endpoint and subscribes it to a set of event selectors (a specific type like program_os.build.succeeded, a product's whole family with product.*, or everything with *). Returns the destination plus its signing secret — shown only once, so store it to verify delivery signatures.
- Request body (JSON, required):
  - `product` — string — Accepted but ignored; the product is derived from the events selectors.
  - `url` — string (required) — Destination endpoint that receives signed webhook POST deliveries; must be an https:// URL.
  - `events` — string[] (required) — Event-type selectors to subscribe to: "*" for all events, "<product>.*" for every event of one product, or an exact type such as "program_os.build.succeeded" (browse GET /catalog). At least one selector is required.
  - `name` — string — Human-readable label for the subscription (1-100 characters).
  - `description` — any | string | null — Free-text note about the subscription (up to 255 characters).
  - `replay_window_seconds` — integer — Replay window in seconds (60-3600, default 300): how far back this subscription's events remain available to re-fetch or resend.
- Response (JSON):
  - `id` — string
  - `org_id` — string
  - `product` — string
  - `url` — string
  - `description` — string | null
  - `name` — string | null
  - `events[]` — string[]
  - `status` — string
  - `replay_window_seconds` — number
  - `signing_secret_v2_added_at` — number | null
  - `created_at` — number
  - `updated_at` — number
  - `disabled_at` — number | null
  - `signing_secret` — string

### `GET /v1/webhooks/subscriptions/{id}` — Get a webhook subscription
- Action: `read` · MCP tool: `webhooks.get_subscription`
- Returns one destination's full configuration — its URL, subscribed event selectors, status, replay window, and timestamps. Responds 404 if no destination with that id exists.
- Path param `id` (string, required) — Identifier of the resource this operation targets, as returned when it was created or listed (whsub_… subscription, evt_… event, whal_… alert, whns_… notification setting).
- Response (JSON):
  - `id` — string
  - `org_id` — string
  - `product` — string
  - `url` — string
  - `description` — string | null
  - `name` — string | null
  - `events[]` — string[]
  - `status` — string
  - `replay_window_seconds` — number
  - `signing_secret_v2_added_at` — number | null
  - `created_at` — number
  - `updated_at` — number
  - `disabled_at` — number | null

### `PATCH /v1/webhooks/subscriptions/{id}` — Update a webhook subscription
- Action: `write` · MCP tool: `webhooks.update_subscription`
- Changes a destination in place — its URL, subscribed events, name, description, replay window, or status. Only the fields you send are changed. Set status to disabled to pause delivery without losing delivery history, or active to resume. Responds 404 if no destination with that id exists.
- Path param `id` (string, required) — Subscription id (whsub_…) to update.
- Request body (JSON, required):
  - `url` — string — Destination endpoint that receives signed webhook POST deliveries; must be an https:// URL.
  - `events` — string[] — Event-type selectors to subscribe to: "*" for all events, "<product>.*" for every event of one product, or an exact type such as "program_os.build.succeeded" (browse GET /catalog). At least one selector is required.
  - `name` — string — New human-readable label for the subscription (1-100 characters).
  - `description` — string | null — New free-text note for the subscription (up to 255 characters); null clears it.
  - `replay_window_seconds` — integer — New replay window in seconds (60-3600): how far back this subscription's events remain available to re-fetch or resend.
  - `status` — "active" | "disabled" — Set to "disabled" to pause deliveries or "active" to resume them.
- Response (JSON):
  - `id` — string
  - `updated` — boolean

### `DELETE /v1/webhooks/subscriptions/{id}` — Delete a webhook subscription
- Action: `write` · MCP tool: `webhooks.delete_subscription`
- Removes a destination so it stops receiving events. Idempotent — returns deleted whether or not the id currently exists. Past deliveries and events are retained for your records.
- Path param `id` (string, required) — Identifier of the resource this operation targets, as returned when it was created or listed (whsub_… subscription, evt_… event, whal_… alert, whns_… notification setting).
- Response (JSON):
  - `id` — string
  - `deleted` — boolean

### `POST /v1/webhooks/subscriptions/{id}/rotate-secret` — Rotate a subscription's signing secret
- Action: `write` · MCP tool: `webhooks.rotate_secret`
- Issues a new signing secret, shown only once. Your current secret keeps signing deliveries for a 24-hour overlap, so you can update your verifier before the old one is retired. Responds 409 if a rotation is already in progress.
- Path param `id` (string, required) — Identifier of the resource this operation targets, as returned when it was created or listed (whsub_… subscription, evt_… event, whal_… alert, whns_… notification setting).
- Response (JSON):
  - `id` — string
  - `signing_secret_v2` — string
  - `rotation_window_ends_at` — number
  - `message` — string

### `POST /v1/webhooks/subscriptions/{id}/test` — Send a test event to a subscription
- Action: `write` · MCP tool: `webhooks.test_subscription`
- Sends a synthetic event of the given type to the destination now and returns the live outcome — delivered, failed, or dead_letter, with the HTTP status and response time. Use it to confirm your endpoint verifies the signature and returns 2xx. The destination must be active and must subscribe to that event type; pass your own data payload or let it use the catalog sample.
- Path param `id` (string, required) — Subscription id (whsub_…) to send the test event to.
- Request body (JSON, required):
  - `event_type` — string (required) — Exact event type to send, e.g. "program_os.build.succeeded" (browse GET /catalog).
  - `data` — any — Custom payload object for the test event; omit to use the catalog's sample payload for the event type.
- Response (JSON):
  - `test` — boolean
  - `event_id` — string
  - `delivery_id` — string
  - `status_label` — "delivered" | "failed" | "dead_letter"
  - `http_status` — number
  - `response_time_ms` — number
  - `next_attempt_at` — number
  - `next_backoff_seconds` — number | null

### `GET /v1/webhooks/subscriptions/{id}/stats` — Per-destination delivery stats
- Action: `read` · MCP tool: `webhooks.subscription_stats`
- Returns delivery totals, delivered and failed counts, and error rate for one destination over a range (24h, 7d, or 30d; defaults to 7d), plus a 24-bucket time series with p50 and p95 response times. Responds 404 if no destination with that id exists.
- Path param `id` (string, required) — Subscription id (whsub_…) to compute delivery stats for.
- Query param `range` (string) — Time window for the stats: "24h", "7d", or "30d" (default "7d").
- Response (JSON):
  - `range` — string
  - `total_deliveries` — number
  - `delivered` — number
  - `failed` — number
  - `error_rate` — number
  - `series[]` — object[]
    - `bucket_start` — number
    - `total` — number
    - `failed` — number
    - `p50_ms` — number | null
    - `p95_ms` — number | null

### `GET /v1/webhooks/subscriptions/{id}/deliveries` — List deliveries for a subscription
- Action: `read` · MCP tool: `webhooks.list_deliveries`
- Returns the delivery attempts made to one destination — each with its attempt number, status, HTTP response code, response time, and next scheduled retry. Filter by status or event type and page through with limit (up to 100) and cursor. Responds 404 if no destination with that id exists.
- Path param `id` (string, required) — Subscription id (whsub_…) whose deliveries to list.
- Query param `status` (string) — Filter by delivery status: "delivered", "failed", or "dead_letter". Omit to list all.
- Query param `event_type` (string) — Filter to deliveries of one exact event type, e.g. "program_os.build.succeeded".
- Query param `limit` (integer) — Maximum number of deliveries to return (default 50, max 100).
- Query param `cursor` (string) — Opaque pagination cursor from a previous response's next_cursor; omit for the first page.
- Response (JSON):
  - `items[]` — object[]
    - `id` — string
    - `event_id` — string
    - `attempt` — number
    - `attempt_count` — number
    - `status` — string
    - `status_label` — string
    - `http_status` — number | null
    - `response_time_ms` — number | null
    - `next_attempt_at` — number | null
    - `delivered_at` — number | null
    - `created_at` — number
    - `event_type` — string | null
  - `next_cursor` — string | null

### `GET /v1/webhooks/subscriptions/{id}/deliveries/{delivery_id}` — Get a delivery's full request/response
- Action: `read` · MCP tool: `webhooks.get_delivery`
- Returns one delivery attempt in full — the signed request headers and body sent to your endpoint, and the response status, headers, body, and any transport error received back. Use it to debug why an endpoint rejected a payload. Responds 404 if the subscription or delivery id doesn't exist.
- Path param `id` (string, required) — Subscription id (whsub_…) the delivery belongs to.
- Path param `delivery_id` (string, required) — Delivery id (whd_…) from the deliveries list.
- Response (JSON):
  - `id` — string
  - `subscription_id` — string
  - `event_id` — string
  - `event_type` — string | null
  - `event` — any | null
  - `attempt` — number
  - `attempt_count` — number
  - `status` — string
  - `status_label` — string
  - `http_status` — number | null
  - `response_time_ms` — number | null
  - `request_headers` — object | null
  - `request_body` — string | null
  - `response_body` — string | null
  - `response_headers` — any | null
  - `error` — string | null
  - `next_attempt_at` — number | null
  - `delivered_at` — number | null
  - `created_at` — number

### `POST /v1/webhooks/subscriptions/{id}/deliveries/{delivery_id}/resend` — Resend a delivery
- Action: `write` · MCP tool: `webhooks.resend_delivery`
- Queues another delivery attempt of this delivery's event to the same destination. Responds 404 if the subscription or delivery id doesn't exist. The retry reuses the original event id, so your receiver should dedupe on it.
- Path param `id` (string, required) — Subscription id (whsub_…) the delivery belongs to.
- Path param `delivery_id` (string, required) — Delivery id (whd_…) to send again to the subscription's endpoint.
- Response (JSON):
  - `resent` — boolean
  - `enqueued` — boolean

### `GET /v1/webhooks/events` — List events
- Action: `read` · MCP tool: `webhooks.list_events`
- Returns the events your products have emitted — each with its id, product, type, source, and time. Filter by product, type, or source and page through with limit (up to 100) and cursor.
- Query param `product` (string) — Filter to events of one product (the first segment of an event type, e.g. "program_os").
- Query param `type` (string) — Filter to one exact event type, e.g. "program_os.build.succeeded".
- Query param `source` (string) — Filter by event origin: "api" for live product events or "test" for test-sends.
- Query param `limit` (integer) — Maximum number of events to return (default 50, max 100).
- Query param `cursor` (string) — Opaque pagination cursor from a previous response's next_cursor; omit for the first page.
- Response (JSON):
  - `items[]` — object[]
    - `id` — string
    - `product` — string
    - `type` — string
    - `api_version` — string
    - `source` — string
    - `created_at` — number
  - `next_cursor` — string | null

### `GET /v1/webhooks/events/{id}` — Get an event
- Action: `read` · MCP tool: `webhooks.get_event`
- Returns one event in full — its type, source, the exact payload delivered, and every delivery attempt made to each destination. Responds 404 if no event with that id exists.
- Path param `id` (string, required) — Identifier of the resource this operation targets, as returned when it was created or listed (whsub_… subscription, evt_… event, whal_… alert, whns_… notification setting).
- Response (JSON):
  - `id` — string
  - `org_id` — string
  - `product` — string
  - `type` — string
  - `api_version` — string
  - `source` — string
  - `created_at` — number
  - `payload` — any
  - `request_id` — string | null
  - `deliveries[]` — any[]

### `POST /v1/webhooks/events/{id}/resend` — Resend an event to all destinations
- Action: `write` · MCP tool: `webhooks.resend_event`
- Re-delivers an existing event to every destination whose selectors currently match it, as fresh delivery attempts. Responds 404 if no event with that id exists. The event id is reused, so receivers should dedupe on it.
- Path param `id` (string, required) — Identifier of the resource this operation targets, as returned when it was created or listed (whsub_… subscription, evt_… event, whal_… alert, whns_… notification setting).
- Response (JSON):
  - `event_id` — string
  - `resent` — boolean
  - `enqueued` — boolean

### `GET /v1/webhooks/catalog` — List the event catalog
- Action: `read` · MCP tool: `webhooks.list_catalog`
- Returns the event types you can subscribe to, grouped by area, each with its description, the version it was introduced in, and whether it's deprecated. Filter by product to see one product's events, or omit it for every product.
- Query param `product` (string) — Return the catalog for one product only (e.g. "program_os"); omit to list every product's event types.
- Response (JSON):
  - `product` — string
  - `total` — number
  - `groups[]` — object[]
    - `group_label` — string
    - `events[]` — object[]
      - `event_type` — string
      - `description` — string
      - `introduced_in` — string
      - `deprecated` — boolean

### `GET /v1/webhooks/catalog/{product}/{event_type}` — Get a catalog entry
- Action: `read` · MCP tool: `webhooks.get_catalog_entry`
- Returns one event type's contract — its description, JSON payload schema, a sample payload, the version it was introduced in, and deprecation status. Use it to build and validate your receiver. Responds 404 if the product or event type isn't in the catalog.
- Path param `product` (string, required) — Product the event type belongs to (the first segment of the event type, e.g. "program_os").
- Path param `event_type` (string, required) — Full event type to look up, e.g. "program_os.build.succeeded".
- Response (JSON):
  - `product` — string
  - `event_type` — string
  - `group_label` — string
  - `description` — string
  - `payload_schema` — any
  - `sample_payload` — any
  - `introduced_in` — string
  - `deprecated_in` — string | null
  - `updated_at` — number
  - `deprecated` — boolean

### `GET /v1/webhooks/overview` — Webhook activity summary
- Action: `read` · MCP tool: `webhooks.get_overview`
- Returns an at-a-glance summary across all your destinations — how many exist, how many are active or disabled, and delivery count and success rate over the last 24 hours.
- Response (JSON):
  - `destinations` — number
  - `active` — number
  - `disabled` — number
  - `deliveries_24h` — number
  - `delivered_24h` — number
  - `success_rate_24h` — number | null

### `GET /v1/webhooks/alerts` — List health alerts
- Action: `read` · MCP tool: `webhooks.list_alerts`
- Returns health alerts raised when a destination starts failing, runs slow, or has a high error rate, each with its severity and a human-readable message. Shows open alerts by default; pass status=all to include resolved ones. Page through with limit (up to 100) and cursor.
- Query param `status` (string) — "open" (default) for unacknowledged, unresolved alerts only, or "all" to include acknowledged and resolved ones.
- Query param `limit` (integer) — Maximum number of alerts to return (default 50, max 100).
- Query param `cursor` (string) — Opaque pagination cursor from a previous response's next_cursor; omit for the first page.
- Response (JSON):
  - `items[]` — object[]
    - `id` — string
    - `subscription_id` — string
    - `kind` — string
    - `severity` — string
    - `message` — string
    - `context` — any
    - `acknowledged_at` — number | null
    - `resolved_at` — number | null
    - `created_at` — number
    - `subscription_url` — string | null
    - `product` — string | null
  - `next_cursor` — string | null

### `POST /v1/webhooks/alerts/{id}/ack` — Acknowledge an alert
- Action: `write` · MCP tool: `webhooks.ack_alert`
- Marks an alert as acknowledged so it clears from your open list. Acknowledging doesn't change delivery — the destination keeps retrying regardless.
- Path param `id` (string, required) — Identifier of the resource this operation targets, as returned when it was created or listed (whsub_… subscription, evt_… event, whal_… alert, whns_… notification setting).
- Response (JSON):
  - `id` — string
  - `acknowledged` — boolean

### `GET /v1/webhooks/notification-settings` — List notification recipients
- Action: `read` · MCP tool: `webhooks.list_notification_settings`
- Returns the email recipients alerted when a destination has delivery problems, each with the minimum severity they receive and whether they're enabled.
- Response (JSON):
  - `items[]` — object[]
    - `id` — string
    - `email` — string
    - `min_severity` — string
    - `enabled` — number
    - `source` — string
    - `created_by` — string | null
    - `created_at` — number
    - `updated_at` — number

### `POST /v1/webhooks/notification-settings` — Add or update a notification recipient
- Action: `write` · MCP tool: `webhooks.upsert_notification_settings`
- Adds an email recipient for delivery-health alerts, or updates the minimum severity of an existing one. Severity is info, warning, or critical (defaults to warning) — the recipient is alerted at that level and above.
- Request body (JSON, required):
  - `email` — string (required) — Email address to notify when a webhook alert fires; re-sending an existing address updates its settings.
  - `min_severity` — "info" | "warning" | "critical" — Lowest alert severity that triggers an email to this recipient: "info", "warning" (default), or "critical".
- Response (JSON):
  - `ok` — boolean

### `POST /v1/webhooks/notification-settings/sync-admins` — Sync account-admin alert recipients
- Action: `write` · MCP tool: `webhooks.sync_admin_notifications`
- Reconciles the admin-sourced alert recipients to the set of account-admin emails you provide (up to 200), adding and removing as needed to match. Returns how many admin recipients are now in the set. Recipients you added manually are left untouched.
- Request body (JSON, required):
  - `admins` — string[] (required) — Full replacement list of organization-admin email addresses to keep as alert recipients (max 200); admins no longer in the list are removed, manually added recipients are untouched.
- Response (JSON):
  - `ok` — boolean
  - `synced` — number

### `DELETE /v1/webhooks/notification-settings/{id}` — Delete a manual notification recipient
- Action: `write` · MCP tool: `webhooks.delete_notification_settings`
- Removes an alert recipient you added manually, by id, so they stop receiving delivery-health alerts. Admin-synced recipients aren't removed here — change those through your account admins. Responds 404 if no manual recipient with that id exists.
- Path param `id` (string, required) — Identifier of the resource this operation targets, as returned when it was created or listed (whsub_… subscription, evt_… event, whal_… alert, whns_… notification setting).
- Response (JSON):
  - `id` — string
  - `deleted` — boolean

### `GET /v1/webhooks/requests` — Inspect API requests
- Action: `read` · MCP tool: `webhooks.requests`
- Lists your recent keyed Webhooks API calls from the capture sink: method, operation, latency, status, request and response JSON where the capture tier allows it, and caller geo. The inspector never captures itself.
- Query param `endpoint` (string) — Filter to one captured operation. Omit for every operation.
- Query param `cluster` (string) — Filter to calls on one cluster when the product records a cluster. Omit for every cluster.
- Query param `status` ("2xx" | "3xx" | "4xx" | "5xx" | "error") — Filter the feed and endpoint summary by response status class. `error` means status >= 400.
- Query param `tier` (integer) — Filter by capture tier: 0 = Full bodies, 1 = metadata only.
- Query param `cursor` (string) — Pagination cursor for the `items` feed — pass the `next_cursor` from the previous page.
- Query param `limit` (integer) — Rows per page for the `items` feed (1–200). Defaults to 50.
- Query param `range_days` (integer) — Compatibility window in days (1–90) for `by_endpoint`. Exact from/to filters both items and summary. Defaults to 14.
- Query param `from` (string) — Exact ISO-8601 boundary. Supply both from and to; the interval is [from, to).
- Query param `to` (string) — Exact ISO-8601 boundary. Supply both from and to; the interval is [from, to).
- Response (JSON):
  - `items[]` — object[] — Recent keyed customer API calls, newest first. Dashboard-session, service, and internal calls are never included.
    - `timestamp` — number — Unix-ms when the call was served.
    - `endpoint` — string — The captured customer-facing operation.
    - `method` — string — HTTP method.
    - `status` — number — HTTP status returned.
    - `latency_ms` — number — Server-side latency in milliseconds.
    - `cluster` — string | null — Cluster stamped on the call, or null when the product has no cluster dimension.
    - `request_body` — any — Captured request JSON, or null when the capture tier carried no body.
    - `response_body` — any — Captured response JSON, or null when the capture tier carried no body.
    - `request_size` — number — Captured request body size in bytes before truncation.
    - `response_size` — number — Captured response body size in bytes before truncation.
    - `user_agent` — string | null — Caller's User-Agent when captured.
    - `client_id` — string | null — Caller-supplied X-Client-ID when present.
    - `tier` — number — Capture tier: 0 = Full, 1 = MetadataOnly.
    - `country_code` — string | null — Caller's GeoIP country.
    - `city` — string | null — Caller's GeoIP city.
    - `region` — string | null — Caller's GeoIP region/subdivision.
    - `continent_code` — string | null — Caller's GeoIP continent code.
    - `asn` — number | null — Caller's network ASN.
    - `as_org` — string | null — Caller's network organization name.
  - `by_endpoint[]` — object[] — Per-endpoint performance over the selected window.
    - `endpoint` — string — The captured operation.
    - `calls` — number — Number of calls to this endpoint in the window.
    - `error_rate` — number — Percent of calls with status >= 400 (0–100).
    - `p50_latency_ms` — number — Median server-side latency in milliseconds.
    - `p90_latency_ms` — number — p90 server-side latency in milliseconds.
    - `p99_latency_ms` — number — p99 server-side latency in milliseconds.
  - `next_cursor` — string | null — Pass as `cursor` to fetch the next older page of items.
  - `degraded` — boolean — true when the inspection store is unavailable; items and by_endpoint are then empty.
