Jeremy Davis
Jeremy Davis
Sitecore, C# and web development
Jeremy Davis
Jeremy Davis
Sitecore, C# and web development
Article printed from:

Webhooks in Sitecore 10.3

A new way to wire up integrations

Published 05 December 2022
Updated 06 December 2022

After a bit more of a pause than most attendees of Symposium this year were expecting, Sitecore 10.3 is finally out in the wild now. And (to me at least) one of the most interesting changes is that the server-side event model for the CMS has been extended with Webhooks. What does that mean, and how can you make use of them? Well since I was lucky enough to get my hands on the "MVP Preview" of this release a couple of weeks back, I've had a chance to do some digging. Read on to find out...

What and why?

This is one of the features added to the 10.3 release which has come from the roadmap around composable and the development for XM Cloud. The move towards SaaS-hosted systems and composability means we need easier "plug and play" ways of connecting systems together, and Webhooks are a useful standard here.

Where in the past you would have written some back-end code to handle an event like "Item Created" and then patched it into the CMS's config, Webhooks allow the CMS to automatically make an HTTP request to an endpoint you own each time that event is triggered. And the body of that request contains a description of the event which occurred. This makes wiring up your events to other systems much simpler - and will eventually tie easily into Sitecore Connect, the new iPaaS product which was announced recently.


If you look at the config 10.3, you'll see that for each of the system's main CMS events there's a new handler in place. For example, with the Item:Deleted event we now see this:

<event name="item:deleted" patch:source="Sitecore.EmailExperience.ContentManagement.config">
    <handler type="Sitecore.Links.ItemEventHandler, Sitecore.Kernel" method="OnItemDeleted" resolve="true"/>
    <handler type="Sitecore.Tasks.ItemEventHandler, Sitecore.Kernel" method="OnItemDeleted"/>
    <handler type="Sitecore.Globalization.ItemEventHandler, Sitecore.Kernel" method="OnItemDeleted"/>
    <handler type="Sitecore.Data.Fields.ItemEventHandler, Sitecore.Kernel" method="OnItemDeleted"/>
    <handler type="Sitecore.Rules.ItemEventHandler, Sitecore.Kernel" method="OnItemDeleted"/>
    <handler type="Sitecore.Caching.Placeholders.PlaceholderCacheEventHandler, Sitecore.Kernel" method="UpdateCaches" resolve="true"/>
    <handler type="Sitecore.Publishing.PartialHtmlCacheClearer, Sitecore.Kernel" method="OnItemEvent" resolve="true"/>
    <handler type="Sitecore.Events.Webhooks.WebhookSubscriber, Sitecore.Kernel" method="OnWebhookItemDeleted" resolve="true" patch:source="Sitecore.Webhooks.config"/>
    <handler type="Sitecore.Marketing.xMgmt.Caching.IdsByAliasesCacheClearer, Sitecore.Marketing.xMgmt" method="OnItemDeleted" resolve="true" patch:source="Sitecore.Marketing.config"/>
    <handler type="Sitecore.Marketing.xMgmt.Definitions.ItemEventHandler, Sitecore.Marketing.xMgmt" method="OnItemDeleted" patch:source="Sitecore.Marketing.config"/>
    <handler type="Sitecore.Analytics.Data.Caches.ProfilesWithDefaultValuesCacheHandler, Sitecore.Analytics" method="OnItemDeleted" resolve="true" patch:source="Sitecore.Analytics.Tracking.config"/>
    <handler method="OnItemDeleted" ref="experienceAnalytics/client/segmentDeployedEventHandler" patch:source="Sitecore.ExperienceAnalytics.Client.config"/>
    <handler type="Sitecore.Modules.EmailCampaign.Core.RootItemEventHandler, Sitecore.EmailCampaign" method="OnRootDeleted" resolve="true" patch:source="Sitecore.EmailExperience.ContentManagement.config"/>
    <handler type="Sitecore.Modules.EmailCampaign.Core.ItemEventHandler, Sitecore.EmailCampaign" method="OnItemDeleted" resolve="true" patch:source="Sitecore.EmailExperience.ContentManagement.config"/>


The file Sitecore.WebHooks.config has patched in a new handler, for this event. That allows the system to make a Webhook call if this event is triggered.

But for a Webhook to be useful, it needs to call a system you own - so how do you configure this? Well there's a new location in the system part of the content tree to hold this config:

Sitecore's Content Editor showing a new config item for a WebHook

You can create items under /sitecore/system/Webhooks to configure the hooks you want called. And for each one you get to configure a set of options:

First, you need to specify which events in the CMS should trigger the call. You can pick from all the common item-change and publishing events, and you are able to select multiple events per hook. The set of options is defined under /sitecore/system/Settings/Webhooks/Event Types:

The list of Webhook event options in the content tree

You are also able to specify a Rules Engine ruleset to further refine when this Webhook should be triggered. For example, triggering only when an item is in a specific part of the content tree:

The rules editor showing a 'descendant item of Home' rule

So you're able to give fairly granular control of when the hook should be triggered.

The remaining options control how the remote URL is triggered:

The config options fields for how a webhook will be triggered

The Enabled flag is fairly obvious - controlling whether this hook should be processed or not. And the Url controls what HTTP endpoint will be called when this hook does trigger. Note that you can call any URL, but if you specify an HTTP url you'll get a Content Editor warning suggesting that you should use HTTPS instead for security. The final option here is for Serialization Type, which lets you choose between sending the body of your hook as JSON or XML data.

The Authorization field allows you to pass credentials when the hook url is called. You set these up under /sitecore/system/Settings/Webhooks/Authorizations and you can specify your credentials in a variety of ways:

The insert options list for authorisation types for Webhooks

You create an item for each OAuth, Basic, Digest or API Key authentication setup you need. For example, you can add a specific security header to each request:

Content Editor settings for a WebHook security header

And once that is created, you can pick this option for the Webhook item's Authorization field, and have it sent.

In action...

Having set up your choices here, you can save this and the CMS will start making calls to your hook whenever a suitable event occurs. So if I create a test item under Home and delete that:

The item to be deleted in the content tree

The Webhook will call the configured endpoint and return some data. To see what it did, I put a simple "dump out the request you receive" ASPX page at my target URL:

Method: POST
Header: x-My-Auth=SecretValueIsSetHere
		<Name>Delete me</Name>
	<WebhookItemName>Delete an item</WebhookItemName>


Sitecore made a POST request to my endpoint, passing the security header I configured, and a blob of XML describing the properties of the event which was triggered and the item it acted on. But that could have been JSON if I'd configured it that way...

What if something goes wrong?

Well you'll get errors in your Sitecore log, and the Webhook URL won't get its post correctly. For example, if I deliberately break the ASPX page that was receiving the call above, I get the following in the logs:

8596 00:38:48 INFO  AUDIT (sitecore\Admin): Add from template: master:/sitecore/templates/Sample/Sample Item, language: en, version: 1, id: {76036F5E-CBCE-46D1-AF0A-4143F9B557AA}
ManagedPoolThread #12 00:38:48 INFO  Job started: Sitecore.ListManagement.Operations.UpdateListOperationsAgent
ManagedPoolThread #12 00:38:48 INFO  Job ended: Sitecore.ListManagement.Operations.UpdateListOperationsAgent
 980 00:38:48 ERROR Webhooks: Request is not successfull https://localhost/HookReceiver.aspx. Response code was InternalServerError
ManagedPoolThread #9 00:38:48 INFO  Job started: Index_Update_IndexName=sitecore_testing_index
ManagedPoolThread #6 00:38:48 INFO  Job started: Index_Update_IndexName=sitecore_fxm_master_index


When the execution of the target URL breaks you see an error like this. So you'd need to check logs for your endpoint to discover the detail of this error. However if the issue is with the config or process of calling that URL instead, any exceptions thrown on Sitecore's side will be recorded in the log. For example, turning of HTTPS on my test endpoint, I see:

8572 01:55:36 ERROR Webhooks: Request failed. https://localhost/HookReceiver.aspx
Exception: System.Net.Http.HttpRequestException
Message: An error occurred while sending the request.
Source: mscorlib
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Common.HttpClient.SitecoreHttpClient.<SendAsync>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Sitecore.Events.Webhooks.Authorization.BaseRequestSender.<SendAsync>d__11.MoveNext()


Plus a chunk of nested SocketException and WebException stack trace that I've omitted for readability...

Updated to add: Over on Mastodon, Per Bering makes an interesting observation about fault tolerance here. He points out that the editorial events are queued in the CMS, so that the Webhook calls can be made asynchronously. But that queue is not persistent - it exists only in memory. So if the CM server recycles for any reason, un-sent Webhook calls are lost.

That's something to think about when you're looking at making use of this. It might be an issue if guaranteed delivery of events is important to your particular scenario.


Whether you're using SaaS or IaaS/PaaS implementations of Sitecore, this has the potential to make wiring CMS events to other systems much simpler in the future. If you have a need for editorial actions to be communicated to an external system, this is probably the easiest route to take going forward. In fact I'm involved in a project at the moment where having this in place could have removed the need for quite a lot of code...