Inbound webhook connection
Included from Basic tier
An instance-level connection object: the signed endpoint
(POST /api/hooks/<code>) an external system fires OpsChain triggers
through. The admin surface is InboundWebhookService (API v2,
PostgreSQL-backed), gated on the /admin updatable determination; the CLI
is opschain admin inbound-webhooks. The hook endpoint itself is
unauthenticated by design except the HMAC signature — the signature is
the authentication — and every firing a delivery causes executes as the
connection's service_account, never as the matching trigger's owner.
Data dictionary
| Field | Meaning |
|---|---|
id | The row id (UUID). |
code | The estate code (1-64 of a-z0-9_-, immutable): the /api/hooks/<code> address, and the data.connection value on every event the connection lands. |
display_name | Human-readable name. |
service_account | The username every trigger firing this connection causes executes — and is authorization-checked — as. A missing, disabled or unauthorised account settles firings failed by name; it never falls back to the trigger owner. |
replay_window_seconds | The replay window (default 300, range 30-86400): a signed X-OpsChain-Timestamp outside it refuses stale_timestamp, and delivery ids dedup exactly-once (the ledger keeps ids far beyond the window). |
rate_per_minute | The per-connection request budget (default 60, range 1-6000); requests beyond it answer 429 before any verification or refusal event is spent. |
secret | Write-only (create/update requests only, at least 16 characters): the HMAC-SHA256 signing secret, encrypted at rest and never echoed by any read, audit row or error. |
secret_set | Whether a signing secret is stored. |
enabled | Whether the endpoint accepts deliveries; a disabled connection refuses 403 connection_disabled, loudly. |
hook_path | The endpoint path deliveries POST to: /api/hooks/<code>. |
created_at / updated_at | Timestamps. |
The delivery wire contract
A delivery is one POST /api/hooks/<code> with a JSON body of at most
1 MiB, signed one of two ways:
- GitHub shape —
X-Hub-Signature-256: sha256=<hex>, HMAC-SHA256 over the raw body;X-GitHub-Deliveryis the dedup id andX-GitHub-Eventthe envelope event type. Provider webhooks configured with the connection's secret work unchanged. - Generic shape —
X-OpsChain-Signature: v1=<hex>, HMAC-SHA256 over<timestamp>.<body>withX-OpsChain-Timestamp(unix seconds, enforced against the replay window) — the exact signature OpsChain's own outbound notification webhooks send.X-OpsChain-Deliveryis the dedup id,X-OpsChain-Eventthe envelope event type.
Without a delivery-id header, the body's SHA-256 digest dedups. An accepted
delivery answers 202 with delivery_id and event_id; every named
refusal answers its typed 4xx (401 missing_signature | missing_timestamp | stale_timestamp | bad_signature, 403 connection_disabled | no_signing_secret, 404 unknown_connection, 409 replayed_delivery,
400 payload_not_json, 413 payload_too_large, 429 rate_limited) and —
rate limiting aside — a warn:webhooks:refused event. The accepted
delivery's event is external:webhook:received; triggers bind to it as
opschain docs triggers describes.