---
title: "Create a webhook endpoint"
method: POST
path: "/v2/workspaces/{id}/webhooks"
tags: ["Webhooks"]
---

# Create a webhook endpoint

`POST /v2/workspaces/{id}/webhooks`

## Request

- Base URL: `https://api.infolang.ai`
- URL: `https://api.infolang.ai/v2/workspaces/{id}/webhooks`
- Auth: one of:
  - HTTP bearer
  - HTTP bearer
  - HTTP bearer

The `secret` is returned in THIS RESPONSE ONLY — the gateway stores a hash and cannot show it again (use rotate-secret if it is lost).

DATA MINIMIZATION: memory text is never sent unless `include_content` is explicitly true (default false). Ingest-originated memory events are coalesced into `ingest.completed` unless `include_ingest_memories` is true, and even then arrive batched (`events[]`, ≤100 per delivery).

Event types: `memory.created`, `memory.deleted`, `namespace.deleted`, `ingest.completed`, `ingest.failed`, `usage.threshold_reached`, `key.created`, `key.revoked`, `member.added`, `member.removed`, `webhook.test`, or `["*"]` for all. Envelopes are pinned at `api_version: 2026-07-25`.

Every delivery carries `X-InfoLang-Signature: t=<unix>,v1=<hex hmac_sha256(secret, "{t}.{raw_body}")>` (Stripe's scheme), plus `X-InfoLang-Event-Id` and `X-InfoLang-Delivery-Id`. Verify with a constant-time compare against the RAW body and reject timestamps outside ±5 minutes. During a secret rotation overlap the header carries TWO space-separated `v1=` values — accept either.

Delivery is AT-LEAST-ONCE: dedupe on `X-InfoLang-Event-Id`. Success is a 2xx within 10s; anything else retries on the ladder immediate, 30s, 120s, 600s, 3600s, 21600s (jittered). An endpoint that fails every delivery for 72h straight flips to `auto_disabled`.

Requires the `webhooks:write` scope. Accepts all three lanes: a session JWT (bounded by the caller's role on this workspace) or an `il_*` machine credential whose own scope set, workspace set and validity window cover it — both ceilings apply and neither can raise the other, so a credential carrying `webhooks:write` is still refused where the role does not permit it.

A credential with no scope record resolves to `LEGACY_DEFAULT`, which does NOT include `webhooks:write` — mint a key with it explicitly (`POST /v2/workspaces/{id}/keys`).

## Path parameters

- `id` string, required

## Request body

- object
  - `url` string, uri, required
  - `enabled_events` string[]
  - `include_content` boolean
  - `include_ingest_memories` boolean
  - `coord_event_kinds` string[]
  - `description` string
  - `status` 'enabled' | 'disabled'

## Response `201`

Created endpoint, including the one-time secret

- object
  - `webhook_id` string, required
  - `workspace_id` string, required
  - `url` string, required
  - `enabled_events` string[], required
  - `status` 'enabled' | 'disabled' | 'auto_disabled', required
  - `include_content` boolean, required
  - `include_ingest_memories` boolean, required
  - `coord_event_kinds` string[]
  - `coord_cursor` string
  - `description` string
  - `auto_disabled_reason` string
  - `created` string, required
  - `updated` string, required
  - `secret` string
  - `previous_secret_expires_at` string

## Other responses

- `401`: Missing/invalid credential, or `credential_expired` — the credential is outside its `[not_before, expires_at)` window and must be replaced
- `403`: `insufficient_scope` — the credential's own scope set, or the caller's role on this workspace, does not cover the scope this route requires (the missing scope is named in the message). Or `forbidden` — the credential is not scoped to this workspace, or holds no grant on it
- `422`: Invalid body — non-https url, private/loopback host, or an unknown event type

---

[API](https://skmtc.dev/infolang/apis/infolang-api.md) · [All operations](https://skmtc.dev/infolang/apis/infolang-api/llms.txt) · [OpenAPI document](https://skmtc.dev/infolang/apis/infolang-api/revisions/d04bf6de72c8?raw)
