Webhook Triggers
Configure GitHub webhooks for instant syncs on push
Webhook Triggers
By default, the operator picks up upstream changes on its ~60s reconcile. GitHub webhooks eliminate the delay — a push writes a Sync operation on every matching App, and the generation bump reconciles immediately.
How It Works
┌──────────┐ push ┌─────────────┐ patch spec.operation ┌──────────┐
│ GitHub │──────────▶│ Webhook │───────────────────────▶│ Operator │
│ │ │ receiver │ {kind:Sync, │ (apply) │
└──────────┘ │ :8082 │ triggeredBy:"…"} └──────────┘
└─────────────┘- You push to GitHub
- GitHub POSTs the push payload to
POST /webhook/githubon the operator's receiver (:8082) - The receiver lists
Apps, matchessource.git.repoURL==repository.clone_urlandbranch== pushed branch andwebhook.enabled: true - Each match gets a merge-patch on
spec.operation {kind:Sync, triggeredBy:"webhook:github"}— the reconcile fires immediately - Response:
{"matched": N}
Configuring the App
spec:
webhook:
enabled: true
# secretRef: webhook-secret # 📋 reserved — not read by the handlerNo signature validation yet. The secretRef field is schema-reserved — the handler doesn't verify X-Hub-Signature-256, so anyone who can reach the endpoint can trigger a sync on matching repos. Put it behind a private ingress or network policy until validation lands. Network-layer rate limiting is also 📋 (there's a TODO in the handler).
Configuring GitHub
Step 1: Expose the Webhook Receiver
The receiver listens on the operator's :8082 (--webhook-bind-address). Expose it with an ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wardn-webhook
spec:
rules:
- host: webhook.wardn.example.com
http:
paths:
- path: /webhook/github
pathType: Prefix
backend:
service:
name: wardn-operator
port:
number: 8082Or port-forward for development:
kubectl port-forward svc/wardn-operator 8082:8082 -n <operator-namespace>Step 2: Add the Webhook in GitHub
- Repo → Settings → Webhooks → Add webhook
- Payload URL:
https://webhook.wardn.example.com/webhook/github - Content type:
application/json - Events: just push
- Save — the
pingdelivery gets200with{"matched":0}
Step 3: Test
Push a commit — the app's spec.operation is patched within seconds:
kubectl --kubeconfig k3s.yaml get app my-app -n org-my-org \
-o jsonpath='{.spec.operation}'
# {"kind":"Sync","initiatedAt":"…","triggeredBy":"webhook:github"}GitLab — 📋 accepts but doesn't act
POST /webhook/gitlab exists but returns 200 without parsing anything — nothing matches and no syncs fire. Track the implementation in the Roadmap.
Match Rules
| Check | Behavior |
|---|---|
| Repo | Payload's repository.clone_url must equal source.git.repoURL exactly (including .git suffix — match your URLs) |
| Branch | Payload's ref (refs/heads/<branch>) must equal source.git.branch |
webhook.enabled | Must be true on the App |
| Signature | 📋 Not verified (see warning above) |
| Source types | Git only — image-source apps aren't matched (they're driven by the tag poller instead) |
Troubleshooting
Webhook not triggering sync
- Receiver logs — the handler logs
failed to set App operationon patch failures:
kubectl logs deployment/wardn-operator -n <operator-namespace> | grep -i webhook-
GitHub delivery status — Repo → Settings → Webhooks → Recent Deliveries.
200with{"matched":0}means delivery worked but nothing matched — checkrepoURL/branchequality andwebhook.enabled. -
Common issues:
{"matched":0}—clone_url≠repoURL(.gitsuffix or SSH-vs-HTTPS mismatch), wrong branch,webhook.enabledunset400— malformed JSON body (check the Content type isapplication/json)- Connection refused — ingress/network policy blocking
:8082
Related
- Configuration —
webhookspec fields - Operator Webhooks — receiver implementation
- Sync Policies — sync trigger types