Expose token presence flags and signing token in Webhooks API
What does this MR do and why?
Exposes signing token metadata in the Webhooks REST API and GraphQL API:
- Adds
token_presentboolean — indicates whether a secret token (X-Gitlab-Token) is configured - Adds
signing_token_presentboolean — indicates whether an HMAC signing token (webhook-signature) is configured - Accepts
signing_tokenon create/update (must be inwhsec_<base64>format encoding a 32-byte key); never returned in responses signing_tokenis silently ignored when thewebhook_signing_tokenfeature flag is disabled
Applies to project hooks, system hooks, and group hooks (EE).
Note: I initially named the attributes has_token and has_signing_token, but I renamed them to token_present and signing_token_present because these names more clearly indicate what they check and are more commonly used in other APIs, according to Claude.
References
How to set up and validate locally
Enable the feature flag
Feature.enable(:webhook_signing_token)Generate a signing token
Generate a valid signing token in the Rails console:
"whsec_#{Base64.strict_encode64(SecureRandom.bytes(32))}"
# => e.g. "whsec_dGVzdHRlc3R0ZXN0dGVzdHRlc3R0ZXN0dGVzdHRlc3Q="Use this value wherever <signing_token> appears below.
REST API — project hooks
Create a hook with a signing token:
curl --request POST \
--header "PRIVATE-TOKEN: <your_token>" \
--header "Content-Type: application/json" \
--data '{
"url": "http://example.com/webhook",
"signing_token": "<signing_token>"
}' \
"https://gdk.test:3000/api/v4/projects/<project_id>/hooks"Verify the response:
signing_tokenis absent from the response bodysigning_token_presentistruetoken_presentisfalse
Create a hook with both a secret token and a signing token:
curl --request POST \
--header "PRIVATE-TOKEN: <your_token>" \
--header "Content-Type: application/json" \
--data '{
"url": "http://example.com/webhook",
"token": "mysecret",
"signing_token": "<signing_token>"
}' \
"https://gdk.test:3000/api/v4/projects/<project_id>/hooks"Verify:
token_presentistruesigning_token_presentistrue- Neither
tokennorsigning_tokenappear in the response
Update a hook and verify persistence:
curl --request PUT \
--header "PRIVATE-TOKEN: <your_token>" \
--header "Content-Type: application/json" \
--data '{"signing_token": "<signing_token>"}' \
"https://gdk.test:3000/api/v4/projects/<project_id>/hooks/<hook_id>"Then GET the hook and verify signing_token_present is true.
REST API — system hooks
curl --request POST \
--header "PRIVATE-TOKEN: <admin_token>" \
--header "Content-Type: application/json" \
--data '{
"url": "http://example.com/webhook",
"signing_token": "<signing_token>"
}' \
"https://gdk.test:3000/api/v4/hooks"Verify signing_token_present is true and signing_token is absent from the response.
GraphQL
The webhook field on Project (and Group on EE) exposes a single hook by ID:
query {
project(fullPath: "<namespace>/<project>") {
webhook(id: "gid://gitlab/ProjectHook/<hook_id>") {
id
url
tokenPresent
signingTokenPresent
}
}
}Verify:
tokenPresentandsigningTokenPresentare returned as booleanssigningTokenis not a field on the type (querying it should return a schema error)
Feature flag disabled
Feature.disable(:webhook_signing_token)Submit a signing_token via the API — it should be silently ignored and signing_token_present should be false.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.