Webhook Secret Token with HMAC Digest
Description
Currently, when creating a webhook you have the option to set a value that will be sent in the X-Gitlab-Token HTTP Header.
This is meant for validation that the request did actually come from GitLab.
But, this security feature is very poorly implemented for a few reasons:
- This token is sent in plain text
- No way to verify that the payload is valid and came from GitLab
- Static tokens are vulnerable to replay attacks — an attacker who intercepts a legitimate request can resend it indefinitely, since the token doesn't change per request (see also #587536 (closed))
This should function more like GitHub's version, where that token (the hook secret) is used as the key for an HMAC SHA256 hex digest, which is then sent as an HTTP Header.
You can then compute your own hash of the payload using the secret that it should be, and compare it to the one sent in the header.
However, with GitLab (as said before), you can not do this, the best that you can do is check the plain text header... Not great.
Some examples of other services that sign their requests:
- Stripe https://stripe.com/docs/webhooks/signatures
- Slack https://api.slack.com/authentication/verifying-requests-from-slack
- GitHub https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks
Proposal
This proposal also addresses the replay attack protection described in #587536 (closed). The HMAC signing token approach solves both payload integrity verification and replay resistance in a single feature; no separate JWT or nonce mechanism is needed.
- Add a signing token field when creating a webhook, explicitly labeled as the key for the HMAC hash of the payload. A new field is needed to ensure backwards compatibility with existing webhooks. This field should be absolutely secret, like a password (even when editing an endpoint).
- When sending a payload to an endpoint, add two additional HTTP headers:
X-Gitlab-Signature— an HMAC-SHA256 hex digest of{timestamp}.{webhook-uuid}.{payload}, prefixed withsha256=X-Gitlab-Timestamp— the Unix timestamp used in the signature, so receivers can verify the request is recent and reject stale/replayed requests (e.g. outside a 5-minute window)
- The UUID and timestamp included in the HMAC message make replay attacks impractical: a replayed request carries a stale timestamp that receivers can detect and reject.
If no signing token is configured, the X-Gitlab-Signature and X-Gitlab-Timestamp headers are not sent.
Out of scope
System Hooks are not covered by this issue. Signing token support for System Hooks will be addressed separately in #503457 (closed).
Technical Proposal
@van.m.anderson worked on a merge request that was very close to implementing the feature Draft: POC for HMAC webhook signing (!163102 - closed). Thank you, Van
The remaining work in that MR is outlined here !163102 (comment 2085734123), which is to split the backend and frontend implementations and then add test coverage.
Links / references
GitLab's Documentation on Webhooks
GitLab's "Secret Token" Documentation
GitHub's Documentation on Webhooks
Replay Attack Protection proposal (#587536)
Designs
Outdated designs
| State | Design |
|---|---|
| New webhook | ![]() |
| Edit legacy webhook (created before signing tokens were introduced) | ![]() |
| Edit webhook (with signing token) | ![]() |
| Edit webhook > Regenerated signing token | ![]() |
Open Questions: Secret Token Deprecation
- Should the legacy secret token (X-Gitlab-Token) be deprecated once the signing token is released?
Note: The deprecation timeline will be difficult to determine from instrumentation alone. While we can measure how many webhooks have a signing token configured on the GitLab side, we have no visibility into how many receivers (external services) are actively validating it, rather than still relying on X-Gitlab-Token. This means adoption data will likely undercount real usage, and any removal decision will require tolerance for some uncertainty.
Documentation blurb
Overview
This feature proposal would allow for more secure endpoints, since users could verify that the payload is authentic and originated from a GitLab server.
It also protects against replay attacks by incorporating a timestamp and unique request UUID into the HMAC signature. Receivers can validate the timestamp is recent (e.g. within 5 minutes) to reject replayed requests, even if an attacker captured a legitimate one.
This would solve multiple security flaws in GitLab's current webhook implementation that allow for easy spoofing and replay of requests that could potentially be damaging.
Use cases
- Any webhook consumer that needs to verify requests genuinely originated from GitLab.
- Security-conscious teams in regulated industries (finance, healthcare) with compliance requirements around request integrity and audit trails.
Feature checklist
Make sure these are completed before closing the issue, with a link to the relevant commit.
- Feature assurance
- Documentation
- Added to features.yml



