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_present boolean — indicates whether a secret token (X-Gitlab-Token) is configured
  • Adds signing_token_present boolean — indicates whether an HMAC signing token (webhook-signature) is configured
  • Accepts signing_token on create/update (must be in whsec_<base64> format encoding a 32-byte key); never returned in responses
  • signing_token is silently ignored when the webhook_signing_token feature 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

#19367 (closed)

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_token is absent from the response body
  • signing_token_present is true
  • token_present is false

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_present is true
  • signing_token_present is true
  • Neither token nor signing_token appear 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:

  • tokenPresent and signingTokenPresent are returned as booleans
  • signingToken is 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.

Edited by Rodrigo Tomonari

Merge request reports

Loading