doc/user/project/integrations/webhooks.md
{{< details >}}
{{< /details >}}
Webhooks connect GitLab to your other tools and systems through real-time notifications. When important events happen in GitLab, webhooks send that information directly to your external applications. Build automation workflows by reacting to merge requests, code pushes, and issue updates.
With webhooks, your team stays synchronized as changes occur:
Various events in GitLab can trigger webhooks. For example:
GitLab.com enforces webhook limits, including:
For GitLab Self-Managed, administrators can modify these limits.
GitLab limits webhook triggers for push events that include multiple changes:
push_event_hooks_limit
setting through the application settings API.If you frequently push multiple tags or branches simultaneously and need webhook notifications, contact your GitLab administrator to increase this limit.
{{< details >}}
{{< /details >}}
Group webhooks are custom HTTP callbacks that send notifications for events across all projects in a group and its subgroups.
You can configure group webhooks to listen for:
If you configure identical webhooks in both a group and a project in that group, both webhooks are triggered for events in that project. This allows for flexible event handling at different levels of your GitLab organization.
Create and configure webhooks in GitLab to integrate with your project's workflow. Use these features to set up webhooks that meet your specific requirements.
{{< history >}}
webhook_signing_token. Enabled by default.{{< /history >}}
[!flag] The availability of this feature is controlled by a feature flag. For more information, see the history.
For new webhooks, use a signing token instead of a secret token. The signing token computes an HMAC-SHA256 signature over the payload, so your endpoint can verify both the authenticity and integrity of the request. The secret token only provides a plain-text value in a header, which offers weaker guarantees. The secret token is not recommended for new webhooks.
Create a webhook to send notifications about events in your project or group.
Prerequisites:
To create a webhook:
X-Gitlab-Token HTTP header and provides weaker
security guarantees than a signing token. Use the signing token instead for new webhooks.{{< history >}}
webhook_signing_token. Enabled by default.{{< /history >}}
[!flag] The availability of this feature is controlled by a feature flag. For more information, see the history.
Use a signing token to verify that webhook payloads originate from GitLab and have not been tampered with. Unlike the secret token, the signing token is used to compute an HMAC-SHA256 signature over the payload. This means recipients can independently verify both the authenticity and integrity of the received payload.
GitLab webhook delivery follows the Standard Webhooks
specification. Every webhook request includes the webhook-id and webhook-timestamp headers.
When a signing token is configured, GitLab also includes the webhook-signature header with the
HMAC-SHA256 signature. Each signature has the format v1,{base64_signature}. The header may
contain multiple space-separated signatures. GitLab currently sends one signature, but this
may change in the future. The signature is computed over the string
{message_id}.{timestamp}.{body}, where:
{message_id} is the value of the webhook-id header.{timestamp} is the value of the webhook-timestamp header.{body} is the raw JSON request body.To verify the signature in your webhook endpoint:
webhook-id, webhook-timestamp, and webhook-signature header values.webhook-signature value on spaces to get the list of signatures."{message_id}.{timestamp}.{body}".whsec_ prefix, then base64-decode the remainder.v1,.Example in Ruby:
require 'base64'
require 'openssl'
def valid_signature?(signing_token, message_id, timestamp, body, received_signatures)
raw_key = Base64.strict_decode64(signing_token.delete_prefix('whsec_'))
message = "#{message_id}.#{timestamp}.#{body}"
digest = OpenSSL::HMAC.digest('sha256', raw_key, message)
expected = "v1,#{Base64.strict_encode64(digest)}"
received_signatures.split(' ').any? do |sig|
ActiveSupport::SecurityUtils.secure_compare(expected, sig)
end
end
Example in Python:
import base64
import hashlib
import hmac
def valid_signature(signing_token, message_id, timestamp, body, received_signatures):
raw_key = base64.b64decode(signing_token.removeprefix('whsec_'))
message = f"{message_id}.{timestamp}.{body}".encode('utf-8')
digest = hmac.new(raw_key, message, hashlib.sha256).digest()
expected = "v1," + base64.b64encode(digest).decode('utf-8')
return any(
hmac.compare_digest(expected, sig)
for sig in received_signatures.split(' ')
)
The signing token works alongside the existing secret token. You can configure both on the same webhook:
X-Gitlab-Token header is still sent if a secret token is configured.webhook-signature and webhook-id headers are sent if a signing token is configured.To migrate an existing webhook using the secret token to a signing token without downtime, configure both tokens on the same
webhook during the transition. Update your receiver to verify the signature when webhook-signature is
present and fall back to the secret token otherwise.
Once your receiver handles signatures correctly, you can remove the secret token from the webhook settings.
To prevent replay attacks, validate that the timestamp in webhook-timestamp is recent before processing the payload.
The signing token is never returned by the API.
Mask sensitive portions of webhook URLs to enhance security. Masked portions are replaced with configured values when webhooks are executed, are not logged, and are encrypted at rest in the database.
To mask sensitive portions of a webhook URL:
a-z), numbers (0-9), or underscores (_).The masked values appear hidden in the UI.
For example, if you've defined variables path and value, the webhook URL can look like this:
https://webhook.example.com/{path}?key={value}
{{< history >}}
custom_webhook_headers. Enabled by default.custom_webhook_headers removed.{{< /history >}}
Add custom headers to webhook requests for authentication to external services. You can configure up to 20 custom headers per webhook.
Custom headers must:
Custom headers show in Recent events with masked values.
{{< history >}}
custom_webhook_template. Enabled by default.custom_webhook_template removed.custom_webhook_template_serialization. Disabled by default.custom_webhook_template_serialization enabled by default.custom_webhook_template_serialization removed in GitLab 18.10.{{< /history >}}
Create a custom payload template for your webhook to control the data sent in the request body.
To create a custom webhook template:
Use fields from the payload of an event in your template. For example:
{{build_name}} for a job event{{deployable_url}} for a deployment eventTo access nested properties, use periods to separate path segments.
For this custom payload template:
{
"event": "{{object_kind}}",
"project_name": "{{project.name}}"
}
The resulting request payload for a push event is:
{
"event": "push",
"project_name": "Example"
}
Custom webhook templates cannot access properties in arrays.
Filter push events sent to your webhook endpoint by the branch name.
Use one of these filtering options:
To filter by using a wildcard pattern:
*-stable to match branches ending with -stable.production/* to match branches in the production/ namespace.To filter by using a regular expression:
For example, to exclude the main branch, use:
\b(?:m(?!ain\b)|ma(?!in\b)|mai(?!n\b)|[a-l]|[n-z])\w*|\b\w{1,3}\b|\W+
{{< details >}}
{{< /details >}}
{{< history >}}
{{< /history >}}
Configure webhooks to support mutual TLS by setting a global client certificate in PEM format.
Prerequisites:
To configure mutual TLS for webhooks:
{{< tabs >}}
{{< tab title="Linux package (Omnibus)" >}}
Edit /etc/gitlab/gitlab.rb:
gitlab_rails['http_client']['tls_client_cert_file'] = '<PATH TO CLIENT PEM FILE>'
gitlab_rails['http_client']['tls_client_cert_password'] = '<OPTIONAL PASSWORD>'
Save the file and reconfigure GitLab:
sudo gitlab-ctl reconfigure
{{< /tab >}}
{{< tab title="Docker" >}}
Edit docker-compose.yml:
version: "3.6"
services:
gitlab:
image: 'gitlab/gitlab-ee:latest'
restart: always
hostname: 'gitlab.example.com'
environment:
GITLAB_OMNIBUS_CONFIG: |
gitlab_rails['http_client']['tls_client_cert_file'] = '<PATH TO CLIENT PEM FILE>'
gitlab_rails['http_client']['tls_client_cert_password'] = '<OPTIONAL PASSWORD>'
Save the file and restart GitLab:
docker compose up -d
{{< /tab >}}
{{< tab title="Self-compiled (source)" >}}
Edit /home/git/gitlab/config/gitlab.yml:
production: &base
http_client:
tls_client_cert_file: '<PATH TO CLIENT PEM FILE>'
tls_client_cert_password: '<OPTIONAL PASSWORD>'
Save the file and restart GitLab:
# For systems running systemd
sudo systemctl restart gitlab.target
# For systems running SysV init
sudo service gitlab restart
{{< /tab >}}
{{< /tabs >}}
After configuration, GitLab presents this certificate to the server during TLS handshakes for webhook connections.
Configure firewalls for webhook traffic based on how GitLab sends webhooks:
Webhooks are sent synchronously from Rails nodes when you test or retry a webhook in the UI.
When configuring firewalls, ensure both Sidekiq and Rails nodes can send webhook traffic.
Monitor and maintain your configured webhooks in GitLab.
View the history of webhook requests to monitor their performance and troubleshoot issues.
Prerequisites:
To view the request history for a webhook:
The Recent events section displays all requests made to a webhook in the last two days. The table includes:
200-299 codesinternal error for failed deliveriesPrerequisites:
Each webhook request in Recent events has a Request details page. This page contains the body and headers of:
To inspect the request and response details of a webhook event:
To send the request again with the same data and the same Idempotency-Key header, select Resend Request.
If the webhook URL has changed, you cannot resend the request.
You can also resend the request programmatically through the project webhooks API.
Test a webhook to ensure it's working properly or to re-enable a disabled webhook.
Prerequisites:
push events, your project must have at least one commit.To test a webhook:
Testing is not supported for some types of events for project and group webhooks. For more information, see issue 379201.
Use this technical reference to:
Implement fast and stable webhook receiver endpoints to ensure reliable webhook delivery.
Slow, unstable, or incorrectly configured receivers might be disabled automatically. Invalid HTTP responses are treated as failed requests.
To optimize your webhook receivers:
200 or 201 status:
4xx range) only for misconfigured webhooks.400 or ignore the payload.500 server error responses for handled events.{{< history >}}
auto_disabling_web_hooks.{{< /history >}}
[!flag] The availability of this feature is controlled by a feature flag. For more information, see the history.
GitLab automatically disables project or group webhooks that fail four consecutive times.
To view auto-disabled webhooks:
In the webhook list, auto-disabled webhooks display as:
Webhooks are temporarily disabled if they fail four consecutive times. If webhooks fail 40 consecutive times, they become permanently disabled.
Failure occurs when:
4xx or 5xx range.Temporarily disabled webhooks are initially disabled for one minute, with the duration extending on subsequent failures up to 24 hours. After this period has elapsed, these webhooks are automatically re-enabled.
Webhooks are permanently disabled if they fail 40 consecutive times. Unlike temporarily disabled webhooks, these webhooks are not automatically re-enabled.
Webhooks that were permanently disabled in GitLab 17.10 and earlier underwent a data migration. These webhooks might display four failures in Recent events even though the UI might state they have 40 failures.
To re-enable a disabled webhook, send a test request.
The webhook is re-enabled if the test request returns a response code in the 2xx range.
{{< history >}}
X-Gitlab-Webhook-UUID header introduced in GitLab 16.2.Idempotency-Key header introduced in GitLab 17.4.webhook-id and webhook-timestamp headers introduced in GitLab 19.0.webhook-signature header introduced in GitLab 19.0 with a flag named webhook_signing_token. Enabled by default.{{< /history >}}
GitLab includes the following headers in webhook requests to your endpoint.
[!flag] The availability of the
webhook-signatureheader is controlled by a feature flag. For more information, see the history.
| Header | Description | Example |
|---|---|---|
Idempotency-Key | Unique ID consistent across webhook retries. Available for legacy reasons; prefer webhook-id. | "f5e5f430-f57b-4e6e-9fac-d9128cd7232f" |
User-Agent | User agent in the format "Gitlab/<VERSION>". | "GitLab/15.5.0-pre" |
webhook-id | Unique message ID consistent across webhook retries. Equal to Idempotency-Key. | "f5e5f430-f57b-4e6e-9fac-d9128cd7232f" |
webhook-signature | Space-separated list of HMAC-SHA256 signatures, each in the format v1,{base64_signature}. Included only when a signing token is configured. | "v1,abc123def456==" |
webhook-timestamp | Unix timestamp (seconds since epoch) when the request was generated. | "1744578123" |
X-Gitlab-Event-UUID | Unique ID for non-recursive webhooks. Recursive webhooks (triggered by earlier webhooks) share the same value. | "13792a34-cac6-4fda-95a8-c58e00a3954e" |
X-Gitlab-Event | Webhook type name. Corresponds to event types in the format "<EVENT> Hook". | "Push Hook" |
X-Gitlab-Instance | Hostname of the GitLab instance that sent the webhook. | "https://gitlab.com" |
X-Gitlab-Token | Secret token for the webhook, sent as plain text. Included only when a secret token is configured. | "my-secret-token" |
X-Gitlab-Webhook-UUID | Unique ID for each webhook. | "02affd2d-2cba-4033-917d-ec22d5dc4b38" |
GitLab rewrites relative image references to absolute URLs in webhook bodies.
If the original image reference in a merge request, comment, or wiki page is:

The rewritten image reference in the webhook body would be:

This example assumes:
gitlab.example.com.123.GitLab does not rewrite image URLs when: