GitLab webhooks and system hooks for event-driven automation

Learn when to use project, group or instance-wide system hooks in self-managed GitLab, how to sign payloads with HMAC-SHA256, and how to handle webhooks that GitLab disables after repeated failures.

On this page

How GitLab webhooks and system hooks send events to your automation

When a team needs GitLab events to trigger tooling outside GitLab, the first decision is scope: a webhook fires for events in one project or group, while a system hook fires for events across the whole instance. Webhooks are HTTP callbacks that GitLab sends to a URL you name, and project owners or group maintainers can usually configure them without involving the platform team. System hooks are different. Only administrators configure them, and they watch lifecycle events such as user, group, project and SSH key changes across every namespace on the instance. A system hook is the right tool for identity or audit automation that must see everything, but it also gives its receiver a view of the whole installation, so the receiving service needs to be treated as a privileged system. Before building, list each event the automation needs, the namespace it belongs to, and whether any team other than the platform team should be able to change the subscription.

The security model depends on the token type you choose. The older secret token is sent in plain text in the X-Gitlab-Token header, and the GitLab webhooks documentation does not recommend it for new webhooks. A signing token is the recommended method: GitLab computes an HMAC-SHA256 signature over the payload following the Standard Webhooks specification, so your receiver can verify that the body was not altered and came from GitLab. Your receiver should verify the signature against the raw request body before parsing it, reject requests that fail verification, and store the signing token in your secrets manager rather than in a repository. Confirm in your own environment which GitLab version and tier your group webhooks require, since the webhooks documentation lists group webhooks as a Premium and Ultimate feature. Keep the full reference open while you build, starting with the GitLab webhooks documentation.

Most failures in this area come from the receiver, not from GitLab, so plan for them early. GitLab temporarily disables a project or group webhook after four consecutive failures and disables it permanently after 40, so a short outage in your automation can silently stop events until someone re-enables the hook. Build monitoring that alerts when expected events stop arriving, make the receiver idempotent because deliveries can be retried, and document who re-enables a disabled hook and how. Self-managed administrators can also change webhook rate and timeout limits, which is worth reviewing if your receiver responds slowly. Ask your team to confirm the current limits for your installation, and to test a disabled-hook scenario in a non-production instance before relying on the automation in production.

What you need before configuring hooks in self-managed GitLab

Webhooks and system hooks only work when the GitLab tier, the administrator role, the receiving endpoint and the network path all line up. Confirm each of these before anyone writes a hook configuration, because a gap in any one of them usually shows up later as silent delivery failures.

GitLab tier for group webhooks

Group webhooks are a Premium or Ultimate feature, so a Free instance can only use project-level webhooks. Check the tier on the instance and the license before designing the hook layout, since a team that expects group-wide events will otherwise have to create the same hook in every project. Confirm the current feature availability against the installed release, as tier boundaries can change between versions.

Administrator access for system hooks

System hooks are configured by instance administrators and cover events across the whole instance, such as user, group, project and SSH key lifecycle changes. Decide which people hold administrator rights before rollout, because a system hook can receive sensitive account events. Record who owns the receiving service so that failures and changes have a clear contact.

A receiver that verifies signatures

The receiving endpoint should check the signature on every request before it acts on the payload. Prefer a signing token that produces an HMAC-SHA256 signature over the legacy plain secret token, which GitLab sends in the X-Gitlab-Token header. Plan how the receiver stores the signing secret, how it rotates that secret, and how it rejects requests with missing or invalid signatures.

Endpoint reachability from GitLab

GitLab must be able to open an outbound connection to the receiver, which means routing, firewall rules and any egress allowlist on the GitLab host must permit that destination. Self-managed instances often restrict outbound requests, so confirm the webhook destination is allowed in the instance settings. Test with a non-production endpoint first to confirm the path works end to end.

TLS certificates and trust

Deliveries to an HTTPS endpoint depend on a certificate chain that the GitLab host trusts, including internal certificate authorities used in private networks. Check that the certificate covers the exact hostname in the hook URL, is not expired, and that the chain is installed on the receiver. A certificate problem often appears as repeated delivery failures, which can lead GitLab to disable the hook.

Version confirmation before configuration

Hook behavior, available settings and limits can change between GitLab releases, so confirm the exact version on the instance against the webhooks and system hooks documentation before writing configuration. Note the current minor and patch level in the change record, and retest hooks after each upgrade. This check also covers the rate and timeout limits that administrators can adjust on self-managed instances.

Choosing between project, group and system hooks

Project webhooks attach to a single repository and fire for events in that project only, such as pushes, merge requests, pipelines and issues. A maintainer with the right project permissions configures them in the project settings, which makes them the simplest option when one team owns one repository and wants a notification or pipeline trigger tied to its work. Because the scope is narrow, a failing endpoint affects only that project's automation.

  • Who configures it: project maintainers or owners, without involving the instance administrator.
  • Event breadth: repository-level events for that project only, so cross-project visibility is not available.
  • Failure impact: limited to the project, which keeps blast radius small during testing.
  • Governance cost: every project needs its own hook, which becomes hard to audit across many repositories.
  • Tier: available in the standard project settings for self-managed GitLab.

Before relying on a project webhook for control or audit purposes, confirm with the webhooks documentation which events it sends and who can change its settings in your permission model.

How to configure a signed webhook step by step

This sequence takes a receiver from endpoint design to a verified test delivery, so that every event your automation accepts has passed a signature check tied to a secret you control.

  1. Define the receiver and its endpoint

    Decide which service will receive GitLab events, the HTTPS URL it will expose, and who owns the code that verifies requests. Confirm that the endpoint is reachable from your GitLab instance, which may sit on a private network, and that your firewall rules allow the outbound call. Record the owner and the URL in your change record. The step is done when the receiver team can show a health route that returns a success code to a plain request from the GitLab host.

  2. Choose the webhook scope

    Pick a project webhook, a group webhook or an instance-wide system hook based on which events the automation needs. Group webhooks are a Premium and Ultimate feature, so confirm your tier before planning around them. System hooks are configured by administrators and cover lifecycle events across the whole instance. Write down the scope and the reason for it, because changing scope later means recreating the hook and retesting it. The step is done when the scope decision is recorded with its owner.

  3. Generate and store the signing token

    Create a signing token for the new webhook rather than relying on the older secret token, which GitLab sends in plain text in the X-Gitlab-Token header and does not recommend for new webhooks. Store the value in your secrets manager with restricted read access, and do not paste it into tickets or chat. Record the secret's name and rotation owner. The step is done when the receiver can read the token from the secrets manager at runtime and the value is absent from source control and logs.

  4. Select the triggers

    Enable only the event types the receiver needs, such as push or merge request events for a project hook, or user, group, project and SSH key lifecycle events for a system hook. Keep the list short so that failures are easier to trace and the receiver handles less traffic. Note that system hooks do not fire by default when one push includes more than 3 branches or tags, so check whether your workflow depends on that case. The step is done when the trigger list and this push limitation are documented for the receiver team.

  5. Verify signatures in the receiver

    Configure the receiver to recompute the HMAC-SHA256 signature over the raw request body using the stored token, following the Standard Webhooks approach that GitLab documents for signing tokens. Compare signatures in constant time, reject requests with missing or mismatched headers, and check the timestamp header to limit replay. Parse the JSON only after the signature passes. The step is done when a unit test with a known payload produces the expected result.

  6. Send a test event and confirm delivery

    Use the test function in the GitLab webhook settings to send a sample event, then confirm the receiver logs a successful delivery with a success response code and the expected event type. Check the delivery history in GitLab for the status of that request. The step is done when the receiver accepts the signed test event and the delivery shows as successful on the GitLab side.

  7. Confirm rejection of a bad signature

    Send a deliberately altered request, such as a changed body or a wrong signing value, to the same endpoint and confirm the receiver rejects it and records the reason without processing the payload. Repeat with a missing header. This negative test proves the verification is active and not just present in code. The step is done when both bad requests are refused and the refusals appear in your receiver logs.

  8. Monitor failures and automatic disabling

    Document how GitLab handles failing deliveries: it temporarily disables project or group webhooks after four consecutive failures and permanently after 40, and self-managed administrators can adjust rate and timeout limits. Add an alert on delivery failures in your monitoring system and assign someone to re-enable hooks after the receiver is fixed. The step is done when the alert routes to a named owner and the re-enable procedure is written down.

Why HMAC-SHA256 signing replaces the plain secret token

The older secret token is sent as-is in the X-Gitlab-Token header on every delivery. Anyone who can read that header in transit, or in a proxy or access log on the receiving side, holds a credential that can make the receiver accept forged events. GitLab does not recommend the secret token for new webhooks. For any webhook you create today, use a signing token instead. With a signing token, GitLab computes an HMAC-SHA256 signature over the payload, so the receiver can confirm two things: the message came from a holder of the shared key, and the body was not altered after sending. The key itself is not sent with the request. Confirm the current signature header names and the timestamp handling in the GitLab webhooks documentation before you write the receiver, because your verification code must match them exactly.

On the receiver, verification runs before any business logic. Read the raw request body exactly as received, without re-serializing parsed JSON, because even whitespace changes will break the computed digest. Recompute the HMAC-SHA256 digest with your stored signing token, then compare it with the signature from the request using a constant-time comparison function. Also check the delivery timestamp against a tolerance window so that a captured request cannot be replayed later. Reject anything that fails and log the rejection with the webhook ID and source address, so a spike in failures is visible to the team that owns the integration. A common mistake is to verify against a web framework's parsed body, which passes in testing and fails in production.

Treat the signing token as a credential. Store it in your secrets manager, restrict read access to the service that verifies deliveries, and keep it out of CI/CD variables that many jobs can read. Plan rotation before go-live: GitLab lets you change the token on the webhook, so the receiver should be able to accept the old and new key for a short overlap, update the webhook, confirm successful deliveries, then retire the old key. Record who owns each token and where it is stored. Note that a webhook with repeated failures is temporarily disabled and can be permanently disabled, so a mismatched key after rotation can silently stop your automation, and the failure-disable behavior covered elsewhere on this page makes that cutover risk concrete.

Event mapping between GitLab objects and downstream systems

This table maps common GitLab events to the downstream system that usually consumes them, the hook scope that emits them, the identifiers worth storing, and the team that should own each mapping. Read it before you create any hook, because the scope you pick decides which events arrive at all. Events marked as system scope only cannot be replaced by a project or group webhook.

EventTypical consumer and hook scopeIdentifiers to storeOwner of the mapping
Push eventsCI trigger or security scanner; project or group webhook, with system hooks skipping pushes over 3 branches or tags by defaultproject ID, project path, ref, before and after commit SHAsPlatform engineering
Merge request eventsReview bot or ticket sync; project or group webhookproject ID, merge request IID, source and target branch, author IDDevelopment team leads
Pipeline and job eventsDeployment tracker or observability platform; project or group webhookproject ID, pipeline ID, job ID, commit SHA, environment nameDevOps or release engineering
User lifecycle eventsIdentity governance or HR-driven deprovisioning; system hook onlyuser ID, username, email, creation and block stateIdentity and access management
Group and project lifecycle eventsAsset inventory or compliance register; system hook for instance-wide coveragegroup ID, full path, project ID, visibility levelGitLab administrators
SSH key lifecycle eventsAccess review or key inventory; system hook onlykey ID, user ID, key fingerprint, creation dateSecurity operations
Group membership changesAccess reporting; group webhook or system hook, confirm which events each scope emitsgroup ID, user ID, access levelIdentity and access management

How GitLab disables failing webhooks and how to recover

How many consecutive failures does GitLab allow before disabling a webhook?

GitLab temporarily disables a project or group webhook after four consecutive failures. The webhook stays disabled for a period, and GitLab does not send it events during that time. If the endpoint keeps failing, GitLab disables the webhook permanently after 40 consecutive failures, so a receiver outage that lasts longer than the temporary window can end in a permanent disable. Check the webhook's status on the project or group settings page before assuming it is still active. Review the GitLab webhooks documentation for the current behavior, since the thresholds are set by GitLab and should be confirmed against your version.

Can administrators change the webhook rate and timeout limits on self-managed GitLab?

Yes. On self-managed instances, administrators can change the webhook rate and timeout limits, which control how many webhook calls GitLab makes and how long it waits for each receiver to respond. Tight timeouts can cause a slow but healthy receiver to count as a failure, which pushes the webhook toward the four-failure temporary disable. Loose limits can let a slow receiver tie up delivery capacity for longer. Set these values with your receiver's normal response time and peak load in mind, and confirm the setting names and defaults in the GitLab webhooks documentation for your version before changing them. Treat any change as a configuration decision to record and test.

How do I monitor webhook failure history in GitLab?

Start with the webhook list in the project or group settings, where GitLab shows recent delivery attempts and their response codes for each hook. Use this to find the first failure in a streak, because that timestamp tells you when the receiver started to fail. Pair it with your receiver's own logs and a health check that records each incoming request, so you can match GitLab's view with what actually arrived. For self-managed instances, also review the instance logs and the system hook configuration, since system hooks have their own delivery behavior. Confirm the exact screens and retention of delivery history with your GitLab version, because the detail available can change between releases.

How do I re-enable a webhook that GitLab disabled?

Fix the cause on the receiver first, then re-enable the webhook in the same settings area where it was configured. Re-enabling before the receiver is healthy usually leads to another run of failures and a faster return to the disabled state. Send a test event from the webhook settings and confirm the receiver returns a success response and logs the payload. If the webhook was permanently disabled, GitLab may require you to recreate it, so check the status shown in the interface and the current documentation before planning recovery. Record the time of the outage and the fix so the event replay step below has a clear window.

How do I replay events that a webhook receiver missed during an outage?

GitLab does not replay missed webhook deliveries for you in the way a message queue would, so recovery is usually a receiver-side task. Identify the outage window from your logs and the GitLab delivery history, then pull the current state of affected objects through the GitLab API, such as merge requests, pipelines, issues or group membership. Reprocess those objects through the same handler your webhook uses, and make the handler idempotent so repeated events do not create duplicate tickets or deployments. For events that matter for audit, such as user or SSH key changes from system hooks, reconcile against the instance's own records. Confirm the API endpoints and rate limits you need with your GitLab version before writing the replay job.

Who should own webhook recovery: our team or a partner?

Your team usually owns the receiver, because the outage, fix and replay logic all sit in your application and your data. A partner can help when the work is fixed in scope: designing the handler, adding idempotency, building the replay job, and documenting the runbook. Ask for the scope in writing, including which receivers, which event types and which GitLab versions are covered, and who runs the tests. Ongoing monitoring and upgrade work after GitLab version changes can be handled separately as a retainer. DeployOpen plans, implements and supports self-hosted GitLab in your own infrastructure, so the work runs in your environment.

What drives the effort of setting up failure monitoring and replay?

The main drivers are how many receivers depend on GitLab events, how idempotent those handlers already are, and how well outages are logged today. A team with a single receiver and clear logs has a much smaller job than one with many consumers that create tickets, deployments or access changes. Other factors include whether replay must cover system hook events with audit impact, how much API volume the replay needs under your rate limits, and how the work is tested in a non-production instance. Ask any provider to break the quote into design, handler changes, replay tooling, testing and documentation so you can see what each part costs and what recurs.

Securing webhook endpoints and the signing token

A webhook is a standing door from your GitLab instance into your automation, so its security depends on the receiver, the signing secret and the scope of each hook. The cards below cover the controls that connect those pieces, from storing and rotating the token to limiting what outbound requests can reach.

Store and rotate the signing token

Treat the signing token as a credential. Keep it in a secrets manager that the receiver can read at runtime, not in a repository, pipeline variable visible to every job, or chat thread. Record which hook uses which token so rotation touches one hook at a time. Confirm with your team how a rotation is coordinated, because the receiver must accept the new token before the old one stops working. Prefer the signing token over the older secret token, which GitLab sends in plain text in the X-Gitlab-Token header.

Verify signatures before acting

A receiver should compute the HMAC-SHA256 signature over the raw request body and compare it in constant time before parsing or acting on the payload. Reject requests that fail the check with no side effects. Check the receiver with a test delivery from GitLab and with a deliberately wrong secret, so you know the rejection path works before go-live.

Allowlist receivers and require TLS

Restrict where webhooks can be sent, using the outbound request controls your administrators set on a self-managed instance. Accept deliveries only over HTTPS with a certificate your team manages, and confirm which internal certificate authority, if any, GitLab must trust. Keep receiver endpoints on a network segment that only GitLab and the automation platform can reach.

Limit SSRF through outbound requests

Webhooks make the GitLab server send HTTP requests to addresses that users may configure. Without limits, a project maintainer could point a hook at internal services such as metadata endpoints or administrative interfaces. Review the outbound request settings in the hardening guidance, decide whether local network addresses are allowed, and ask who can create or edit hooks on each project and group.

Use timestamps to block replays

A captured delivery can be resent later, so the receiver should check the timestamp and message identifier that accompany a signed delivery and reject messages outside an agreed tolerance. Keep a short record of recently seen identifiers so duplicates are dropped. Confirm the exact header names and the Standard Webhooks behavior against the current GitLab documentation before you write the check.

Choose the narrowest scope

A project hook sees one repository, a group hook sees every project below the group, and a system hook sees instance-wide lifecycle events for users, groups, projects and SSH keys. Start each automation at the lowest level that supports it, and reserve system hooks for administrators. Review the event list on each hook so it receives only what the receiver needs.

Audit system hook changes

System hooks change what the whole instance tells external systems, so their creation, edits and deletion should be visible to the security team. Export the current hook list on a schedule, compare it with an approved baseline, and investigate unexpected endpoints. Confirm with your administrators how instance audit events are retained and forwarded to your log platform.

Common webhook failures and how to fix them

This table lists the failures most often seen with GitLab webhooks and system hooks, with the usual cause and fix for each. Read it as a triage guide: match the symptom in your receiver logs or GitLab hook history to a row, then check the cause before changing settings. The main takeaway is that automatic disabling is a symptom of a repeated failure, so fix the receiver first and then re-enable the hook.

FailureLikely causeFix
Signature mismatchReceiver verifies with the legacy secret token or a different key than the signing token, or it hashes a re-serialized body instead of the raw bytesUse the signing token to compute HMAC-SHA256 over the raw request body following the Standard Webhooks headers, and compare in constant time
Certificate trust errorSelf-managed GitLab cannot validate the receiver's TLS certificate, often because it uses an internal certificate authorityInstall the internal CA on the GitLab host trust store, or confirm the chain is complete, then test delivery again
TimeoutReceiver takes longer than the configured timeout, or does slow work synchronously before respondingReturn a fast acknowledgment, queue the work, and ask your administrator to review the timeout setting if the receiver is legitimately slow
Receiver returns errorsReceiver responds with 4xx or 5xx codes, for example from a bad route, an auth check on the webhook endpoint, or an unhandled exceptionCheck the receiver logs for the failing request, fix the endpoint, and replay a test event from the hook settings
Events missing from system hooksA single push includes more than 3 branches or tags, and the default behavior skips that push's hook eventsConfirm the default with your administrator, and if you need those events, ask for the threshold to be reviewed or reconcile missing refs through the API
Hook temporarily disabledFour consecutive failures disable a project or group webhook temporarily; continued failures lead to permanent disabling after 40Fix the root cause, send a test event, and re-enable the hook; watch for repeat failures to avoid permanent disabling
Hook not firing at allWrong trigger selected, the hook is attached to the wrong scope, or a system hook is expected at project levelVerify the trigger list and scope: project or group webhooks for one scope, system hooks for instance-wide events

Questions teams ask before building GitLab webhooks

What drives the effort of a GitLab webhook project?

The main drivers are how many events you need, how many receivers consume them, and which scope each hook covers. A single project webhook sending merge request events to one receiver is a small task. Dozens of group and project hooks feeding several systems, with different payload handling and retry expectations, is a larger one. System hooks add a further layer because they see events across the whole instance, so filtering, access control and volume planning matter more. Ask for a written scope that lists each event type, each receiver, each owner and the hook level it will use before you compare quotes.

Can our own team build GitLab webhooks without a partner?

Yes, a capable team can build webhooks alone, especially for a small set of project hooks. The harder part is usually coordination rather than code: someone must decide the scope, sign payloads, handle receiver failures and agree on who changes instance settings. Teams that lack a GitLab administrator with access to system hooks, or receiver owners willing to verify signatures and respond quickly, tend to get stuck. A partner can take on design, build and handover, and the split of work should be written into the scope.

What do GitLab administrators and receiver owners need to provide?

GitLab administrators control system hooks, webhook rate and timeout limits on self-managed instances, and the integration settings that decide what can be enabled. Receiver owners control the endpoints that accept payloads, the secret or signing token storage, and the handling of retries and duplicate events. Before work starts, confirm who can create group webhooks, who approves instance-level hooks, and who will receive alerts when a hook is disabled. Missing ownership on either side is a common cause of delays.

How long does a GitLab webhook implementation take?

Duration depends on scope, so no fixed figure applies across projects. The inputs that move the timeline are the number of events, the number of receivers, whether system hooks are involved, and how many approvals sit between the team and the GitLab administrators. Ask for a timeline in the written scope that separates design, build, testing against a staging receiver and handover. Confirm the timeline against your own change process, since production changes to instance settings often need a scheduled window.

Do group webhooks require a Premium or Ultimate subscription?

Yes, according to the GitLab webhooks documentation, group webhooks are a Premium and Ultimate feature. The GitLab pricing page lists Premium at 29 US dollars per user per month billed annually, and Ultimate is custom priced. If your current tier is Free, you can still use project webhooks, but group-level event routing would need a tier change first. Confirm the edition on your instance before designing the hook layout, because the choice between project, group and system hooks affects both cost and the number of receivers you maintain.

Why does a webhook stop firing, and how do we catch it?

GitLab temporarily disables a project or group webhook after four consecutive failures and disables it permanently after 40, so a broken receiver can silently stop receiving events. Monitoring should check the hook status in the GitLab interface, alert on repeated failures from the receiver side, and track whether events are arriving on schedule. Self-managed administrators can also adjust webhook rate and timeout limits, which should be reviewed when receivers slow down. Agree on a runbook that names who re-enables a hook and how the cause is fixed first.

How do we verify that webhook payloads are authentic?

Use a signing token rather than the older secret token. The secret token is sent in plain text in the X-Gitlab-Token header and is not recommended for new webhooks. A signing token instead computes an HMAC-SHA256 signature that follows the Standard Webhooks specification, which lets the receiver confirm that the payload came from GitLab and was not altered. Your receiver must verify that signature before acting on any event. Confirm the header names and verification steps against the current GitLab documentation during build, since they are the security control your receiver depends on.

Why did a system hook not fire for a large push?

By default, system hooks do not fire when a single push includes more than 3 branches or tags, according to the GitLab system hooks documentation. Teams often discover this during testing when a bulk push produces no event. Decide whether that default suits your automation, and document the behavior so receivers do not assume every push is reported. System hooks also cover user, group, project and SSH key lifecycle changes, so filter them carefully to avoid unnecessary load on receivers.

How DeployOpen scopes and delivers GitLab webhook integrations

DeployOpen plans, builds, documents and supports GitLab webhooks and system hooks inside your own self-managed GitLab instance and infrastructure. We start from the events your automation needs, then agree the hook level, signing method and failure handling in writing before any build begins.

How we scope

  • We map each event your automation depends on to a project, group or system hook level.
  • We record which receiving endpoints, owners and networks each hook needs, in a written scope.
  • We agree signing, failure handling and rate limit settings with your security and platform teams before build.

How we build

  • We configure HMAC-SHA256 signing tokens for new webhooks and verify signatures at each receiving endpoint.
  • We set up system hooks for instance-wide lifecycle events and test the push-threshold behavior with your branch patterns.
  • We test failing endpoints so you see how temporary and permanent webhook disabling affects your automation.

How we hand over

  • We deliver a documented hook inventory showing owner, level, events, signing method and receiving service for each hook.
  • We write runbooks for re-enabling disabled webhooks and for rotating signing tokens without interrupting delivery.
  • We train your engineers to review webhook delivery logs and to change rate and timeout limits where your administrators allow it.

How we support

  • Engagements are fixed-scope, with scope, deliverables and timelines agreed in writing before work starts.
  • Ongoing support, upgrades and continuous testing are available as a retainer after handover.
  • We can review webhook changes when GitLab releases affect event payloads or hook behavior in your environment.

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.