Skip navigation

Use webhooks for automatic updates

11 min read

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:

    Using webhooks, you can also react to workflow changes in content items to accomplish tasks like:

    How webhooks work in a nutshell

    Let's go through the process in more detail.

    Create a webhook

    To register a new webhook in your project:

    1. In Kentico Kontent, go to Project settings.
    2. Under Environment settings, select Webhooks.
    3. Click Create new webhook.
    4. Enter a name for the webhook such as Invalidate cache.
    5. Enter a publicly available URL address of your webhook endpoint, such as https://example.com/cache-invalidate.
    6. (Optional) Choose the events to trigger the webhook. By default, the events related to content items and taxonomy are selected.
    7. Click Save.
    Filled-in webhook properties with the default triggers.

    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 notifications (as 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 base64 encoded hash generated using a HMAC-SHA-256 with a secret key.

    Tip: Save time with sample webhook handlers

    Try our sample Kontent webhook handlers 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.

    • Java
    // 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))); }
    • JavaScript
    // 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'] ); };
    • C#
    // 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); } }
    • PHP
    // 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);
    • TypeScript
    // 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 content (if cached by the CDN) while it's fetching the latest content to minimize wait time.

    • Java
    // 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
    • JavaScript
    // 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));
    • C#
    // 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;
    • PHP
    // 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
    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'
    • Ruby
    # 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
    • TypeScript
    // 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. 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 health 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. You can also reset the webhook if you've issued a fix and want to reset 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 resetting it.

    Reset and revive webhooks

    If your webhook is failing or dead, you can reset it. Resetting a webhook means that Kontent attempts to resend the last failed notification, effectivelly resetting the webhook's retry policy.

    1. In Kentico Kontent, go to Project settings.
    2. Under Environment settings, select Webhooks.
    3. Click a failing or dead webhook to open its details.
    4. Click Reset.

    Disable and enable webhooks

    If you plan to make changes to your content model, you might want to disable your webhooks and adjust your app to the new content model. Disabling webhooks also helps if you plan to release hundreds or thousands of content items in a short amount of time.

    When you disable a webhook, the webhook stops sending notifications and deletes any unsent notifications. Disabled webhooks ignore their triggers and don't create any new notifications.

    1. In Kentico Kontent, go to Project settings.
    2. Under Environment settings, select Webhooks.
    3. Click a webhook to open its details.
    4. Click Disable.

    After you enable the webhook, it begins sending new notifications whenever the webhook is triggered.

    Get more information about your webhooks

    For more information about each webhook, click . 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 to copy the response to the clipboard.
    • The date and time when the most recent delivery attempt was made.

    What's next?