Skip navigation

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:

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

    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.

    Creating a webhook

    To register a new webhook in your project:

    1. In Kentico Kontent, choose from the app menu.
    2. Under Environment settings, choose 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.
    A screenshot of a new webhook with 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.

    Validating 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.

    Here's an example of generating the hash and verifying the notification.

    • Java
    // Tip: Find more about Java/JavaRx SDKs at https://docs.kontent.ai/java import javax.crypto; import javax.crypto.spec; import javax.xml.bind; 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/JavaRx SDKs at https://docs.kontent.ai/java import javax.crypto; import javax.crypto.spec; import javax.xml.bind; 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'); // Note: Use raw body data from the request, for example, by using body-parser const isValidSignature = (req, secret) => { return signatureHelper(req.body, 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'); // Note: Use raw body data from the request, for example, by using body-parser const isValidSignature = (req, secret) => { return signatureHelper(req.body, 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; 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; 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
    <?php $givenSignature = $_SERVER['HTTP_X_KC_SIGNATURE']; $computedSignature = base64_encode(hash_hmac('sha256', $json_message, $secret, true)); $result = hash_equals($givenSignature, $computedSignature); ?>
    <?php $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'; // Note: Use raw body data from the request, for example, by using body-parser const isValidSignature = (req, secret) => { return signatureHelper(req.body, 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'; // Note: Use raw body data from the request, for example, by using body-parser const isValidSignature = (req, secret) => { return signatureHelper(req.body, secret, req.headers['x-kc-signature']); };

    Once you've received and verified the message, you can see what's inside and react accordingly.

    Getting 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 new content to minimize wait time.

    • Java
    // Tip: Find more about Java/JavaRx SDKs at https://docs.kontent.ai/javaandroid import com.github.kentico.kontent_delivery_core.*; import com.github.kentico.kontent_delivery_rx.*; import io.reactivex.Observer; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Function; // Prepares an array to hold strongly-typed models List<TypeResolver<?>> typeResolvers = new ArrayList<>(); // Registers the type resolver for articles typeResolvers.add(new TypeResolver<>(Article.TYPE, new Function<Void, Article>() { @Override public Article apply(Void input) { return new Article(); } })); // Prepares the DeliveryService configuration object String projectId = "<YOUR_PROJECT_ID>"; IDeliveryConfig config = DeliveryConfig.newConfig(projectId) .withDefaultQueryConfig(new QueryConfig(true, false)) .withTypeResolvers(typeResolvers); // Initializes a DeliveryService for Java projects IDeliveryService deliveryService = new DeliveryService(config); // Gets specific elements of an article using a simple request Article article = deliveryService.<Article>item("my_article") .get() .getItem(); // Gets specific elements of an article using RxJava2 deliveryService.<Article>item("my_article") .getObservable() .subscribe(new Observer<DeliveryItemResponse<Article>>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(DeliveryItemResponse<Article> response) { // Gets the article Article article = response.getItem(); } @Override public void onError(Throwable e) { } @Override public void onComplete() { } });
    // Tip: Find more about Java/JavaRx SDKs at https://docs.kontent.ai/javaandroid import com.github.kentico.kontent_delivery_core.*; import com.github.kentico.kontent_delivery_rx.*; import io.reactivex.Observer; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Function; // Prepares an array to hold strongly-typed models List<TypeResolver<?>> typeResolvers = new ArrayList<>(); // Registers the type resolver for articles typeResolvers.add(new TypeResolver<>(Article.TYPE, new Function<Void, Article>() { @Override public Article apply(Void input) { return new Article(); } })); // Prepares the DeliveryService configuration object String projectId = "<YOUR_PROJECT_ID>"; IDeliveryConfig config = DeliveryConfig.newConfig(projectId) .withDefaultQueryConfig(new QueryConfig(true, false)) .withTypeResolvers(typeResolvers); // Initializes a DeliveryService for Java projects IDeliveryService deliveryService = new DeliveryService(config); // Gets specific elements of an article using a simple request Article article = deliveryService.<Article>item("my_article") .get() .getItem(); // Gets specific elements of an article using RxJava2 deliveryService.<Article>item("my_article") .getObservable() .subscribe(new Observer<DeliveryItemResponse<Article>>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(DeliveryItemResponse<Article> response) { // Gets the article Article article = response.getItem(); } @Override public void onError(Throwable e) { } @Override public void onComplete() { } });
    • Java
    // Tip: Find more about Java/JavaRx SDKs at https://docs.kontent.ai/java import com.github.kentico.kontent.delivery; DeliveryOptions deliveryOptions = new DeliveryOptions(); deliveryOptions.setProjectId("<YOUR_PROJECT_ID>"); deliveryOptions.setWaitForLoadingNewContent(true); DeliveryClient client = new DeliveryClient(deliveryOptions); ContentItemResponse item = client.getItem("my_article");
    // Tip: Find more about Java/JavaRx SDKs at https://docs.kontent.ai/java import com.github.kentico.kontent.delivery; DeliveryOptions deliveryOptions = new DeliveryOptions(); deliveryOptions.setProjectId("<YOUR_PROJECT_ID>"); deliveryOptions.setWaitForLoadingNewContent(true); DeliveryClient client = new DeliveryClient(deliveryOptions); ContentItemResponse item = client.getItem("my_article");
    • JavaScript
    // Tip: Find more about JS/TS SDKs at https://docs.kontent.ai/javascript const KontentDelivery = require('@kentico/kontent-delivery'); // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models class Article extends KontentDelivery.ContentItem { constructor() { super(); } } const deliveryClient = new KontentDelivery.DeliveryClient({ projectId: '<YOUR_PROJECT_ID>', typeResolvers: [ 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'); // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models class Article extends KontentDelivery.ContentItem { constructor() { super(); } } const deliveryClient = new KontentDelivery.DeliveryClient({ projectId: '<YOUR_PROJECT_ID>', typeResolvers: [ 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/strongly-typed-models DeliveryItemResponse response = await client.GetItemAsync("my_article"); ContentItem 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/strongly-typed-models DeliveryItemResponse response = await client.GetItemAsync("my_article"); ContentItem item = response.Item;
    • PHP
    <?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');
    <?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');
    • 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'; // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models export class Article extends ContentItem { public title: Elements.TextElement; public summary: Elements.TextElement; public post_date: Elements.DateTimeElement; public teaser_image: Elements.AssetsElement; public related_articles: Article[]; } const deliveryClient = new DeliveryClient({ projectId: '<YOUR_PROJECT_ID>', typeResolvers: [ 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'; // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models export class Article extends ContentItem { public title: Elements.TextElement; public summary: Elements.TextElement; public post_date: Elements.DateTimeElement; public teaser_image: Elements.AssetsElement; public related_articles: Article[]; } const deliveryClient = new DeliveryClient({ projectId: '<YOUR_PROJECT_ID>', typeResolvers: [ 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 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.

    Debugging 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 > 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. To revive a dead webhook, change the webhook's URL.

    For more information about each webhook, click on 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 will show:

    • How many times the delivery has been attempted
    • A button () to see the most recent response
    • The date and time when the most recent delivery attempt was made
    • A button () to see the content of the sent notification with the chance to copy it

    What's next?