This was not the same installation as my earlier 325K-node publishing investigation. I was working with several large Umbraco environments, and this case came from another installation with more than 805,000 documents.

At that scale, the objective was not to find one magical performance setting. The migration and publishing process ran for days, so the practical approach was to identify avoidable work, measure it, understand why it happened, and remove it where it was safe to do so.

Database scale from the 805K-document Umbraco installation. At this size, even small recurring workloads become worth inspecting during multi-day processing.

Database scale from the 805K-document Umbraco installation. At this size, even small recurring workloads become worth inspecting during multi-day processing.

Why webhook polling became interesting

Umbraco 13.6.0 exposed the following webhook settings under Umbraco:CMS:Webhook:

{
  "Umbraco": {
    "CMS": {
      "Webhook": {
        "Enabled": true,
        "MaximumRetries": 5,
        "Period": "00:00:10",
        "EnableLoggingCleanup": true,
        "KeepLogsForDays": 30
      }
    }
  }
}

The important values for this investigation were Enabled and Period. The period controlled how often Umbraco checked for webhook requests that needed to be fired, with a ten-second interval in the default configuration.

I was not using webhooks for this workload, so setting Enabled to false looked like the obvious way to remove webhook processing from the equation.

The unexpected behavior in Umbraco 13.6.0

The application configuration said webhooks were disabled, but SQL Profiler told a different story. Queries against umbracoWebhookRequest continued to appear at the configured interval.

SQL Profiler showing cyclic queries to umbracoWebhookRequest even though webhook functionality had been disabled in configuration.

SQL Profiler showing cyclic queries to umbracoWebhookRequest even though webhook functionality had been disabled in configuration.

This did not prove that webhook polling was the main bottleneck in the migration. It was not. The important point was narrower: it was recurring database work that was not needed for this workload, and I expected the configuration switch to stop it.

Performance context: a query every ten seconds may be insignificant on a normal site. During a multi-day migration involving more than 805,000 documents, the threshold for investigating avoidable background work is different.

The temporary mitigation I used

Before the Core fix existed, I could not make the recurring webhook-firing activity disappear completely using the available configuration. The practical mitigation was to make it wake up far less often and to disable webhook log cleanup as well.

"Webhook": {
  "Enabled": false,
  "MaximumRetries": 1,
  "Period": "1.00:00:00",
  "EnableLoggingCleanup": false,
  "KeepLogsForDays": 30
}

The key change was Period = 1 day. Instead of hitting the webhook request table every ten seconds, the background cycle was reduced to once per day. Disabling EnableLoggingCleanup also removed a background cleanup task that was irrelevant when webhooks were not being used.

Historical workaround only. This configuration is part of the affected Umbraco 13 behavior described in this case study. It should not be copied into a current Umbraco project as a general performance recommendation.

The issue was fixed in Umbraco 13.8.0

The original post caught the attention of Andy Butland from Umbraco HQ. The behavior was then addressed in Umbraco Core through PR #18383, titled Disable webhook firing if disable in configuration (13).

Umbraco's official 13.8.0 release notes list that pull request as one of the bug fixes shipped in the release. That is the key historical boundary for this article: the workaround was useful for the affected version, while 13.8.0 introduced the Core change that made the configuration switch do what it was expected to do.

The implementation change is conceptually small. When webhook firing is disabled in the configuration, the firing job should not continue performing the work that triggers polling for pending webhook requests. That is exactly the class of behavior the 13.8.0 fix was intended to correct.

The affected behavior, the temporary mitigation used in production, and the corrected configuration semantics from Umbraco 13.8.0 onward.

The affected behavior, the temporary mitigation used in production, and the corrected configuration semantics from Umbraco 13.8.0 onward.

Does this still affect Umbraco 17?

After reviewing the current Umbraco 17 webhook code and the history of the Core fix, this specific issue should no longer occur in current versions. The original defect was fixed in Umbraco 13.8.0 by PR #18383, and the same correction was carried into the later code line for Umbraco 15 in PR #18386.

Umbraco 17 still exposes the familiar webhook settings, including Enabled, MaximumRetries, Period, EnableLoggingCleanup, and KeepLogsForDays. Its webhook pipeline also continues to use WebhookSettings, where Enabled remains the global switch controlling whether webhooks are active.

At first glance, there is no indication in the current v17 implementation that the old Umbraco 13.6.0 behavior has returned. In other words, setting Enabled = false should no longer leave the webhook firing path doing the unnecessary work described in this case study. The one-day Period setting should therefore be treated purely as a historical workaround for affected versions, not as a recommendation for Umbraco 17.

What I would configure today

If a current Umbraco installation does not use webhooks, start with the supported configuration switch:

"Webhook": {
  "Enabled": false
}

Do not artificially increase Period to one day just because an older article told you to. That value was a mitigation for an affected implementation, not a universal optimization pattern.

What this case taught me about large Umbraco installations

With hundreds of thousands of documents, performance engineering becomes less about isolated tips and more about accounting for work. SQL queries, indexing, publishing notifications, scheduled jobs, background services, cache invalidation, and custom integrations all consume part of the same system.

That does not mean every recurring query is a problem. It means you should be able to explain why it exists, whether your workload needs it, and what disabling it actually changes.

This case was useful precisely because the investigation moved through a clean sequence: configuration suggested one behavior, SQL Profiler showed another, a safe mitigation reduced unnecessary work, and the discrepancy ultimately resulted in a Core fix.

The same principle applied in my separate 325K-node publishing case study, even though the underlying bottleneck there was different. At unusual scale, assumptions need to be verified against what the application and database are actually doing.

References