---
title: "Receive GitLab Webhooks Locally | WebhookRelay"
meta:
  "og:description": "Receive GitLab webhooks locally with no public IP. Inspect the real payload, forward push and merge request events to localhost, and verify the token."
  "og:title": "Receive GitLab Webhooks Locally"
  description: "Receive GitLab webhooks locally with no public IP. Inspect the real payload, forward push and merge request events to localhost, and verify the token."
---

![Stripes](https://webhookrelay.com/blog/receive-gitlab-webhooks-locally/images/stripes.svg)

# **Receive GitLab Webhooks Locally**

Receive GitLab webhooks locally with no public IP. Inspect the real payload, forward push and merge request events to localhost, and verify the token.

![Receive GitLab Webhooks Locally (No Public IP Required)](https://webhookrelay.com/blog/receive-gitlab-webhooks-locally/images/blog/heroes/localhost.jpg)

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](#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](https://my.webhookrelay.com/register) does, and the endpoint is **stable**: configure GitLab once and forget about it.

## [Step 1: Inspect the real payload with Webhook Bin](#step-1-inspect-the-real-payload-with-webhook-bin)

Before writing handler code, see what GitLab actually sends. Open the free [Webhook Bin](https://webhookrelay.com/blog/receive-gitlab-webhooks-locally/webhook-bin/) — no signup — for an instant public URL.

1. Copy the Webhook Bin URL.
2. In your project, go to **Settings → Webhooks → Add new webhook**.
3. 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.
4. 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](https://webhookrelay.com/blog/receive-gitlab-webhooks-locally/blog/how-to-test-webhooks/) and [What is a webhook](https://webhookrelay.com/blog/receive-gitlab-webhooks-locally/blog/what-is-webhook/) for background.

## [Step 2: Forward the events to localhost with the relay agent](#step-2-forward-the-events-to-localhost-with-the-relay-agent)

Now route those events into your local handler. [Sign up for Webhook Relay](https://my.webhookrelay.com/register), 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](https://webhookrelay.com/blog/receive-gitlab-webhooks-locally/docs/webhooks/internal/localhost/).

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](#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-Event` header 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-Token`** header. That makes verification a direct comparison rather than a recomputed digest.

## [Step 3: Verify the GitLab token](#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](https://webhookrelay.com/blog/receive-gitlab-webhooks-locally/blog/verify-webhook-signature/) and the free [HMAC signature verifier](https://webhookrelay.com/blog/receive-gitlab-webhooks-locally/hmac-verification/).

## [Replay and iterate](#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-Token` header) 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](#get-started)

1. Inspect the real payload in the free [Webhook Bin](https://webhookrelay.com/blog/receive-gitlab-webhooks-locally/webhook-bin/) — no signup needed.
2. [Create a Webhook Relay account](https://my.webhookrelay.com/register), install the agent, and run `relay forward --bucket gitlab http://localhost:8080/webhook`.
3. 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.