The operator creates Webhook Relay buckets, public inputs, forwarding outputs,
and an in-cluster relay agent from a namespaced WebhookRelayForward resource.
It lets a public webhook producer reach a Kubernetes Service without a public
load balancer or inbound firewall rule.
Requirements are Kubernetes, Helm 3, and a Webhook Relay access token. The operator watches its installation namespace.
helm repo add webhookrelay https://charts.webhookrelay.com
helm repo update
helm upgrade --install webhookrelay-operator webhookrelay/webhookrelay-operator \
--namespace webhookrelay --create-namespaceHelm installs the CRD on the first install but does not upgrade or delete it. Use the same explicitly pinned chart version to apply the target CRD and upgrade the operator. A Helm uninstall retains the CRD and custom resources, plus the controller-created leader-election Lease; delete custom resources first if their owned agent Deployments should be garbage-collected. Rollbacks only revert the operator resources, so remove new-only fields, scale the current operator to zero, and wait for its pods to terminate before restarting an older controller. The chart lifecycle guidance provides the version-pinned upgrade and tested rollback commands.
Create credentials in the same namespace as the custom resource. Supplying credentials through a Secret avoids placing them in Helm command history or values files.
apiVersion: v1
kind: Secret
metadata:
name: whr-credentials
namespace: webhookrelay
type: Opaque
stringData:
key: replace-with-token-key
secret: replace-with-token-secretkubectl apply -f credentials.yamlCross-namespace Secret reads are rejected. secretRefNamespace is deprecated;
omit it. Install a separate operator in another namespace for tenant isolation.
apiVersion: forward.webhookrelay.com/v1
kind: WebhookRelayForward
metadata:
name: github-to-atlantis
namespace: webhookrelay
spec:
secretRefName: whr-credentials
websocketTransport: true
resources:
requests:
cpu: 25m
memory: 32Mi
limits:
memory: 128Mi
buckets:
- name: github-to-atlantis
inputs:
- name: github
outputs:
- name: atlantis
destination: http://atlantis.atlantis.svc.cluster.local:4141/events
internal: true
lockPath: true
disabled: falseApply it and read the generated public endpoint:
kubectl apply -f forward.yaml
kubectl -n webhookrelay get webhookrelayforward github-to-atlantis \
-o jsonpath='{.status.publicEndpoints[0]}'; echoroutingStatus describes remote bucket/input/output reconciliation.
agentStatus and ready describe the generated relay-agent Deployment. A
configured route is not proof of delivery; send a uniquely identifiable test
webhook and verify it at the destination.
The following example shows the current typed options. Some Webhook Relay features depend on the account subscription.
apiVersion: v1
kind: Secret
metadata:
name: bucket-auth
namespace: webhookrelay
type: Opaque
stringData:
password: replace-with-a-password
---
apiVersion: forward.webhookrelay.com/v1
kind: WebhookRelayForward
metadata:
name: full-example
namespace: webhookrelay
spec:
secretRefName: whr-credentials
websocketTransport: true
image: registry.example.com/webhookrelayd:platform-compatible
extraEnvVars:
- name: LOG_LEVEL
value: debug
resources:
requests:
cpu: 25m
memory: 32Mi
limits:
cpu: 250m
memory: 128Mi
buckets:
- name: full-example
description: Managed by Kubernetes
stream: true
ephemeral: false
largeWebhooks: true
staticIP: false
auth:
type: basic
username: webhook-producer
secretKeyRef:
name: bucket-auth
key: password
inputs:
- name: public-endpoint
functionId: 00000000-0000-0000-0000-000000000000
responseHeaders:
X-Webhook-Receiver:
- kubernetes
responseStatusCode: 202
responseBody: accepted
responseFromOutput: application
customDomain: example.hooks.webhookrelay.com
pathPrefix: /events
stripPathPrefix: true
tlsVersion: "1.2"
legacyTLS: false
outputs:
- name: application
destination: http://receiver.default.svc.cluster.local:8080/hooks
functionId: 00000000-0000-0000-0000-000000000000
responseFunctionId: 00000000-0000-0000-0000-000000000000
internal: true
lockPath: true
disabled: false
timeout: 10
retries: 2
tlsVerification: true
overrideHeaders:
X-Managed-By: webhookrelay-operator
rules:
match:
type: value
value: push
parameter:
source: header
name: X-GitHub-Event
durability:
enabled: true
schedule: long
deadline: 720h
handoffAfter: 15m
throttle:
enabled: true
mode: concurrency
maxConcurrent: 5
maxQueueDepth: 1000
deadline: 24h
- name: replay-on-connect
destination: http://receiver.default.svc.cluster.local:8080/replay
internal: true
disabled: false
replayMissing:
enabled: true
lookback: 30m
limit: 250For token bucket authentication, use auth.type: token, omit username, and
point secretKeyRef at the token key. Use auth.type: none to explicitly
remove authentication. If auth is omitted, existing remote authentication is
preserved.
Replay-on-connect is valid only for an internal output. It cannot be enabled on
an output selected by responseFromOutput, including anyOutput; use a
dedicated output as shown above. It is also incompatible with an ephemeral
bucket because ephemeral delivery state is not retained. Durations use Go
syntax such as 30s, 15m, or 24h.
| Scope | Fields |
|---|---|
| Agent | secretRefName, deprecated secretRefNamespace, image, resources, extraEnvVars, websocketTransport |
| Bucket | name, description, stream, ephemeral, largeWebhooks, staticIP, auth, inputs, outputs |
| Bucket auth | type (none, basic, or token), username, secretKeyRef.name, secretKeyRef.key |
| Input | name, description, functionId, responseHeaders, responseStatusCode, responseBody, responseFromOutput, customDomain, pathPrefix, stripPathPrefix, tlsVersion, legacyTLS |
| Output | name, description, destination, functionId, responseFunctionId, overrideHeaders, internal, lockPath, disabled, timeout, retries, tlsVerification, rules, durability, throttle, replayMissing |
| Durability | enabled, schedule (seconds, medium, long, or custom), customDelays, deadline, handoffAfter |
| Throttle | enabled, mode (rate or concurrency), rate, interval (second, minute, or hour), maxConcurrent, maxQueueDepth, deadline |
| Replay | enabled, lookback, limit |
The operator API endpoint is an administrator setting, not a CRD field. The
Helm value apiEndpointURL (environment variable WHR_API_ENDPOINT_URL)
defaults to https://my.webhookrelay.com/v1. Because the operator sends Relay
credentials to this endpoint, configure only an absolute HTTP(S) URL that you
administer; URL user information, query parameters, and fragments are rejected.
Use functionId for both inputs and outputs. The old output-only
function_id spelling remains readable during migration but is deprecated. If
both properties are present they must have the same value. Update manifests to
functionId, apply them, and then remove function_id.
Set websocketTransport: true when outbound gRPC is restricted; the agent then
uses WebSocket over port 443. The typed field takes precedence over a legacy
WEBSOCKET_TRANSPORT entry in extraEnvVars.
spec.image controls the relay-agent image, while the Helm image.* values
control the operator image. The webhookrelay/webhookrelayd:1.37.0 and
webhookrelay/webhookrelayd-ubi8:1.37.0 images publish linux/amd64 and
linux/arm64/v8 variants, so Kubernetes selects the correct agent image on
either architecture. Use spec.image only when selecting another relay-agent
repository or tag.
- HTTP 402 from the Relay API means the manifest requested a feature that is
unavailable on the account subscription. Start with the minimal quick-start
input. In particular, remove
responseBody,responseStatusCode, andresponseHeadersbefore testing, then add static responses, custom domains, advanced TLS, large webhooks, static IP, durability, or other paid controls one at a time. routingStatus: Failedincludes API or validation details. Inspect it withkubectl -n <namespace> describe webhookrelayforward <name>and check the operator logs.- The
RoutingReady,AgentReady, and aggregateReadyconditions distinguish successful Relay API configuration from the relay-agent Deployment rollout. A condition is current only when itsobservedGenerationequalsmetadata.generation;status.readyremains as a compatibility mirror of the aggregate condition. exec format errorin the agent pod means its image architecture does not match the node. Setspec.imageto a compatible build.- If the agent cannot connect over gRPC, set
websocketTransport: trueand inspect the generated Deployment and agent logs. - Bucket authentication applies at the public input. HTTP Basic clients must send the configured username/password; token clients must send the token.
Repository guidance is in AGENTS.md, with focused operator
development and debugging skills under .agents/skills. The normal checks are:
make go-gen
make test
make golangci-lint lint
make build
make e2emake e2e creates an isolated K3s server and requires Linux, Docker, and
passwordless sudo. Pull requests use GitHub Actions. The protected production
workflow additionally creates uniquely owned Relay resources, delivers real
webhooks into the K3s receiver, and removes only the exact resources it owns.
Maintainers should follow the versioned release process;
chart publication is reproducible, collision-safe, and separately approved.
See LICENSE.
