---
title: "Update a webhook subscription"
method: PATCH
path: "/webhooks/{webhookId}"
tags: ["Webhooks"]
---

# Update a webhook subscription

`PATCH /webhooks/{webhookId}`

## Request

- Base URL: `https://inkseal.net/api/v1`
- URL: `https://inkseal.net/api/v1/webhooks/{webhookId}`
- Auth: HTTP bearer

Change a subscription's target URL, its event list, or whether it is active. Every field is optional; omitted fields are left alone.

A new `events` array replaces the subscription outright rather than merging; `null` means every event, including ones added later. `isActive: false` pauses delivery without destroying the subscription or its history, which is the safe way to stop a noisy endpoint while its owner repairs it.

A new `url` is put through the same HTTPS and SSRF checks as creation: private, reserved, loopback, and unresolvable destinations are refused with 400 `invalid_url`.

The signing secret does not change and is not returned. There is no way to rotate it; replace the subscription instead.

The update is applied by public id within the caller's workspace, so an id from another workspace matches nothing and answers 404 like an unknown one. Accepts an `Idempotency-Key`; a retry with the same key replays the original response instead of re-applying the update.

## Headers

- `Idempotency-Key` string

## Request body

- WebhookSubscriptionUpdateRequest: Fields to change on a subscription. All optional; omitted fields are left alone.
  - `url` string, uri: A new endpoint. Put through the same HTTPS and SSRF checks as creation.
  - `events` WebhookSubscriptionEventType[], nullable: Replaces the event list outright; not merged. Null subscribes to every event including future ones.
  - `isActive` boolean: False pauses delivery while keeping the subscription and its history.

## Response `200`

The subscription as it now stands. Timestamps are omitted from this response; read the subscription back for those.

- object
  - `data` WebhookSubscriptionUpdated, required: The subscription after the update. Timestamps are not included in this response; read the subscription back for those.
    - `id` string, required
    - `url` string, uri, required
    - `events` WebhookSubscriptionEventType[], nullable, required
    - `isActive` boolean, required

## Other responses

- `400`: The request is malformed or the resource is in the wrong state for a synchronous check. Codes: `validation_error`, `invalid_body`, `invalid_parameter`, `invalid_idempotency_key`, `invalid_event_id`, `invalid_file`, `invalid_url`, `invalid_status`, `key_mismatch`, `no_signers`, `not_completed`, `envelope_exists`, `document_not_ready`, `upload_failed`.
- `401`: The credential is missing, malformed, unknown, expired, revoked, or past its rotation grace window. Code: `unauthorized`. Every authentication failure is deliberately indistinguishable.
- `403`: The credential is valid but may not do this. Codes: `insufficient_scope` (the route's scopes are not all held; `details` names `requiredScopes` and `missingScopes`), `forbidden` (the capability check on the resolved resource failed), `scope_escalation` (a grant wider than the caller's own was requested).
- `404`: The resource does not exist, was deleted, or belongs to another workspace. Code: `not_found`. The three cases are answered identically on purpose, so a 404 never confirms that an identifier exists elsewhere.
- `409`: The resource's state forbids this transition, or another request owns the work. Codes: `invalid_state`, `invalid_status`, `credential_state`, `limit_exceeded`, `envelope_missing`, `not_retryable`, `owner_unavailable`, `workspace_unavailable`, `detection_not_completed`, `document_in_use`, `version_conflict`, `document_processing`, `document_processing_failed`, `normalized_pdf_unavailable`, `pages_not_rendered`, `document_not_ready`, `idempotency_in_progress` (which also carries `Retry-After`), `payment_replayed` (a USDC payment signature presented twice), `payment_pending` (a USDC settlement whose outcome is not yet known).
- `422`: The request parsed but its content is not acceptable. Codes: `validation_error` (envelope configuration, with an `issues` array in `details`), `invalid_envelope`, `envelope_not_ready`, `document_page_limit`, `idempotency_key_reuse`.
- `429`: A limit was reached. Codes: `rate_limited` (the credential's per-bucket HTTP limit, or the per-workspace detection throttle), `workspace_limit_exceeded` (a workspace content or send limit; `details.limit` names it), `daily_budget_exceeded` (the workspace's daily detection budget). The four rate-limit headers are sent on this response and on no successful one, so a client cannot read its remaining budget without being refused. Retrying after `Retry-After` is always safe: a rate-limit refusal happens before the handler runs.
- `503`: A dependency the request needs is unreachable and nothing was changed. Codes: `queue_unavailable` (a background job could not be enqueued; retry the call), `billing_unavailable` (Stripe would not open a checkout session; nothing was charged, retry the call), `service_unavailable` (the rate-limit store is down, so the request was refused before running).

---

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