Skip to main content
Version: v5.6.1

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​

FieldMeaning
idThe row id (UUID).
codeThe 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_nameHuman-readable name.
service_accountThe 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_secondsThe 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_minuteThe per-connection request budget (default 60, range 1-6000); requests beyond it answer 429 before any verification or refusal event is spent.
secretWrite-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_setWhether a signing secret is stored.
enabledWhether the endpoint accepts deliveries; a disabled connection refuses 403 connection_disabled, loudly.
hook_pathThe endpoint path deliveries POST to: /api/hooks/<code>.
created_at / updated_atTimestamps.

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-Delivery is the dedup id and X-GitHub-Event the 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> with X-OpsChain-Timestamp (unix seconds, enforced against the replay window) — the exact signature OpsChain's own outbound notification webhooks send. X-OpsChain-Delivery is the dedup id, X-OpsChain-Event the 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.