Guides

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:"…"}    └──────────┘
                       └─────────────┘
  1. You push to GitHub
  2. GitHub POSTs the push payload to POST /webhook/github on the operator's receiver (:8082)
  3. The receiver lists Apps, matches source.git.repoURL == repository.clone_url and branch == pushed branch and webhook.enabled: true
  4. Each match gets a merge-patch on spec.operation {kind:Sync, triggeredBy:"webhook:github"} — the reconcile fires immediately
  5. Response: {"matched": N}

Configuring the App

spec:
  webhook:
    enabled: true
    # secretRef: webhook-secret   # 📋 reserved — not read by the handler

No 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: 8082

Or port-forward for development:

kubectl port-forward svc/wardn-operator 8082:8082 -n <operator-namespace>

Step 2: Add the Webhook in GitHub

  1. Repo → Settings → Webhooks → Add webhook
  2. Payload URL: https://webhook.wardn.example.com/webhook/github
  3. Content type: application/json
  4. Events: just push
  5. Save — the ping delivery gets 200 with {"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

CheckBehavior
RepoPayload's repository.clone_url must equal source.git.repoURL exactly (including .git suffix — match your URLs)
BranchPayload's ref (refs/heads/<branch>) must equal source.git.branch
webhook.enabledMust be true on the App
Signature📋 Not verified (see warning above)
Source typesGit only — image-source apps aren't matched (they're driven by the tag poller instead)

Troubleshooting

Webhook not triggering sync

  1. Receiver logs — the handler logs failed to set App operation on patch failures:
kubectl logs deployment/wardn-operator -n <operator-namespace> | grep -i webhook
  1. GitHub delivery status — Repo → Settings → Webhooks → Recent Deliveries. 200 with {"matched":0} means delivery worked but nothing matched — check repoURL/branch equality and webhook.enabled.

  2. Common issues:

    • {"matched":0} — clone_url ≠ repoURL (.git suffix or SSH-vs-HTTPS mismatch), wrong branch, webhook.enabled unset
    • 400 — malformed JSON body (check the Content type is application/json)
    • Connection refused — ingress/network policy blocking :8082

On this page