GitHub Webhook for Instant Sync#
What This Covers#
Why ArgoCD’s default polling is slow, how GitHub webhooks replace polling with event-driven sync, and how to wire it up for a local cluster using ngrok - including a shared HMAC secret so ArgoCD can verify requests really came from GitHub.
Why Polling Is Slow#
By default ArgoCD polls your Git repo every 3 minutes. After a git push you
wait up to 3 minutes before ArgoCD notices the change and triggers a sync. For
rapid iteration this is painful.
The fix is webhooks. Instead of ArgoCD asking GitHub “anything new?”, GitHub calls ArgoCD the moment a push happens. Sync starts in seconds.
How It Works#
ArgoCD exposes a built-in webhook endpoint:
| |
It understands GitHub, GitLab, Bitbucket, and generic payloads natively - no plugin required.
| |
The Webhook Secret (HMAC-SHA256)#
GitHub signs every webhook payload using your shared secret:
| |
ArgoCD reads webhook.github.secret from the argocd-secret Kubernetes Secret and
validates this header on every incoming request. If the signature doesn’t match, the
request is rejected with 401. This prevents anyone with your ngrok URL from spoofing
a sync trigger.
Why ngrok?#
ArgoCD runs inside Rancher Desktop - it has no public IP. GitHub can’t reach
localhost. ngrok creates a temporary public HTTPS tunnel to your local port:
| |
The ngrok URL changes every time you restart the tunnel (on the free tier without a static domain). You’ll need to update the GitHub webhook URL if that happens.
Setup#
1. Generate a webhook secret#
| |
2. Store the secret in argocd-secret#
| |
ArgoCD watches this secret and picks up the change without a pod restart.
Verify it was written:
| |
3. Port-forward ArgoCD#
ArgoCD is running in insecure (HTTP) mode - TLS is terminated at Traefik. Use port 80 on the service side:
| |
Keep this running in its own terminal.
4. Start ngrok#
In a second terminal:
| |
Note the Forwarding HTTPS URL from the output - e.g.:
| |
5. Add the webhook in GitHub#
Go to: github.com/ravikrs/learning-argocd → Settings → Webhooks → Add webhook
| Field | Value |
|---|---|
| Payload URL | https://abc123.ngrok-free.app/api/webhook |
| Content type | application/json |
| Secret | the value from step 1 |
| Which events | Just the push event |
Click Add webhook. GitHub immediately sends a ping event - you should see a
green tick and a 200 response in the Recent Deliveries tab.
Verification#
1. GitHub - confirm delivery succeeded#
In the GitHub webhook settings (Settings → Webhooks → your webhook → Recent Deliveries),
each delivery shows the HTTP status ArgoCD returned. A green tick with 200 means the
request was delivered and ArgoCD accepted it. This confirms the network path works but
not that ArgoCD acted on it - GitHub has no visibility into what ArgoCD does after the 200.
2. ngrok terminal - confirm the request arrived locally#
After a git push, you should see a line appear within a couple of seconds:
| |
If you see 200 here as well as on GitHub, the full network path is confirmed:
GitHub → ngrok → argocd-server.
3. argocd-server logs - confirm ArgoCD received and processed the webhook#
This is the most direct confirmation on the ArgoCD side:
| |
A successful receipt looks like:
| |
If you want more context lines around the match:
| |
If GitHub and ngrok show 200 but this log line is absent, the request was likely
rejected silently due to an HMAC signature mismatch. Check with:
| |
4. ArgoCD CLI - confirm the app synced as a result#
| |
Look for Operation: Sync with a timestamp matching your push. Webhook-triggered
syncs appear identical to automated syncs - what you’re checking is that the timestamp
is within seconds of the push rather than up to 3 minutes later.
Alternatively via kubectl:
| |
5. Kubernetes events - confirm the operation was started#
| |
Smoke test: make a trivial change and push#
| |
Watch steps 2–4 in sequence: ngrok shows 200, argocd-server log shows the push
event, and the app’s sync timestamp updates within seconds.
Gotchas#
ngrok URL changes on every restart
Free ngrok accounts without a static domain get a new random subdomain each session. You must update the GitHub webhook Payload URL after restarting ngrok. A free ngrok account gives you one permanent static domain - worth using once you’ve verified the setup works.
Port-forward must use port 80, not 443
Because argocd-server runs in insecure (HTTP) mode, the correct service port is
80. Port-forwarding to 443 still works technically (both listeners map to the same
pod port) but sends HTTP traffic over what looks like an HTTPS port, which can confuse
tools. Be explicit with port 80.
argocd-secret is not hot-reloaded on older ArgoCD versions
On ArgoCD v2.4 and earlier, argocd-server reads argocd-secret only at startup.
Patching the secret has no effect until you restart the pod. On v2.5+ the server
watches the secret dynamically. If validation keeps failing with 401, try:
| |
GitHub sends a ping on webhook creation - that’s expected
The first delivery is a ping event with {"zen":"..."}. ArgoCD responds with 200
but does not trigger a sync. This is correct behaviour. Subsequent push events
trigger the sync.
Webhook delivers to ArgoCD, not to individual Applications
ArgoCD matches the webhook’s repository URL against all registered Applications and refreshes every Application that points to that repo. If you have multiple apps from the same repo (as this setup does), all of them get refreshed on every push - which is exactly what you want.