GitLab Webhook: Example Payload, Headers and Signature

The JSON a GitLab project webhook posts on a push, the headers that describe it, and the secret token that is compared rather than computed.

No webhook URL yet. Creating one needs no account.

Request details
Requests sent to your webhook URL appear here: method, headers and the body exactly as it arrived.

The button posts the example below to your webhook URL from your browser. It is not sent by GitLab, it carries your browser's User-Agent, and its signature is a placeholder that will not verify. To see a real delivery, follow the steps further down.

Last updated . How this page is checked.

How the webhook URL works. It is a public address that records whatever is sent to it and answers 200. An inbox keeps the 50 most recent requests, accepts bodies up to 256 KB, and is deleted 24 hours after it was created — or sooner if you press Delete URL or our server restarts, because requests are held in memory and never written to disk.

Anyone who has the URL can read what it received. Use sandbox and test-mode accounts, do not route production traffic or customer data through it, and delete it when you are done. Privacy details.

What is a GitLab webhook?

A GitLab webhook is an HTTP POST sent to a URL configured on a project or group when a selected trigger fires — a push, a tag, a merge request change, a pipeline status, a comment. The body's object_kind names the event and the same information appears, in human form, in the X-Gitlab-Event header.

It behaves the same on GitLab.com and on a self-managed instance, with one practical difference: a self-managed administrator can change timeouts and limits, so the numbers you observe may not be the defaults.

What a GitLab webhook request looks like

The request line and headers as they reach your server, followed by the body. Ids, tokens and the signature are placeholders.

POST /your/webhook/path HTTP/1.1
Host: your-server.example
X-Gitlab-Event: Push Hook
X-Gitlab-Token: your-secret-token
X-Gitlab-Event-UUID: 13792a34-cac6-4fda-95a8-c58e00a3954e
X-Gitlab-Webhook-UUID: 8d7d0f8b-1b0c-4b4e-9d2a-2f5d1c6e7a90
X-Gitlab-Instance: https://gitlab.com
Idempotency-Key: 9f3d2c1b-7a6e-4d5c-8b9a-0e1f2a3b4c5d
{
  "object_kind": "push",
  "event_name": "push",
  "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
  "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
  "ref": "refs/heads/main",
  "ref_protected": true,
  "checkout_sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
  "user_id": 4,
  "user_name": "John Smith",
  "user_username": "jsmith",
  "project_id": 15,
  "project": {
    "id": 15,
    "name": "Diaspora",
    "web_url": "https://gitlab.example.com/mike/diaspora",
    "path_with_namespace": "mike/diaspora",
    "default_branch": "main"
  },
  "commits": [
    {
      "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
      "message": "Fix retry backoff in webhook worker",
      "timestamp": "2026-01-01T12:00:00+00:00",
      "url": "https://gitlab.example.com/mike/diaspora/-/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
      "author": {
        "name": "John Smith",
        "email": "jsmith@example.com"
      },
      "added": [],
      "modified": [
        "worker/retry.rb"
      ],
      "removed": []
    }
  ],
  "total_commits_count": 1
}

The commits array is capped at a small number of entries for large pushes; total_commits_count tells you how many there really were. Merge request, pipeline and note events use the same outer fields but put their detail in object_attributes.

Headers GitLab sends

HeaderExampleWhat it is
X-Gitlab-EventPush HookHuman-readable event name: Push Hook, Tag Push Hook, Merge Request Hook, Pipeline Hook, Note Hook…
X-Gitlab-Tokenyour-secret-tokenThe secret token you configured, sent as is. Present only when one is set.
X-Gitlab-Event-UUID13792a34-cac6-4fda-95a8-c58e00a3954eId of the event. Shared by every webhook the same event triggers.
X-Gitlab-Webhook-UUID8d7d0f8b-1b0c-4b4e-9d2a-2f5d1c6e7a90Id of this individual webhook request.
X-Gitlab-Instancehttps://gitlab.comBase URL of the GitLab instance that sent it.
Idempotency-Key9f3d2c1b-7a6e-4d5c-8b9a-0e1f2a3b4c5dStable across retries of the same webhook — the de-duplication key.

Header names are case-insensitive. Your framework may show them lower-cased, and HTTP/2 always does.

The secret token is compared, not computed

GitLab's classic mechanism is the simplest of any major provider: whatever you type into the webhook's Secret token field is sent back to you verbatim in the X-Gitlab-Token header. There is no HMAC and nothing is derived from the body, so the header proves the sender knows the token but says nothing about whether the body was altered in transit. That makes HTTPS — with certificate verification left on — a requirement rather than a nicety.

Compare the header to your stored token in constant time. If the webhook form on your GitLab version offers a signing option in addition to the plain token, prefer it and follow the documentation for that version for the header and scheme — this page describes the token mechanism, which every version supports.

const crypto = require('crypto');

function verifyGitLab(req, secretToken) {
  const a = Buffer.from(req.headers['x-gitlab-token'] || '');
  const b = Buffer.from(secretToken);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Timeouts and auto-disabling

GitLab.com gives an endpoint ten seconds to respond, per its documentation at the time of writing. GitLab does not wait for your processing and does not care about the response body, so return a 2xx immediately.

A webhook that keeps failing is automatically disabled. Repeated 5xx responses or timeouts disable it temporarily with a growing back-off; repeated 4xx responses are treated as a configuration problem and can disable it until someone re-enables it by hand. A disabled hook is marked in the project's webhook list, and its Recent events page shows each request and response so you can resend one after fixing the receiver.

Catch a real GitLab webhook

  1. Press Create webhook URL at the top of this page and copy the address.
  2. In the project, open Settings → Webhooks and add a new webhook.
  3. Paste the address, enter a throwaway secret token to see the header, and tick the triggers you want.
  4. Save, then use the webhook's Test menu to send a sample push event.
  5. Read the headers and JSON in the inbox above.

To run a caught request against your own code, select it and press Open in API Tester: the method, headers and body are loaded into the request console, where you enter your server's public URL and send it. A body you edit will no longer match its signature — that is the signature doing its job.

Things that catch people out

  • Test events are built from real data. The Test button uses the project's latest commits, so an empty repository cannot send a test push.
  • Returning 4xx for "not interested" backfires. It counts toward disabling the hook. Return 200 for events you choose to ignore.
  • Local addresses are blocked by default. A self-managed instance refuses to call private network URLs unless an administrator allows it — an SSRF protection, not a bug.

api-tester.com is independent and is not affiliated with GitLab. Providers change header names, limits and retry schedules; treat the official GitLab documentation as the source of truth.

Related examples and tools

Other webhooks that send a plain secret token

References

These links leave api-tester.com, which is independent of the companies and projects they point to.

GitLab webhook FAQ

What does a GitLab webhook payload look like?

A JSON object whose object_kind names the event. A push includes before, after, ref, user and project details, a commits array and total_commits_count; other events put their detail in object_attributes.

How do I verify a GitLab webhook?

Compare the X-Gitlab-Token header with the secret token you configured, using a constant-time comparison. The token is sent as is and is not an HMAC of the body, so always use HTTPS.

Which header contains the GitLab event type?

X-Gitlab-Event, with values such as Push Hook, Merge Request Hook or Pipeline Hook. The body's object_kind field carries the same information in machine form.

Why was my GitLab webhook disabled?

GitLab automatically disables webhooks that fail repeatedly. Server errors and timeouts disable it temporarily; repeated 4xx responses can disable it until it is re-enabled manually.

How long does GitLab wait for a webhook response?

Ten seconds on GitLab.com according to its documentation at the time of writing. Self-managed instances can be configured differently.