API changelog

Append-only record of every change to the v1 API contract, newest first.

The v1 contract is append-only: fields may be added, existing fields and code values never change meaning. Anything that would break a parser lands in a v2, not here. Newest changes come first.

New hosts for the API and capture URLs

The API and capture URLs moved to webhookvault.net. Paths, request and response shapes are unchanged; only the host differs.

  • API base URL: https://app.webhookvault.net. The OpenAPI document's servers entry names it, and the document itself is served at /openapi/v1.json. webhookvault.dev answers every path with a 301 to the same path on app.webhookvault.net. Point your client at the new host: most HTTP clients do not repeat a POST, PUT, PATCH or DELETE across a 301 (they switch to GET or stop), and many drop the Authorization header when a redirect changes host.
  • Capture URLs: an endpoint's url is now on https://in.webhookvault.net, with the same path as before. in.webhookvault.dev no longer answers, so a provider still sending to the old host needs the new URL from the endpoint.

Alert deliveries and parked-delivery alerts

  • eventKind=delivery.recovered is a valid filter on GET /api/v1/alert-channels/deliveries. It lists the notices that close a parked-delivery incident, sent when a delivery to the action succeeds again.
  • subjectId on a parked-delivery alert and on its recovered notice now holds the action's id, where it held the request's id. The same value is subject_id in a webhook channel's payload. Parked-delivery alerts also coalesce: one per action per outage, with a running count, instead of one per request. See Alerts.
  • eventKind lists every kind it accepts: the filter takes any kind a delivery can carry, action.notify included, and the OpenAPI document now enumerates all eight. Any other value answers 400 (code: validation_failed).

Replay to one action, action signing, the single-URL fields

Two additions, and one read rule stated plainly because an integrator can see it change.

  • Replay to one action: POST /api/v1/endpoints/{endpointId}/requests/{requestId}/replay accepts an optional actionId query parameter, and POST /api/v1/endpoints/{endpointId}/requests/bulk-replay an optional actionId in its body. With it, the request goes through that one action only and the endpoint's other actions send nothing. The action must belong to the endpoint (an action of any other endpoint answers 404, the same as one that never existed) and must be switched on (400, code: action_inactive). Without it, replay is unchanged: every action that is switched on.
  • Action signing: POST /api/v1/endpoints/{id}/actions and PUT /api/v1/endpoints/{id}/actions/{actionId} accept a write-only signingSecret on a forward: omitted leaves it, an empty string stops signing, a value sets or rotates it (at least 16 characters). It is never returned; the action reports only whether it is signed and since when. It replaces the endpoint's deprecated forwardSigningSecret.
  • The deprecated single-URL fields read the first forward action by position: an endpoint's forwardUrl and forwardSigning now describe the endpoint's first action of kind forward by position, whether or not it is switched on, and are null (with signed: false) when the endpoint has no forward action. They used to describe the first switched-on action of any kind, so an endpoint whose first active action was a notify read forwardUrl: "". The read now names the same row a write to these fields changes. forwardingEnabled still reads true when any action is switched on.
  • Writes to those fields: forwardingEnabled: true sent without forwardUrl to an endpoint that has no forward action is now refused (400) rather than accepted and ignored, because there is no forward to switch on. forwardingEnabled: false in the same case still answers 200 and changes nothing: no forward means nothing forwards, which is the state it asks for. forwardSigningSecret sent on its own no longer re-checks the forward's URL, so rotating it never depends on the receiver's DNS.
  • Concurrent changes: PATCH /api/v1/endpoints/{id} answers 409 (code: concurrent_change) when one of the endpoint's actions was removed by another request while the update was saving. Nothing is saved; read the endpoint again and retry. This used to surface as a 500.
  • Marked deprecated: forwardUrl, forwardingEnabled, forwardSigning and forwardSigningSecret on an endpoint carry deprecated: true in the OpenAPI document. They are still read and honoured as described above; new integrations should use the endpoint's actions (GET /api/v1/endpoints/{id}/actions).

Provider setup paths

Additive. No existing field changes meaning.

  • setupPath: each provider returned by GET /api/v1/providers now carries setupPath: where the webhook URL is entered in that provider's own console, or, for the few providers with no console field, the one step that registers it. It is null when no path is documented.

The Skipped delivery state

Additive: a new value in an existing enum.

  • Skipped joins the delivery state values on a request's latest delivery and on the state filter of GET /api/v1/endpoints/{endpointId}/requests. It means every action on the endpoint carries a condition and none matched the request, so nothing was queued. NotAttempted now means the endpoint had no action switched on when the request arrived.

Capture responses: what they may contain

A capture URL is public, so the response it returns to a sender is now limited to what a webhook acknowledgement needs. Writes that used to be accepted can now be refused.

  • responseContentType must be one media type from an allowlist: application/json or any +json type, application/problem+json, text/plain, text/csv, application/xml, text/xml, application/soap+xml, application/x-www-form-urlencoded or application/octet-stream. HTML, images and scripts are refused.
  • responseStatusCode may not be a 3xx: a capture response never redirects.
  • responseHeaders may not set Content-Type (it has its own field), cookies, redirects (Location, Refresh, Link), security policies, framing or transport headers.
  • A refused value answers 400 (code: validation_failed) on POST /api/v1/endpoints and PATCH /api/v1/endpoints/{id}, with detail naming what was refused.
  • Endpoints saved before the rule keep working: a content type outside the list is served as text/plain, a stored 3xx is served as 200, and blocked headers are dropped. Every capture response also carries X-Content-Type-Options: nosniff and a sandboxing Content-Security-Policy.

Signed forwards, computed values, two more signature schemes

Additive. No existing field changes meaning.

  • Signed forwards: a destination can carry a signing secret, and every delivery to it then leaves with a wv-signature header the receiver can verify. Set it with forwardSigningSecret on POST /api/v1/endpoints or PATCH /api/v1/endpoints/{id} (write-only, at least 16 characters; an empty string stops signing). The endpoint reports forwardSigning with signed and setAt and never the secret. See Deliveries.
  • compute: a new transformation op that writes a value worked out from the payload. fn names one function from a fixed table and args is a list of paths, literals and nested calls. It is not an expression language: no operators, variables or control flow. See Transformations.
  • Rules that keep failing: TransformationRuleView gains disabledReason, set when WebhookVault switches a rule off after it faults on consecutive deliveries, and null otherwise. A new alert event, transformation.disabled, is available on GET /api/v1/alert-channels/event-kinds and can be subscribed to.
  • Verification: Discord (Ed25519) and Wise (RSA-SHA256) are now verified rather than reported as unsupported, so their verification.supported flips to true. signatureAlgorithm accepts rsa-sha256, rsa-sha512 and ed25519 for a manual scheme, where signingSecret then holds a public key rather than a shared secret.

Watchdogs

Additive. A new tag, Watchdogs, and no change to any existing route.

  • Watchdogs: GET / POST /api/v1/watchdogs, GET / PUT / DELETE /api/v1/watchdogs/{id}, and POST /api/v1/watchdogs/{id}/active to pause or resume one without deleting it. A watchdog reports its state, whether it is active, and pausedByPlan when it is kept but over the plan's limit and therefore not being evaluated.
  • Cadence: GET /api/v1/watchdogs/suggest?endpointId=… proposes an interval and grace from what the endpoint has actually received. available: false with a reason when there is too little history to propose anything.
  • Labels: GET /api/v1/watchdogs/labels?endpointId=… lists the event labels an endpoint has sent, with counts, for building a match that will fire.
  • See Watchdogs.

Alert channels

Additive. A new tag, Alert channels, and no change to any existing route.

  • Channels: GET / POST /api/v1/alert-channels, GET / PUT / DELETE /api/v1/alert-channels/{id}. A channel reports health (untested, verified, failing), whether a credential is set and when, and what it is subscribed to. Credentials are write-only: secret is accepted on write and returned by nothing. Omit it on update to keep the stored one.
  • Discovery: GET /api/v1/alert-channels/kinds lists the destinations this workspace can add, with credentialLabel naming the credential each one needs and any extra fields; GET /api/v1/alert-channels/events lists the event kinds a channel can subscribe to.
  • Test: POST /api/v1/alert-channels/{id}/test sends a real message and answers what the provider said. A rejected send is 200 with delivered: false and the provider's own words in detail; the request succeeded, the send did not.
  • History: GET /api/v1/alert-channels/deliveries returns every send, newest first, filterable by channel, event kind and outcome. emailFallback marks an alert that went by email because a channel was failing.
  • Email needs no channel and cannot be created as one: it is on for every plan and is managed from workspace contacts. See Alerts.

The recycle bin

DELETE on a captured request changes meaning: it no longer removes the request.

  • Deletes move requests to the workspace recycle bin, restorable for up to 7 days or until the plan's retention window ends, whichever comes first. This applies to DELETE …/requests/{id}, POST …/requests/bulk-delete and DELETE …/requests. Their response shapes are unchanged; a deleted request still answers 404 everywhere and is gone from every list, search and replay.
  • Recycle bin (new tag): GET /api/v1/recycle-bin lists what is in the bin with purgesAt per row, and POST /api/v1/recycle-bin/restore puts requests back by id and answers restoredCount plus skippedAtCap (rows left in the bin because their endpoint is at the plan's stored-request limit).
  • Only the retention sweep or a purge applied by WebhookVault support removes a deleted request for good. See Recycle bin.

Transformation sets, versions, full delivery records

Additive; the singular transformation routes keep working.

  • Transformations become an ordered set per destination: GET / POST …/endpoints/{id}/transformations, GET / PUT / DELETE …/transformations/{ruleId}, PUT …/transformations/order, POST …/transformations/preview-set. Every rule carries position, enabled, condition and version; PUT takes { rule, enabled }. New problem code on POST: limit_reached (409).
  • Versions: GET …/transformations/{ruleId}/versions and POST …/versions/{n}/restore (a restore is a new version; history is never rewritten).
  • The singular …/endpoints/{id}/transformation routes now address the first rule of the set; their view gains ruleId and ruleCount.
  • Deliveries (new tag): GET …/endpoints/{id}/deliveries lists every attempt on the endpoint; GET …/deliveries/{attemptId} returns exactly what was sent (method, URL, headers, body) and exactly what came back (status, headers, body, content type), with truncation flags. Delivery attempts carry hasDetail.
  • transformationVersion on an attempt is set only when exactly one rule was in force; with several, transformNote names each rule and version.

Verification and transformations

Additive only; every existing field keeps its meaning.

  • Requests carry verification: { verdict, note }, the signature verdict computed at capture time (verified, failed, unsigned, unverifiable, or null when nothing was checked). A verification query filter joins method, state and q on list and await (verification=none selects unchecked requests).
  • Endpoints carry a read-only verification object (scheme in force, whether a secret is set, handshake token flags, the manual scheme) and accept write-only inputs on create and update: signingSecret, handshakeToken, signatureHeader, signatureAlgorithm, signatureEncoding, signaturePrefix. The secret is never returned.
  • Providers carry verification: { supported, scheme, secretLabel } (null when the provider does not sign) and handshakeTokenNeeded.
  • Delivery attempts carry transformed, transformationVersion and transformNote.
  • Transformations (new tag): GET /transformations/schema, POST /transformations/preview, and GET / PUT / DELETE …/endpoints/{id}/transformation. See Transformations. New problem code on PUT: no_destination (409).

v1 launch

The first public surface:

  • Introspection: GET /me: key, workspace, plan, limits.
  • Endpoints: list, create (including ephemeral endpoints via ttlSeconds), get, PATCH partial update, delete, and GET …/stats (storage, last traffic, retention, delivery backlog).
  • Requests: list with search-lite filters (method, state, q, since, until, afterId), get, delete, clear, bulk delete.
  • Await: GET …/requests/await: block until a matching webhook arrives (200) or time out clean (204). No polling.
  • Replay: single replay with inline outcome, full per-request attempt history, and bulk replay queued through the delivery pipeline (plan-gated).
  • Contract: RFC 9457 problems with stable codes everywhere; one pagination envelope; per-key rate limits with honest Retry-After 429s.

The OpenAPI document this reference renders from is public: /openapi/v1.json.

On this page