Skip to content

Webhooks

Internally in PIM, domain events are being raised when product catalogue items and other entities are created, updated, deleted, etc.

A subset of these events can be subscribed to from external applications via webhooks.

Event flow

The flow of operations and events in PIM is pretty complex and varies a bit depending on the particular operation but let's take a look at the fairly simple case of the BrandViewsCreated event.

graph LR
  A[Brand created] --> B[Build frontend view] --> C[BrandViewsCreated raised] --> D{Webhook subscribers?};
  D -->|Yes| E[Call webhooks];
  D -->|No| F[Done];

So basically BrandViewsCreated is the internal event that is raised as a result of a brand frontend view being built (as a result of a brand being created). Webhooks are called as the last step in the flow.

Note

Some of these steps are asynchronous so from the time the brand is created and until the webhook receives the event it might take anywhere from a few seconds up to a couple of minutes depending on how busy PIM is at the moment.

Webhook forwarding

The webhook forwarding runs in a recurring Hangfire job named ForwardWebHookSubscriberEvents:

ForwardWebHookSubscriberEvents

It runs in 1-minute intervals but it can also be triggered manually from the Hangfire UI (System Administration → Jobs).

Forwarding strategy:

  • Forward one event at a time
  • Wait at most 10 seconds for webhook to respond with status code 200 or 204 or else consider the request failed
  • If a request fails, then the reason is logged
  • In case there are failed requests, retry forwarding later (up to 10 times)
  • In case entire batch fails (due to internal errors in PIM), retry entire batch later

Note

Permanently failed requests (10 failed attempts) will stay in the queue until the corresponding domain event reaches its max age. The default value is 3 days after which the event will be deleted.

Be sure to monitor the log files and act on failing requests in due time if they are critical.

Criterias for forwarding:

  • Webhook must be registered for the particular event
  • The event must be successfully handled internally in PIM
  • The event must have less than 10 forwarding attempts

Exposed domain events

Internally in PIM there are hundreds of events, but only a subset of them are exposed via webhooks.

Find the list of exposed events in the PIM UI under Administration → Webhooks. For each event there is a payload example in the UI that shows how the event payload is serialized.

Registering webhooks

In order to subscribe to events, you must register one or more webhook urls. This is done either via the api or in the UI under Administration → Webhooks:

Register webhooks

Click View on the event of interest, add a new item and set a url and a secret:

Webhook details

The secret must be a string of at least 32 characters. It's the value for the IssuerSigningKey of a JWT token. The same value should be used on the receiving end of the webhook. See code example in the "Implementation of webhooks" section.

If you prefer using the api instead of the ui that's possible in the domain-events resource:

Webhook api

Use the PUT operation. Url and secret previously mentioned are specified in the request body.

Implementation of webhooks

When implementing a webhook please follow these guidelines.

  • Implement as idempotent
  • Reply 200/204 within 10 seconds (otherwise request will be considered failed)

The following code example is the application startup code for a simple .NET application in which controllers can be added to receive domain events via webhooks.

It serves to illustrate how to possibly configure it with a secret ("LongSecretWithAtLeast32Characters"). The secret must match the secret specified for the webhook URL in PIM.

Click to expand code example...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;

namespace TestAspNetCore_Api
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddAuthentication(e =>
            {
                e.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                e.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(e =>
            {
                e.TokenValidationParameters = new TokenValidationParameters
                {
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("LongSecretWithAtLeast32Characters")),
                    ValidateIssuerSigningKey = true,
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ValidateLifetime = true,
                    RequireSignedTokens = true,
                    TokenDecryptionKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("LongSecretWithAtLeast32Characters"))
                };
                e.IncludeErrorDetails = true;
                e.Validate(JwtBearerDefaults.AuthenticationScheme);
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseDeveloperExceptionPage();
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseDeveloperExceptionPage();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

When implementing the webhook endpoint for a given event, Bizzkit.Sdk.Pim api client supplies a payload type named after the event.

So for example the ProductCatalogueItemCreated event has a payload type named ProductCatalogueItemCreatedPayload to which the payload can be deserialized.

The following is an example of an endpoint in an ASP.NET MVC controller for ProductCatalogueItemCreated:

1
2
3
4
5
6
7
[HttpPost("product-catalogue-item-created")]
[Consumes("application/json")]
public IActionResult ProductCatalogueItemCreated([FromBody] Bizzkit.Sdk.Pim.ProductCatalogueItemCreatedPayload payload)
{
   // Do something
   return Ok();
}