Webhook API
Webhooks deliver committed repository changes to an HTTP endpoint outside the mutation request.
Target schema
Each target belongs to one index:
| Field | Contract |
|---|---|
name | Unique target name within the index |
url | HTTP or HTTPS endpoint without user info, query, or fragment |
secret_env | Environment variable that contains the HMAC secret |
secret | Literal HMAC secret, used instead of secret_env |
events | Optional allowlist of event names exposed by the ecosystem owner |
An empty or omitted event list receives each event the owner exposes. Ecosystem guides define event names and payload schemas.
Secret strength
Peryx uses the UTF-8 bytes of the resolved secret or secret_env value as the HMAC-SHA256 key. The value must contain
at least 32 bytes. RFC 2104 section 3 recommends a key at least
as long as the hash output, which is 32 bytes for SHA-256. Length does not supply entropy; generate each target secret
from a cryptographic random source.
Generate 32 random bytes as hexadecimal and create the output with owner-only permissions:
$ umask 077
$ openssl rand -hex 32 > peryx-webhook-secret
Set secret_env to an environment variable containing that file's value, or set secret to the value itself. Peryx
checks the resolved value during startup and check-config.
Delivery envelope
Every delivery uses POST with an owner-defined JSON body and these headers:
| Header | Contract |
|---|---|
Content-Type | application/json |
User-Agent | peryx/<version> |
X-Peryx-Event | Owner-defined event name |
X-Peryx-Delivery | Stable delivery identifier across retries |
X-Peryx-Timestamp | Unix second used to sign this attempt |
X-Peryx-Signature | sha256=<hex> HMAC-SHA256 signature |
Consumers must ignore unknown payload fields so implementations can extend their schemas without breaking receivers.
Signature contract
peryx signs these exact bytes with the target secret:
<timestamp>.<delivery-id>.<raw-json-body>
Receivers compare the HMAC in constant time and reject timestamps outside their replay window. Re-serializing the body before verification changes the signed bytes.
Delivery contract
peryx stores each delivery before a background worker sends it. A process restart retains queued work. A 2xx response
completes delivery. Transport failures and HTTP 5xx responses retry with the same delivery ID; 408 and 429 use the
same retry path. A valid Retry-After response delays the next attempt when it is later than peryx's local backoff, and
the stored deadline survives a process restart. Other 4xx responses are final.
Redirects are final after the first attempt. peryx neither follows nor retries them because sending the signed payload
to a target-selected location could move it outside the configured origin. A 302 stores
webhook target returned redirect 302; redirects are not followed in the delivery log.
Delivery is at least once and does not preserve mutation order. If the receiver accepts a request but peryx loses the
process before recording its result, the next process may send the request again. Retries keep the same
X-Peryx-Delivery value so receivers can deduplicate them.
The delivery log stores the target name, attempt count, next retry, response status, and bounded error text. It excludes secrets, signatures, credentials, URL queries, and response bodies.