Skip to main content

Webhook egress

Sellable product

This capability is granted by an API key scoped to the webhooks product (product face). See the product reference below.

Multi-tenant, resilient webhook egress. POST a delivery job; the service durably queues, signs (HMAC-SHA256), retries (exp-backoff + jitter, honoring Retry-After), circuit-breaks per (org, target-host), SSRF-guards, and audits every attempt to Postgres. The HMAC signing secret is resolved server-side per org (EGRESS_SECRET__<ORG> -> EGRESS_DEFAULT_SECRET, Infisical-rendered) — never accepted in-body, never persisted. v1.1 (additive, backward-compatible expand-contract): a SUBSCRIPTION REGISTRY + event-type FAN-OUT. Register endpoints (/v1/endpoints) subscribed to event_types (or '*'), then emit an event (/v1/events) and the service fans it out to every matching active endpoint — each fanned delivery is a normal durable, signed, retried delivery. Ported from vagary-platform's webhook_delivery_service subscription model + content_syndication partner fan-out. v1.2 (additive, S3 merge-gap — absorbs the vagary-platform model so the platform's webhook registry can be retired losing nothing): (a) owner_id — a WITHIN-org per-user scope (organization_id stays the outer tenant, I6). CRUD-by-id is owner-STRICT (a wrong/absent owner cannot read/update/delete an owner-set endpoint — closes the platform cross-owner AUTHZ bug); fan-out/list are owner-OPT-IN (omit = all owners in the org). Supply owner via the X-Owner-Id header or an owner_id body field. (b) signing_mode — 'org_timestamp' (DEFAULT, stronger: HMAC(per-org-secret, "<ts>.<body>") + X-Webhook-Timestamp, secret never persisted) or 'endpoint_body' (vagary-platform compat: HMAC(per-endpoint-secret, body), header 'X-Webhook-Signature: sha256=<hex>', no timestamp) with a required per-endpoint signing_secret. The per-endpoint secret is write-only — accepted on register / rotated on patch, NEVER echoed in any response.

  • Group: Comms & workflow
  • Contract: contracts/webhook-egress/v1/openapi.yaml
  • Public base: https://api.vagarylabs.com (the consolidated API gateway — one host, per-brand sibling api.<zone>)
  • Auth: a product API key (vgk_…) issued from the console — Authorization: Bearer vgk_…
  • Product face (customer-keyed):
    • https://api.vagarylabs.com/product/v1/webhooks/deliveries
  • Capability face (internal first-party — NOT customer-keyed):
    • https://api.vagarylabs.com/v1/deliveries

Endpoints

MethodPathSummary
POST/v1/deliveriesEnqueue a durable, signed, audited delivery job
GET/v1/deliveries/&#123;id&#125;Delivery status (terminal or in-flight)
POST/v1/endpointsRegister a subscription endpoint for event-type fan-out
GET/v1/endpointsList the org's subscription endpoints (active and paused)
GET/v1/endpoints/&#123;id&#125;One subscription endpoint (org-scoped)
PATCH/v1/endpoints/&#123;id&#125;Update a subscription endpoint (url / event_types / active — org-scoped)
DELETE/v1/endpoints/&#123;id&#125;Deactivate a subscription endpoint (soft-delete, org-scoped)
POST/v1/eventsEmit an event-type; fan out to every matching active subscription
GET/healthDB reachability gate (open — no auth)
GET/metricsPrometheus text exposition (open — fleet scrape + healthcheck)

Product face

The external, paying-customer surface served by the edge at /product/v1/* (contracts/webhook-egress/product/v1/openapi.yaml).

MethodPathSummary
POST/deliveriesEnqueue a durable, signed, audited webhook delivery (managed, metered)

Schemas

EnqueueRequest

FieldTypeDescription
organization_idstringPRODUCT-TIER end-customer tenant (I6). Defaults to the service default_org if omitted.
owner_idstringoptional WITHIN-org owner (per-user) audit dimension; may also be supplied via X-Owner-Id.
urlstringdelivery target (SSRF-guarded — private/loopback/link-local/metadata ranges rejected)
event_typestring
payloadobjectJSON object, <= 512 KiB serialized
custom_headersobject
idempotency_keystringdedup key — a repeat returns 200 with deduplicated=true
max_attemptsintegerclamped to [1,20]; defaults to the service delivery.maxAttempts

EnqueueAccepted

FieldTypeDescription
delivery_idstring
organization_idstring
statusobject
idempotency_keystring
deduplicatedboolean

DeliveryRecord

FieldTypeDescription
delivery_idstring
organization_idstring
owner_idstringWITHIN-org owner (per-user) if the delivery was owner-scoped
urlstring
event_typestring
statusobject
attemptsinteger
max_attemptsinteger
last_response_codeinteger
last_errorstring
endpoint_idstringsource subscription for a fanned delivery (null for a direct enqueue)
signing_modeobject
next_attempt_atstring
created_atstring
updated_atstring

DeliveryStatus

SigningMode

org_timestamp (default, stronger): HMAC(per-org-secret, "<timestamp>.<body>"), header X-Webhook-Signature=<hex> + X-Webhook-Timestamp, secret resolved server-side per org and never persisted. endpoint_body (vagary-platform compat): HMAC(per-endpoint-secret, body), header X-Webhook-Signature=sha256=<hex>, no timestamp.

RegisterEndpointRequest

FieldTypeDescription
owner_idstringoptional WITHIN-org owner (per-user); may also be supplied via the X-Owner-Id header. Owner-set endpoints are owner-strict on CRUD-by-id.
urlstringsubscription target (SSRF-guarded at both register and delivery time)
event_typesarrayevent types this endpoint receives; a single '*' subscribes to all events
signing_modeobject
signing_secretstringREQUIRED when signing_mode='endpoint_body' (the per-endpoint HMAC secret); rejected otherwise. Write-only — never returned in any response.

UpdateEndpointRequest

partial update — any subset of the whitelisted fields. signing_mode is immutable after creation; only the per-endpoint signing_secret may rotate.

FieldTypeDescription
urlstring
event_typesarray
activebooleanfalse pauses the endpoint without deleting it
signing_secretstringrotate the per-endpoint secret (endpoint_body endpoints). Write-only — never returned.

Endpoint

FieldTypeDescription
endpoint_idstring
organization_idstring
owner_idstringWITHIN-org owner (per-user)
urlstring
event_typesarray
activeboolean
signing_modeobject
created_atstring
updated_atstring

Generated by scripts/gen-capability-docs.py from contracts/webhook-egress/v1/openapi.yaml — the contract IS the source of truth; edit the contract, not this page.