Authentication
Authorization: Bearer <session token>, verified as
/api-reference/overview describes. Reading
subscriptions needs
workspace.read; writing and deleting need connection.manage.
The write endpoints deliberately do not use the framework’s own validation
errors. FastAPI’s 422 body echoes the rejected input, which would reflect
encrypted material and URLs back to the caller, so the body is parsed only after
authorization and every refusal collapses to one message:
List subscriptions
GET /api/v1/workspaces/{workspace_id}/connections/{connection_id}/webhooks
string
required
Connection to read.
404 connection not found when it is not in this
workspace.array
Public subscription projections. Encrypted credential material is never
returned.
string
wsub_-prefixed id.string
The exact registered HTTPS callback, or
null. It is configuration, never
derived from Host or forwarded headers.object
The bundle-declared shape:
signature_profile, header,
timestamp_tolerance_s, event_types, provider_event_id, and optional
upstream_id.object
Metadata only —
{version, not_before, not_after}. The ciphertext, wrapped
DEK, and scheme are dropped on the way out.object
The same metadata for the outgoing key during rotation, or
null.Create or replace a subscription
PUT /api/v1/workspaces/{workspace_id}/connections/{connection_id}/webhooks/{subscription_id}
The body is a WebhookSubscription document
(packages/proto/webhook_subscription.schema.json), validated with unknown
properties closed off.
string
required
Must match the path segment and the
^wsub_ pattern, and must be 1–128
characters of [A-Za-z0-9_.-].object
required
Must equal the expected spec exactly — the connector bundle’s
webhook block
for a native connection, or the reviewed profile’s spec for a federated one.
signature_profile must exist in the reviewed registry
(packages/connectors/catalog/webhook-profiles.json) and its provider must
match the connector slug, or the broker for a federated connection.
timestamp_tolerance_s must be greater than 0 and at most 900, and
event_types must hold 1–32 entries matching [A-Za-z0-9_.:/-]{1,128}.object
required
{version, credential, not_before, not_after}. version is a 1–128 character
safe label, the validity window is greater than zero and at most 30 days, and
credential is an EncryptedCredential whose scheme is exactly
{"type": "bearer"}, whose last4 is at most 4 characters, and whose
ciphertext_b64 and dek_wrapped_b64 decode to 28–16384 bytes.object
The outgoing key. Its version must differ from the current key, it must start
before the current key does, it must not outlive it, and its overlap past the
current key’s start is capped at 24 hours.
string
Required for the
square_sha256, hubspot_v3, and contentful_sha256
profiles, which sign the destination. Must be HTTPS, at most 2048 characters,
with no userinfo, query, or fragment.Constraints
- Bodies over 128 KiB are
413 webhook configuration is too large. - A connection holds at most 16 subscriptions.
- A native connection must be
obolcustody with anopen_apitarget; a federated connection must carry anangoorcomposiobroker and a well-formed upstream connection id. - Replacing an existing subscription cannot change its
notification_url, redefine retained key versions or their encryption envelopes, move a validity start, or extend a validity end. Rotate with new secret material rather than re-encrypting the same plaintext.
200 with the connection’s full subscription list, in the same public
projection the read returns. A webhook.configure audit event is recorded.
Sealing happens outside this API. The gateway ships an
obol-gateway seal-webhook-key command that seals bounded, non-interactive
stdin under the gateway KEK without starting a service; its output is what goes
in credential. A dashboard secret-entry flow and remote sealing are separate
work (ADR-0041).Delete a subscription
DELETE /api/v1/workspaces/{workspace_id}/connections/{connection_id}/webhooks/{subscription_id}
Removes one subscription and answers 200 with the remaining list. Records a
webhook.delete audit event.
What ingest does with this
The gateway loads the published subscription from the snapshot, rate-limits known-subscription traffic, caps bodies at 256 KiB, decrypts the signing key in-process, and verifies the signature before parsing. Verified events are appended to a workspace-qualified evidence stream with replay markers; invalid signatures and replay collisions produce bounded telemetry and never evidence or a contradiction. Keys and raw payloads never enter the evidence stream, a receipt, or control-plane memory. Because GitHub authenticates neither its event-family header nor a timestamp, the gateway derives supported families from the signed payload structure, rejects conflicting unsigned family headers, requires a UUID-shaped delivery id, and deduplicates the signed body digest as well as the provider id. That does not prove freshness of a previously unseen payload. See /receipts/webhooks for the evidence model and /receipts/evidence for what each trust class means.Receipt delivery recovery
Webhook evidence and receipts reach control over a durable at-least-once audit stream. ADR-0042 makes that recovery lossless: pending deliveries are reclaimed in bounded pages from a persisted scan position, each receipt is projected with a single upsert conditioned on a strictly greater revision, and receipt listings page on a versioned opaque cursor over(occurred_at, receipt_id) rather than a
timestamp alone.
There is no HTTP endpoint for that recovery — it is a worker. What you see of it
is the next_cursor and next_before contract on
/api-reference/operator and
/api-reference/routing.