Version

Webhook Security

Secret rotation, replay windows, the IP allow-list and what to trust on an inbound delivery, so nothing can spoof or replay a Zuse Bifrost event.

Outbound webhooks are the only way Zuse Bifrost talks to your infrastructure unprompted. Getting the security right prevents spoofing and replay.

Trust only signed, fresh payloads

Every delivery carries:

  • ZG-Signature: t=<unix>,v1=<hex_hmac_sha256(secret, "t.body")>
  • ZG-Event-Id: <ulid>, unique per event.
  • ZG-Event-Name: <event>, which matches payload.type.

Do three things on every inbound POST:

  1. Verify the signature with a constant-time comparison (crypto.timingSafeEqual, hash_equals, hmac.compare_digest).
  2. Reject stale deliveries: if abs(now - t) > 300 (five minutes), drop the request even when the signature matches. This is your replay window.
  3. De-duplicate on the event id: store seen ids for at least 24 hours. Retries cause the same event to arrive more than once.

Secret rotation

Rotate a webhook secret the moment a developer leaves, a laptop is lost or CI logs leak. In the app: Settings → Webhooks, the endpoint's menu, Rotate secret. The same action exists on the REST API and as an MCP tool, each behind the webhook-endpoint:update ability.

The new secret is shown once; you cannot fetch it again. Deploy the new secret to your handler before you rotate, or deliveries between the rotation and your deploy will fail verification.

There is no grace window

Rotation replaces the secret in place. The old secret stops verifying the instant the rotation completes, and every delivery after that point carries a single v1= signature made with the new one. There is no dual-signing and no overlap period.

Deploy the new secret before you rotate, not after. Anything delivered between the rotation and your deploy fails verification and is retried on the normal back-off schedule, so a slow deploy costs you time, not events.

Who may change an endpoint

Every member of the team can see the team's endpoints and their deliveries. Only the member who registered an endpoint and the team's owner can rotate, pause, resume, test or delete it; another member's attempt is refused with a message that says so, and over the API the same call answers 403. A guest of a project never sees webhooks at all.

IP allow-list

allowed_ips is an optional list of IPv4 or IPv6 addresses and CIDR ranges (for example 198.51.100.7 or 2001:db8::/32) accepted when registering an endpoint.

It is enforced on every delivery. Before a delivery is posted, the worker resolves the endpoint's hostname (or reads the literal address in the URL) and checks every address it gets back against the list. If any address falls outside it, the delivery is dead-lettered without being posted, with the refused address recorded in the delivery's response excerpt (Endpoint host resolved to 203.0.113.10, outside its allowed IPs.). Correct the list or the DNS record, then replay the dead deliveries from the deliveries page.

The list restricts where Zuse Bifrost will deliver; it does not authenticate what arrives. Keep verifying the signature on your side, and firewall the handler to the Zuse Bifrost egress if you need inbound filtering too. A hostname that does not resolve at all is not refused by the list; the connection failure walks the normal retry ladder.

TLS and public hosts

Zuse Bifrost enforces full certificate-chain validation (no self-signed bypasses) and TLS 1.2 at minimum.

Only https:// targets on public hosts are accepted at registration. Plain http:// URLs are refused, and so is any host that is a loopback, private, link-local or reserved address, or a public name that resolves to one, because the delivery worker would otherwise be reachable as a proxy into the platform's own network. A name that does not resolve at registration is refused too.

Certificate pinning is not supported. If you need it, terminate TLS on a proxy you control and connect from the proxy to your application over an internal network.

Data exposure

Webhook payloads carry Zuse Bifrost identifiers, keys, names, statuses and roles. They never include:

  • API tokens, webhook secrets or integration credentials.
  • Telegram chat identifiers or any other messaging identifier.
  • Time logs, cost rates or internal comments.
  • A guest's personal details beyond the user id their membership row names.

The page broadcast that mirrors each event to open browser tabs is scrubbed a second time: any key that looks like a secret, token, password, key or signature is dropped before it reaches the channel, and an oversized payload is replaced by a marker rather than sent.

How is this guide?

Last updated on

Version

On this page

What changed recentlyEvery release of Zuse Bifrost, with the pages it touched.

For AI agents: llms.txt · llms-full.txt