Webhooks And Secret Refs
Breyta webhook security guide for authenticated ingestion with profile-managed secrets.
Goal
Run webhook-triggered flows safely with explicit auth config, secret refs, and predictable payload constraints.
Quick Answer
Use an event trigger with :config {:source :webhook ... :auth ...} and bind secrets through flow profile bindings.
Canonical Trigger Shape
{:triggers [{:type :event
:label "Order webhook"
:enabled true
:config {:source :webhook
:event-name "orders.updated"
:path "/hooks/orders"
:auth {:type :hmac-sha256
:header "Stripe-Signature"
:secret-ref :webhook-signing-secret}}}]}
Notes:
- flows-api also accepts
:type :webhookand normalizes it - canonical stored trigger form is
:type :event+:source :webhook
Secret Slot Setup
{:requires [{:slot :webhook-signing-secret
:type :secret
:label "Webhook Signing Secret"}]}
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
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 | Not guaranteed to match live endpoint values. | Not guaranteed to match draft endpoint values. |
Guidelines:
- Do not derive webhook URLs from slug/event-name assumptions.
- Treat server-returned trigger endpoints as authoritative for the target you are testing.
- For installation/live ingress, inspect trigger endpoints with
breyta flows installations triggers <installation-id>.
Trigger Strategy By Target
Use trigger types differently across targets:
| Target | Preferred trigger during development/operations | Why |
|---|---|---|
draft | :manual runs first; optional webhook testing via draft-event route | Fast iteration and deterministic debugging. |
live | :schedule (for unattended jobs) and installation/live webhooks | Stable, continuous runtime behavior for consumers. |
Draft webhook test route (workspace-auth):
POST /:workspace-id/api/events/draft/*event-path
Example (when trigger path is /hooks/orders):
curl -X POST "$BREYTA_API_URL/$BREYTA_WORKSPACE/api/events/draft/hooks/orders" \
-H "Authorization: Bearer $BREYTA_TOKEN" \
-H "Content-Type: application/json" \
-d '{"orderId":"ord_123"}'
Common Pitfalls
- missing
:authon webhook trigger -> 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 trigger config or in :requires?
Both. Trigger config declares auth behavior; :requires declares where secret material is bound.
Can I keep webhooks unauthenticated in production?
No. Public webhooks should use authenticated trigger config.