Serialize Time/Date in custom webhook template payloads

What does this MR do and why?

Custom webhook template rendering produced inconsistent JSON between the synchronous and asynchronous webhook paths.

WebHookService#render_custom_template calls value.to_json and strips the surrounding JSON quotes only when value.is_a?(String). Whether the renderer saw a Time/Date object or its String serialization depended on which path delivered the payload:

  • The asynchronous path (#async_execute) enqueued the payload, Sidekiq JSON-roundtripped it, and the worker received Strings.
  • The synchronous path (#execute, used by the Test webhook UI button) handed raw Time/Date objects straight to the renderer.

For a docs-compliant template such as "{{object_attributes.created_at}}" that meant the test path produced doubled quotes (""…"" — invalid JSON) while production worked.

For an unquoted template the symptom was the mirror image: production stripped the quotes (bare-timestamp — invalid JSON) while test mode worked by accident.

This MR adds a Gitlab::WebHooks.normalize_dates helper that recursively serializes Time, DateTime, ActiveSupport::TimeWithZone, and Date values to ISO 8601 strings (milliseconds for time, plain ISO date otherwise), and calls it in both places that feed the renderer:

  • WebHookService#async_execute, before enqueuing to Sidekiq, so the worker receives a controlled string format instead of relying on Rails default to_json behavior.
  • WebHookService#request_payload, before rendering a custom webhook template, so the synchronous test-mode path matches the asynchronous one. The call is idempotent on the async path (already stringified).

After this change, the docs-compliant pattern produces the same valid JSON in both modes:

{
  "created_at": "2026-04-23T12:30:45.123Z",
  "due_on": "2026-04-23"
}

References

How to set up and validate locally

  1. Create any project.

  2. Go to the project's Settings → Webhooks and add a webhook (for example on issue events)

  3. Create at least one issue in that project.

  4. Use a docs-compliant custom payload template (placeholders wrapped in JSON double quotes):

    {
      "created_at": "{{object_attributes.created_at}}",
      "updated_at": "{{object_attributes.updated_at}}"
    }
  5. Click Test → Issue events. The receiver should record a request with valid JSON, dates wrapped in quotes.

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 Manuel Schönlaub

Merge request reports

Loading
Loading