Use webhooks for automatic updates
Webhooks allow you to integrate Kentico Kontent with other software applications and automate your processes. Think of webhooks as programmatic notifications that let your application know when something changes inside your Kentico Kontent project.
Table of contents
What can you do with webhooks
As an example of webhooks in action, when a new content item is published your application can automatically react in numerous ways such as:
- Update the search index for your project's content with AlgoliaOpens in a new window, Azure Search IndexOpens in a new window, and more.
- Trigger a new build process and redeploy your application.
- Notify your team by sending an email, post a message to a Slack channelOpens in a new window, or move a card inside Trello.
- Schedule a social media post featuring the newly published content item.
- Invalidate the cache of your appOpens in a new window to make sure users see the latest content.
Using webhooks, you can also react to workflow changes in content items to accomplish tasks like:
- Automatically send content for translationOpens in a new window and receive notifications when the translation is done.
- Notify reviewers that content is ready for review.
How webhooks work in a nutshell
- When something changes inside your project, Kontent sends an HTTP POST request to the URL you specify.
- The payload of the request contains structured information about the type of change and the affected content items.
- The header of the request contains a secret token you can use to verify the message.
- Your application's endpoint at the specified URL must process the request - parse the information in the payload and react accordingly.
- If your application is down or fails to process the request correctly, Kontent sends it again according to the retry policy.
Let's go through the process in more detail.
Create a webhook
To register a new webhook in your project:
- In Kentico Kontent, choose Project settings from the app menu.
- Under Environment settings, choose Webhooks.
- Click Create new Webhook.
- Enter a name for the webhook, such as Invalidate cache.
- Enter a publicly available URL address of your webhook endpoint, such as
https://example.com/cache-invalidate
. - (Optional) Choose the events to trigger the webhook. By default, the events related to content items and taxonomy are selected.
- Click Save.

Registering a webhook in Kentico Kontent.
Now, whenever content in your project changes and your webhook is set to watch for that kind of change (you've set it as a trigger), Kontent will send a notification to your webhook endpoint.
Validate received notifications
Once the webhook is registered, Kontent will start sending POST requests to the webhook URL you've specified. Receiving your notifications might take a few minutes or possibly even longer. The notifications may sometimes come in batches because the content changes are processed dynamically based on load.
The notifications sent to your webhook URL come with the X-KC-Signature
header. This header contains a checksum of the JSON payload that you can use to verify the authenticity of the notification. The signature is a base64Opens in a new window encoded hash generated using a HMAC-SHA-256Opens in a new window with a secret key.
Tip: Save time with sample webhook handlers
Try our sample Kontent webhook handlersOpens in a new window to validate notification signatures, log valid webhooks, and display received webhooks.
To verify the received notification, use the JSON payload (that is the raw body of the notification as-is) and the generated webhook Secret as the secret for the HMAC function. The secret is unique for each created webhook in your project. Once you generate a hash, compare it with the value of the X-KC-Signature
header.
// Tip: Find more about Java SDK at https://docs.kontent.ai/java import javax.crypto; import javax.crypto.spec; import javax.xml.bind; // Example of generating the hash to verify the notification public static String generateHash(String message, String secret) throws Exception { Mac sha256Hmac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); sha256Hmac.init(secretKeySpec); return Base64.getEncoder().encodeToString(sha256Hmac.doFinal(message.getBytes(StandardCharsets.UTF_8))); }// Tip: Find more about Java SDK at https://docs.kontent.ai/java import javax.crypto; import javax.crypto.spec; import javax.xml.bind; // Example of generating the hash to verify the notification public static String generateHash(String message, String secret) throws Exception { Mac sha256Hmac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); sha256Hmac.init(secretKeySpec); return Base64.getEncoder().encodeToString(sha256Hmac.doFinal(message.getBytes(StandardCharsets.UTF_8))); }
// Tip: Find more about JS/TS SDKs at https://docs.kontent.ai/javascript const signatureHelper = require('@kentico/kontent-webhook-helper'); // Example of generating the hash to verify the notification const isValidSignature = (req, secret) => { return signatureHelper( req.body, // Use raw body data from the request, i.e., by using body-parser secret, req.headers['x-kc-signature'] ); };// Tip: Find more about JS/TS SDKs at https://docs.kontent.ai/javascript const signatureHelper = require('@kentico/kontent-webhook-helper'); // Example of generating the hash to verify the notification const isValidSignature = (req, secret) => { return signatureHelper( req.body, // Use raw body data from the request, i.e., by using body-parser secret, req.headers['x-kc-signature'] ); };
// Tip: Find more about .NET SDKs at https://docs.kontent.ai/net using System; using System.Security.Cryptography; using System.Text; // Example of generating the hash to verify the notification private static string GenerateHash(string message, string secret) { secret = secret ?? ""; UTF8Encoding SafeUTF8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); byte[] keyBytes = SafeUTF8.GetBytes(secret); byte[] messageBytes = SafeUTF8.GetBytes(message); using (HMACSHA256 hmacsha256 = new HMACSHA256(keyBytes)) { byte[] hashmessage = hmacsha256.ComputeHash(messageBytes); return Convert.ToBase64String(hashmessage); } }// Tip: Find more about .NET SDKs at https://docs.kontent.ai/net using System; using System.Security.Cryptography; using System.Text; // Example of generating the hash to verify the notification private static string GenerateHash(string message, string secret) { secret = secret ?? ""; UTF8Encoding SafeUTF8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); byte[] keyBytes = SafeUTF8.GetBytes(secret); byte[] messageBytes = SafeUTF8.GetBytes(message); using (HMACSHA256 hmacsha256 = new HMACSHA256(keyBytes)) { byte[] hashmessage = hmacsha256.ComputeHash(messageBytes); return Convert.ToBase64String(hashmessage); } }
// Example of generating the hash to verify the notification $givenSignature = $_SERVER['HTTP_X_KC_SIGNATURE']; $computedSignature = base64_encode(hash_hmac('sha256', $json_message, $secret, true)); $result = hash_equals($givenSignature, $computedSignature);// Example of generating the hash to verify the notification $givenSignature = $_SERVER['HTTP_X_KC_SIGNATURE']; $computedSignature = base64_encode(hash_hmac('sha256', $json_message, $secret, true)); $result = hash_equals($givenSignature, $computedSignature);
// Tip: Find more about JS/TS SDKs at https://docs.kontent.ai/javascript import { signatureHelper } from '@kentico/kontent-webhook-helper'; // Example of generating the hash to verify the notification const isValidSignature = (req, secret) => { return signatureHelper( req.body, // Use raw body data from the request, i.e., by using body-parser secret, req.headers['x-kc-signature'] ); };// Tip: Find more about JS/TS SDKs at https://docs.kontent.ai/javascript import { signatureHelper } from '@kentico/kontent-webhook-helper'; // Example of generating the hash to verify the notification const isValidSignature = (req, secret) => { return signatureHelper( req.body, // Use raw body data from the request, i.e., by using body-parser secret, req.headers['x-kc-signature'] ); };
Once you've received and verified the message, you can see what's inside and react accordingly.
Get the latest content
After you get a notification about changed content, you might want to explicitly request the new content from the Delivery API. To do this, send a standard request to the Delivery API with the X-KC-Wait-For-Loading-New-Content
header set to true
.
If the requested content has changed since the last request, the header determines whether to wait while fetching content. By default, when the header is not set, the API serves stale contentOpens in a new window (if cached by the CDN) while it's fetching the latest content to minimize wait time.
// Tip: Find more about Java SDK at https://docs.kontent.ai/java import kentico.kontent.delivery.*; DeliveryOptions deliveryOptions = new DeliveryOptions(); deliveryOptions.setProjectId("<YOUR_PROJECT_ID>"); deliveryOptions.setWaitForLoadingNewContent(true); DeliveryClient client = new DeliveryClient(deliveryOptions); CompletionsStage<ContentItemResponse> item = client.getItem("my_article"); // To use the code for Android projects, see http://docs.kontent.ai/android// Tip: Find more about Java SDK at https://docs.kontent.ai/java import kentico.kontent.delivery.*; DeliveryOptions deliveryOptions = new DeliveryOptions(); deliveryOptions.setProjectId("<YOUR_PROJECT_ID>"); deliveryOptions.setWaitForLoadingNewContent(true); DeliveryClient client = new DeliveryClient(deliveryOptions); CompletionsStage<ContentItemResponse> item = client.getItem("my_article"); // To use the code for Android projects, see http://docs.kontent.ai/android
// Tip: Find more about JS/TS SDKs at https://docs.kontent.ai/javascript const KontentDelivery = require('@kentico/kontent-delivery'); const deliveryClient = new KontentDelivery.DeliveryClient({ projectId: '<YOUR_PROJECT_ID>', typeResolvers: [ // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models new KontentDelivery.TypeResolver('article', (rawData) => new Article()) ] }); deliveryClient.item('my_article') .queryConfig({ waitForLoadingNewContent: true }) .toObservable() .subscribe(response => console.log(response));// Tip: Find more about JS/TS SDKs at https://docs.kontent.ai/javascript const KontentDelivery = require('@kentico/kontent-delivery'); const deliveryClient = new KontentDelivery.DeliveryClient({ projectId: '<YOUR_PROJECT_ID>', typeResolvers: [ // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models new KontentDelivery.TypeResolver('article', (rawData) => new Article()) ] }); deliveryClient.item('my_article') .queryConfig({ waitForLoadingNewContent: true }) .toObservable() .subscribe(response => console.log(response));
// Tip: Find more about .NET SDKs at https://docs.kontent.ai/net using Kentico.Kontent.Delivery; // Initializes a client that retrieves the latest version of published content IDeliveryClient client = DeliveryClientBuilder .WithOptions(builder => builder .WithProjectId("<YOUR_PROJECT_ID>") .UseProductionApi .WaitForLoadingNewContent .Build()) .Build(); // Gets a content item // Create strongly typed models according to https://docs.kontent.ai/net-strong-types IDeliveryItemResponse<Article> response = await client.GetItemAsync<Article>("my_article"); Article item = response.Item;// Tip: Find more about .NET SDKs at https://docs.kontent.ai/net using Kentico.Kontent.Delivery; // Initializes a client that retrieves the latest version of published content IDeliveryClient client = DeliveryClientBuilder .WithOptions(builder => builder .WithProjectId("<YOUR_PROJECT_ID>") .UseProductionApi .WaitForLoadingNewContent .Build()) .Build(); // Gets a content item // Create strongly typed models according to https://docs.kontent.ai/net-strong-types IDeliveryItemResponse<Article> response = await client.GetItemAsync<Article>("my_article"); Article item = response.Item;
// Tip: Find more about PHP SDKs at https://docs.kontent.ai/php // Defined by Composer to include required libraries require __DIR__ . '/vendor/autoload.php'; use Kentico\Kontent\Delivery\DeliveryClient; $client = new DeliveryClient("<YOUR_PROJECT_ID>", null, true); $item = client->getItem('my_article');// Tip: Find more about PHP SDKs at https://docs.kontent.ai/php // Defined by Composer to include required libraries require __DIR__ . '/vendor/autoload.php'; use Kentico\Kontent\Delivery\DeliveryClient; $client = new DeliveryClient("<YOUR_PROJECT_ID>", null, true); $item = client->getItem('my_article');
curl --request GET \ --url https://deliver.kontent.ai/<YOUR_PROJECT_ID>/items/my_article \ --header 'X-KC-Wait-For-Loading-New-Content: true' \ --header 'content-type: application/json'curl --request GET \ --url https://deliver.kontent.ai/<YOUR_PROJECT_ID>/items/my_article \ --header 'X-KC-Wait-For-Loading-New-Content: true' \ --header 'content-type: application/json'
# Tip: Find more about Ruby SDKs at https://docs.kontent.ai/ruby require 'delivery-sdk-ruby' delivery_client = Kentico::Kontent::Delivery::DeliveryClient.new project_id: '<YOUR_PROJECT_ID>' delivery_client.item('my_article') .request_latest_content .execute do |response| item = response.item end# Tip: Find more about Ruby SDKs at https://docs.kontent.ai/ruby require 'delivery-sdk-ruby' delivery_client = Kentico::Kontent::Delivery::DeliveryClient.new project_id: '<YOUR_PROJECT_ID>' delivery_client.item('my_article') .request_latest_content .execute do |response| item = response.item end
// Tip: Find more about JS/TS SDKs at https://docs.kontent.ai/javascript import { ContentItem, DeliveryClient, Elements, TypeResolver } from '@kentico/kontent-delivery'; import { Article } from './models/Article'; const deliveryClient = new DeliveryClient({ projectId: '<YOUR_PROJECT_ID>', typeResolvers: [ // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models new TypeResolver('article', (rawData) => new Article) ] }); deliveryClient.item<Article>('my_article') .queryConfig({ waitForLoadingNewContent: true }) .toObservable() .subscribe(response => console.log(response));// Tip: Find more about JS/TS SDKs at https://docs.kontent.ai/javascript import { ContentItem, DeliveryClient, Elements, TypeResolver } from '@kentico/kontent-delivery'; import { Article } from './models/Article'; const deliveryClient = new DeliveryClient({ projectId: '<YOUR_PROJECT_ID>', typeResolvers: [ // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models new TypeResolver('article', (rawData) => new Article) ] }); deliveryClient.item<Article>('my_article') .queryConfig({ waitForLoadingNewContent: true }) .toObservable() .subscribe(response => console.log(response));
Retry policy
If your application responds with a 20X
HTTP status code, the notification delivery is considered successful. Any other status code or a request timeout (which occurs after 60 seconds) will result in a retry policy.
On the first unsuccessful delivery, we will try to send the notification again in 1 minute. If the delivery is unsuccessful, the delay between resending the notification increases exponentially to a maximum of 1 hour. The specific delay intervals are (in minutes): 1, 2, 4, 8, 16, 32, 60. When the delay reaches 60 minutes, we try to deliver the notification every hour for up to 3 days, after which the notification is removed from the queue.
All notifications are delivered in the order they were created. For example, if a notification is successfully delivered after 4 minutes, the notifications created after it will follow in the original order.
Email notifications
We will send email notifications to the users with the Manage APIs permission in these cases:
- Notification delivery repeatedly failing for 1 hour. This email is sent only once for each registered webhook.
- Notification delivery repeatedly failing for 3 days. Note that we will not attempt to deliver the notification again.
- Notification delivery was successful after failed attempts. This email is only sent if you previously received an email notification about a failed delivery.
Debug webhooks
If you get an email that a webhook is failing, you might want to know more about that webhook and what the problem is. For that, you can find more information inside Kentico Kontent in your list of webhooks under Project settings > Webhooks.
For an overview of the health of your webhooks, each webhook in your list has a colored status next to its name:
- Light grey – Ready for message. This appears for newly created webhooks before any change to published content has been made (so no notification has been sent).
- Green – Working. This appears for webhooks that have properly delivered notifications.
- Red – Failing. This appears for webhooks that have not been delivered properly (received a response other than a
20X
HTTP status code). These webhook notifications are still being sent based on the retry policy. - Grey – Dead. This appears for webhooks where delivery has repeatedly failed and no notifications have been accepted for 7 days. After this time, no more notifications will be sent and all notifications waiting to be sent will be deleted. You can revive a dead webhook by changing its URL.
Revive a dead webhook
To revive a dead webhook, you need to change its URL. The logic behind processing webhooks detects the URL change and revives the webhook.
If you need to keep the current webhook URL, change the URL only temporarily.
- Change the URL of the dead webhook.
- Make an action that triggers the webhook, for example, publish or unpublish a content item.
- Wait a couple of minutes until the revived webhook receives a notification.
- Change the webhook URL back to the old one.
Get more information about your webhooks
For more information about each webhook, click Debugging. You'll find a list of all notifications with attempts at sending within the last 3 days, sorted from newest to oldest. You can filter the list to show everything, only failures (at any time in sending the message), or only active failures (where the last response was a failure). Click Refresh to reload the list.
Each notification in the list shows:
- How many times the delivery has been attempted.
- A button to view the most recent response.
- In the window that pops up, a button Copy to clipboard to copy the response to clipboard.
- The date and time when the most recent delivery attempt was made.