GitLab webhooks: test events, headers, secret token and limits
To test a GitLab webhook, add a free webhooks.sh bin URL under Settings, Webhooks and use the Test button: each event appears live with its X-Gitlab-Event header and JSON body. No signup; a bin lasts 48 hours and takes 100 requests a day.
A GitLab webhook is an HTTP POST that GitLab sends to your URL when something happens in a project or group, like a push or a merge request, and the fastest way to see one is to paste a free bin URL into Settings > Webhooks and pick an event from the Test dropdown. You don't need to sign up, and the bin stays up for 48 hours and takes 100 requests a day. Below we cover setup and testing, headers and tokens, timeouts and limits, the push payload, and system hooks.
Send a GitLab test event to a free bin
Project webhooks need the Maintainer or Owner role on GitLab Free, Premium or Ultimate, while group webhooks need Premium or Ultimate and the Owner role.
- Open view.webhooks.page in your browser or run
curl -X POST https://webhooks.sh/v1/binsto get an ingest URL for GitLab and a secret view URL for yourself. - In your GitLab project, go to
Settings > Webhooks > Add new webhook, paste the ingest URL with a path like/gitlab/push(the bin accepts any path underhttps://<bin>.webhooks.page/), pick your trigger events, and selectAdd webhook. - Open the
Testdropdown next to your new webhook and pickPush events(your project needs at least one commit for a push test to work). - Watch the request show up live in your browser viewer with its method, path, headers and body.
# 1. Create a free bin (or open view.webhooks.page)
curl -X POST https://webhooks.sh/v1/bins
# 2. Paste ingest_url into Settings > Webhooks, for example
# https://<bin>.webhooks.page/gitlab/push
# 3. Test > Push events, then read the capture:
curl -H "x-view-key: <view key>" \
https://view.webhooks.page/api/bins/<bin>/deliveries
Headers, secret token and signing token
Every delivery includes X-Gitlab-Event to name the event, such as Push Hook or Merge Request Hook. To verify that a request came from GitLab, you have two options: the older secret token or the newer signing token.
The secret token arrives as plain text in X-Gitlab-Token, which GitLab no longer recommends for new webhooks. The signing token (introduced in GitLab 19.0 and generally available in 19.1) follows the Standard Webhooks spec: GitLab signs {webhook-id}.{webhook-timestamp}.{body} with HMAC-SHA256 and sends v1, followed by the base64 digest in webhook-signature. To verify it in your receiver, strip the whsec_ prefix from your signing token, base64-decode it to get the HMAC key, and reject old webhook-timestamp values.
One thing to know about the webhooks.sh viewer: headers with token or key in the name are automatically redacted, so X-Gitlab-Token and the legacy Idempotency-Key header show as redacted. The webhook-id header carries the exact same value as Idempotency-Key, and webhook-id, webhook-timestamp and webhook-signature all stay visible in the viewer.
| Header | Example | In a free bin |
|---|---|---|
X-Gitlab-Event | Push Hook | Visible |
X-Gitlab-Token | Your secret token, in plain text | Redacted (name contains token) |
webhook-signature | v1, plus a base64 HMAC, with a signing token | Visible |
webhook-id | Same value as Idempotency-Key | Visible |
webhook-timestamp | 1744578123 (Unix seconds) | Visible |
Idempotency-Key | UUID (legacy header) | Redacted (name contains key) |
X-Gitlab-Instance | https://gitlab.com | Visible |
import base64, hashlib, hmac
def valid_signature(token, msg_id, timestamp, body: bytes, sigs):
key = base64.b64decode(token.removeprefix("whsec_"))
message = f"{msg_id}.{timestamp}.".encode() + body
digest = hmac.new(key, message, hashlib.sha256).digest()
expected = "v1," + base64.b64encode(digest).decode()
return any(hmac.compare_digest(expected, s)
for s in sigs.split(" "))
Timeouts, auto-disabling and GitLab.com limits
GitLab.com waits 10 seconds for your server to respond. It counts any 4xx, 5xx, timeout or other HTTP error as a failure, and after 4 failures in a row it temporarily disables the webhook for 1 minute (growing up to 24 hours with further failures); after 40 consecutive failures the webhook is permanently disabled, though a Test request that returns 2xx turns it back on.
A free bin replies with 200 and {"ok":true,"delivery_id":"..."} right away, so it won't trigger auto-disabling during normal testing. The catch is the free tier caps: after 100 requests in a day the bin returns 429 (daily_cap_reached), and any payload over 100KB gets 413 (body_too_large), both of which GitLab counts as failures. Claiming your bin with a free Google sign-in keeps the same URL past 48 hours with 30 days of rolling history at the same 100 requests a day and 100KB cap, while Solo ($12/month) gives you persistent endpoints on webhooks.host with 250,000 requests per billing period, 5MB bodies, and the wsh CLI (wsh listen <endpoint> --to localhost:3000) to relay live deliveries to localhost.
| Limit | GitLab.com value |
|---|---|
| Response timeout | 10 seconds |
| Auto-disable | 4 failures in a row: 1 minute, up to 24 hours; 40 in a row: permanent |
| Webhooks per project / per group | 100 / 50 |
| Maximum payload | 25 MB |
| Calls per minute per top-level namespace | 500 on Free, 1,600 to 13,000 on Premium and Ultimate |
| Source IP ranges | 34.74.90.64/28 and 34.74.226.0/24 |
The push payload and Recent events
A Push Hook JSON body carries object_kind set to push, the branch in ref, the old and new commit SHAs in before and after, who pushed in user_username, the project object, and the commits array. If someone pushes more than 20 commits at once, commits only lists the newest 20 while total_commits_count holds the real total, and a push that changes more than 3 branches or tags at once sends no push webhook at all by default.
When you want to inspect deliveries inside GitLab's webhook settings, open the webhook's edit page and scroll to Recent events. It lists every request from the last 2 days with its status code, event and elapsed time, View details shows the request and response, and Resend Request repeats a delivery with the same Idempotency-Key.
{
"object_kind": "push",
"before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
"after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"ref": "refs/heads/master",
"ref_protected": true,
"user_username": "jsmith",
"project_id": 15,
"project": { "path_with_namespace": "mike/diaspora" },
"commits": [
{
"id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
"added": ["CHANGELOG"],
"modified": ["app/controller/application.rb"]
}
],
"total_commits_count": 4
}
GitLab system hooks
GitLab system hooks are instance-wide webhooks available only on GitLab Self-Managed and GitLab Dedicated; GitLab.com doesn't have them. An administrator sets them up under Admin > System hooks.
They fire for project, group, user and key changes, plus push, tag push, merge request and repository update events, sending X-Gitlab-Event: System Hook and the secret token in X-Gitlab-Token (which shows as redacted in a bin). You can point a system hook straight at a bin URL as long as your GitLab instance can make outgoing HTTPS requests on port 443.
FAQ
How do I test a GitLab webhook without a server?
Create a free bin at view.webhooks.page with no signup, paste the ingest URL into Settings > Webhooks, and pick an event from the Test dropdown. The request shows up live in your browser with its headers and JSON body. Testing Push events needs at least one commit in the project.
Where is the GitLab webhook secret token in the request?
GitLab sends it in the X-Gitlab-Token header as plain text. Because our viewer redacts any header whose name contains token, you'll see it redacted in a free bin and should verify the actual value in your own handler, though signing token headers like webhook-signature stay visible.
Why did GitLab disable my webhook?
GitLab.com waits 10 seconds for a reply and counts 4xx, 5xx and timeouts as failures, disabling the webhook temporarily (1 minute up to 24 hours) after 4 failures in a row and permanently after 40. You can re-enable it by sending a Test request that returns 2xx. Make sure your endpoint replies quickly with 200 or 201 and puts slow work in a queue.
Can I use system hooks on GitLab.com?
No, system hooks are only on GitLab Self-Managed and GitLab Dedicated and require an administrator to configure. On GitLab.com, use project webhooks or group webhooks (available on Premium and Ultimate).
Why doesn't my GitLab push webhook fire?
By default, a single push that changes more than 3 branches or tags at once doesn't trigger push webhooks. If it was a normal push, check Recent events on the webhook's edit page (or our troubleshooting guide) to see the status codes and check whether GitLab disabled the webhook after repeated failures.
Last updated
Try it in 10 seconds.
Create a free bin at view.webhooks.page — no signup, no install. Point your sender at the URL and watch requests land live. Bins expire after 48 hours.