Webhooks And Secret Refs
Breyta webhook security guide for authenticated ingestion with target-bound secrets.
Goal
Run webhook-interface flows safely with explicit auth config, secret refs, and predictable payload constraints.
Quick Answer
Use :invocations for the callable webhook payload, then expose webhook ingress
with :interfaces {:webhook [...]}. Bind secret material through target
bindings.
Canonical Interface Shape
{:requires [{:slot :webhook-signing-secret
:type :secret
:label "Webhook Signing Secret"}]
:invocations {:order-updated
{:inputs [{:name :order-id
:type :text
:label "Order ID"
:required true}]}}
:interfaces {:webhook [{:id :order-webhook
:invocation :order-updated
:enabled true
:event-name "orders.updated"
:description "Receive order update webhooks."
:auth {:type :hmac-sha256
:header "Stripe-Signature"
:secret-ref :webhook-signing-secret}}]}}
Notes:
- older event-style webhook definitions are still accepted for existing flows
- webhook paths are generated by Breyta; inspect the generated interface endpoint
for the target you are testing instead of hardcoding:pathin the flow source - the interface details panel shows the generated webhook path and whether the
referenced secret binding is configured - author draft/live endpoints are flow-source scoped:
/api/flows/{flow-slug}/interfaces/draft/{interface-id}
and
/api/flows/{flow-slug}/interfaces/live/{interface-id} - installed consumer endpoints are installation scoped:
/api/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id} - workspace-scoped interface endpoint forms remain available as alternate
compatibility URLs when you need an explicit workspace id in the path - legacy
:config :fieldswebhook payload validation is still accepted for
existing flows, but new payload contracts should be expressed as invocation
inputs
Secret Slot Setup
The :requires secret slot in the canonical shape above declares where the
webhook signing secret is bound.
Bind secret values through flow configuration; never hardcode secret strings in flow files.
Auth Modes
:hmac-sha256for providers like Stripe/GitHub:signaturefor generic signed payload schemes (HMAC/ECDSA):bearerand:api-keyfor token/header auth:noneshould be avoided on public webhook endpoints
GitHub X-Hub-Signature-256 example:
{:auth {:type :signature
:algo :hmac-sha256
:signature-header "X-Hub-Signature-256"
:signed-message :payload
:signature-format :hex
:signature-prefix "sha256="
:secret-ref :github-webhook-secret}}
GitHub signs the exact raw request body and sends the hex digest with a
sha256= prefix.
Payload Limits That Matter
- webhook total payload max: 50 MB
- signed multipart webhook payload max: 5 MB
- raw MIME field payload max: 50 MB
Rotation And Operations
- rotate by updating bound secret values, then rerun with the target (
flows run --target livewhen validating live behavior) - if replay detection trips, ensure providers send unique signatures/event ids
- preserve raw request body for signature verification paths
Draft Vs Live Webhook Behavior
Webhook behavior follows runtime target selection:
| Aspect | draft | live |
|---|---|---|
| Intended usage | Dev/staging-style iteration while authoring. | Stable installed runtime ingress for consumers. |
| Definition/config source | Latest pushed working copy + draft target configuration. | Installed released version + live/installation configuration. |
| Endpoint stability expectation | Can change frequently during iteration. | Treated as stable runtime surface between controlled release/promote events. |
| Endpoint shape | Flow-source scoped with draft in the path. | Flow-source scoped with live in the path for author testing; installed consumer endpoints stay installation scoped. |
Guidelines:
- Do not derive webhook URLs from slug/event-name assumptions.
- Treat server-returned interface endpoints as authoritative for the target you are testing.
- For installation/live ingress, inspect interface metadata with
breyta flows installations interfaces <installation-id>. - For keyed concurrency, choose a
:key-fieldthat exists in the raw incoming
webhook payload before the flow body normalizes input. For GitHub pull
request events, prefer:numberor[:pull_request :id]over a later derived
request key.
Runtime Surface Strategy By Target
Use runtime surfaces differently across targets:
| Target | Preferred runtime surface during development/operations | Why |
|---|---|---|
draft | Manual interface runs first; optional webhook testing via generated draft interface endpoint | Fast iteration and deterministic debugging. |
live | Top-level schedules for unattended jobs, plus installation/live webhook interfaces | Stable, continuous runtime behavior for consumers. |
Draft webhook interface endpoints are workspace-authenticated and source scoped:
POST /api/flows/{flow-slug}/interfaces/draft/{interface-id}
Legacy draft-event routes are still accepted for older tooling, but new clients
should use the generated interface endpoint.
Example:
curl -X POST "$BREYTA_API_URL/api/flows/orders/interfaces/draft/orders-updated" \
-H "Authorization: Bearer $BREYTA_TOKEN" \
-H "Content-Type: application/json" \
-d '{"orderId":"ord_123"}'
Common Pitfalls
- missing
:authon webhook interface -> auth error at runtime :authpresent but missing:type-> validation error- signature auth without raw body preservation -> verification fails
Frequently Asked Questions
Should webhook auth live in interface config or in :requires?
Both. Interface config declares auth behavior; :requires declares where secret material is bound.
Can I keep webhooks unauthenticated in production?
No. Public webhooks should use authenticated interface config.