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...
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"/> </event>
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:
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
:
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:
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
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:
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:
And once that is created, you can pick this option for the Webhook item's
Authorization
field, and have it sent.
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 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 <event> <EventName>item:deleted</EventName> <Item> <Language>en</Language> <Version>1</Version> <Id>f22e1fd7-dcf8-4216-8067-71a654b5d8a3</Id> <Name>Delete me</Name> <ParentId>00000000-0000-0000-0000-000000000000</ParentId> <TemplateId>76036f5e-cbce-46d1-af0a-4143f9b557aa</TemplateId> <MasterId>00000000-0000-0000-0000-000000000000</MasterId> <SharedFields> <Id>06d5295c-ed2f-4a54-9bf2-26228d113318</Id> <Value>applications/32x32/delete.png</Value> </SharedFields> <SharedFields> <Id>a4f985d9-98b3-4b52-aaaf-4344f6e747c6</Id> <Value>{A5BC37E7-ED96-4C1E-8590-A26E64DB55EA}</Value> </SharedFields> </Item> <ParentID>110d559f-dea5-42ea-9c1c-8a5df7e70ef9</ParentID> <WebhookItemId>67d6575f-31df-4231-8f2c-556d2f3646f2</WebhookItemId> <WebhookItemName>Delete an item</WebhookItemName> </event>
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...
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...
↑ Back to top