Receive GitLab Webhooks Locally (No Public IP Required)
Receive GitLab webhooks locally with no public IP. Inspect the real payload, forward push and merge request events to localhost, and verify the token.
You are wiring up a GitLab integration — a chatops bot, a custom deploy trigger, a pipeline notifier — and you need to watch your handler react to a real push or merge_request event. But there is an immediate wall: GitLab will only POST to a public URL, and your handler is running on localhost:8080 behind a NAT or a corporate firewall with no public IP.
Deploying to a staging server for every change is slow. Copying a sample payload from the docs gives you a guess, not the real headers and body GitLab sends. What you want is to receive GitLab webhooks locally — real events hitting your local handler, over a URL that does not change every time you restart.
Here is how to set that up.
Why receiving GitLab webhooks locally is tricky
A webhook is just an HTTP request GitLab sends to a URL when something happens in your project. GitLab is on the public internet; your dev machine almost never is. It sits behind a router or firewall with no inbound ports open and no routable address.
So you need a public endpoint GitLab can hit that relays each request down to your laptop — without opening any firewall ports. That is what Webhook Relay does, and the endpoint is stable: configure GitLab once and forget about it.
Step 1: Inspect the real payload with Webhook Bin
Before writing handler code, see what GitLab actually sends. Open the free Webhook Bin — no signup — for an instant public URL.
- Copy the Webhook Bin URL.
- In your project, go to Settings → Webhooks → Add new webhook.
- Paste the URL into the URL field and select the trigger events you want — Push events, Merge request events, Pipeline events, Tag push, and so on.
- Save the webhook, then click Test and pick an event (for example, Push events). GitLab immediately sends a sample payload.
The sample lands in Webhook Bin instantly. Inspect the full JSON body and every header, including X-Gitlab-Event (which event fired) and X-Gitlab-Token (your secret). Now you know the exact data shape before writing a line of code. See How to test webhooks and What is a webhook for background.
Step 2: Forward the events to localhost with the relay agent
Now route those events into your local handler. Sign up for Webhook Relay, install the relay agent (CLI or Docker), and create a bucket — say gitlab. The bucket gives you a stable public input endpoint.
Start forwarding to your local server:
relay forward --bucket gitlab http://localhost:8080/webhook
The agent opens an outbound connection to Webhook Relay and streams every incoming request down to http://localhost:8080/webhook. Because the connection is outbound, there are no firewall ports to open and no public IP needed — this works from your laptop, behind a corporate proxy, or inside a Kubernetes cluster. The official webhookrelay/webhookrelayd Docker image runs the same command. Full details are in the localhost forwarding docs.
Now update the GitLab webhook's URL to your Webhook Relay endpoint (or create it there from the start), hit Test again, and watch the event arrive on localhost.
GitLab-specific configuration and quirks
A few GitLab details to get right:
- Where to add it: project Settings → Webhooks for a single project, or group Settings → Webhooks to cover every project in the group.
- Trigger events: tick the events you need — push, tag push, merge request, pipeline, issue, comment, deployment, and more. Use the
X-Gitlab-Eventheader to branch in your handler. - The Test button: GitLab can send a representative sample for any trigger type on demand. This is the fastest way to confirm your endpoint is reachable and your handler parses the body — no need to manufacture a real push.
- Secret token, not a signature: this is the key difference from GitHub. GitLab does not compute an HMAC over the body. Instead, it sends the secret token you configured verbatim in the
X-Gitlab-Tokenheader. That makes verification a direct comparison rather than a recomputed digest.
Step 3: Verify the GitLab token
Because GitLab sends the raw token, verification is straightforward: read the X-Gitlab-Token header and compare it against the secret you stored, using a constant-time comparison to avoid timing attacks. Reject the request if it does not match.
Two notes:
- Unlike GitHub's
X-Hub-Signature-256, there is no HMAC to recompute — GitLab's value is the secret itself, so do not run it through a hashing step. - Always serve your endpoint over HTTPS so the token is not exposed in transit. Webhook Relay endpoints are HTTPS by default.
For the broader pattern — and the GitHub-style HMAC approach if you also handle signed providers — see Verify a webhook signature and the free HMAC signature verifier.
Replay and iterate
This is where local development gets fast:
- Re-fire from GitLab with the Test button to send a fresh sample event whenever you want.
- Replay from Webhook Relay — past requests are stored on your bucket, so you can resend a captured event (including its
X-Gitlab-Tokenheader) without touching GitLab at all. - Iterate on your handler by editing code and replaying the same event until it behaves correctly. No staging deploys, no merge requests just to test a code path.
Because the Webhook Relay endpoint is stable, you can stop and restart the agent, reboot your machine, or pick this back up next week — the GitLab configuration never needs to change.
Get started
- Inspect the real payload in the free Webhook Bin — no signup needed.
- Create a Webhook Relay account, install the agent, and run
relay forward --bucket gitlab http://localhost:8080/webhook. - Point your GitLab webhook at the stable endpoint, hit Test, and watch the event hit
localhost.
You will be testing real GitLab events against your local handler in minutes — no deploys, no open firewall ports, and no public IP required.
