Mosaic products documentation: Concepts, API Reference, Technical articles, How-to, Downloads and tools

Webhooks

Introduction

Webhooks are a way for your application to provide on-request information to our Manages Services. They are essentially HTTP callbacks that are triggered by specific events. This mechanism allows you to extend the functionality of our Managed Services by integrating them with your services.

A webhook is a URL configured in our service, which can be invoked via an HTTP POST request. This request is accompanied by a digital signature to authenticate that it originates from our service. Webhooks operate on a synchronous request-and-response pattern. Our service initiates this pattern by sending a JSON message as a POST request, with the expectation of receiving a JSON response in the "application/json" format.

Configure a Webhook

You can configure your webhooks for our Managed Services that support webhooks. One example is the Image Service where you can validate the image metadata to ensure that the image being uploaded conforms with any quality requirements.

Webhook Settings
Figure 1. service settings

To configure the webhook, click on the desired webhook row in the webhook list. This will navigate you to the Create Webhook station. Provide a URL for the webhook and click on Proceed. This will create a secret for the webhook. Copy the webhook secret and store it securely. This secret is required to validate the authenticity of the request coming to the webhook as described in the next section.

Creating a webhook

When a Managed Service wants to get additional information from your service, it calls your service via an HTTP POST request to the URL that you configured in the settings (e.g. the image upload webhook). The payload of this request will have metadata related to the specific action that will allow you to build the required response logic around it.

After your evaluation/action is completed, the webhook must return an HTTP response 200 for a successful action or a 401 status code containing errors if there are any.

To check the authenticity of the request and to ensure that the request has not been tampered with, webhook requests are digitally signed.

Request

Format

Webhook messages have a JSON request body with the following properties:

Field Name Example Description

payload

{ "movie-id": 123 }

Payload containing webhook-specific data

message_id

41409546-df67-42b6-b5c4-c006b26b6bad

Unique identifier for the message

timestamp

2022-09-22T09:26:10.498Z

Timestamp indicating when the message was sent

message_type

PublishEntityXYZ

Type of the message

message_version

1.0

Version of the message format, e.g., "1.0"

Example
{
   "payload":{
      "movie-id": 123
   },
   "message_id":"41409546-df67-42b6-b5c4-c006b26b6bad",
   "timestamp": "2022-09-22T09:26:10.498Z",
   "message_type": "PublishEntityTypeA",
   "message_version": "1.0"
}

The Webhook JSON schema payloads and Typescript interface for the messages can be found in the @axinom/mosaic-messages package.

Validating the webhook request

The secret created during the configuration of the webhook is used to create a hash-based message authentication code (<<https://en.wikipedia.org/wiki/Hash-based_message_authentication_code,HMAC>>) with SHA-256. This HMAC is created by using the stringified value of the request body and is attached to the request headers through the custom header x-mosaic-signature.

The webhook can use the same secret to validate if the webhook data has been tampered with using the received request body. For this validation, the webhook implementor can use the validateWebhookRequest method in the @axinom/mosaic-service-common library.

You can alternatively verify the signature manually. Take the full request body in its raw form and append the webhook secret. Then you compute the HMAC with the SHA256 hash function from this string. If the calculated one matches the one from the X-Mosaic-Signature the request is valid and should be processed.

Response

Your webhook response must have a JSON body with the following properties:

Field Name Example Description

payload

{ "valid": "true" }

Payload containing webhook-specific data

errors

[{ "message": "The error message", "code": "ERROR_CODE_1" }]

An array of error objects with message and code

warnings

[{ "message": "The warning message", "code": "WARNING_CODE_1" }]

An array of warning objects with message and code

message_version

1.0

Version of the message format, e.g., "1.0"

Example
{
   "payload":{
      "valid": "true"
   },
   "errors": [{
     "message": "The error message",
     "code": "ERROR_CODE_1"
   }],
   "warnings": [{
     "message": "The warning message",
     "code": "WARNING_CODE_1"
   }],
   "message_version": "1.0"
}

The Webhook JSON schema responses and Typescript interface for the responses can be found in the @axinom/mosaic-messages package.

Example webhook implementation

This code provides an HTTP POST endpoint under the /demo URL path. The verifyWebhookRequestMiddleware is used to verify that the request is correctly signed. When everything is fine, it returns a payload that contains demo: true.

Example of a Webhook definition
import {
  Dict,
  generateWebhookResponse,
  handleWebhookErrorMiddleware,
  verifyWebhookRequestMiddleware,
  WebhookRequestMessage,
} from '@axinom/mosaic-service-common';
import { Express, NextFunction, Request, Response } from 'express';
import { Config } from '../../common';

export const setupDemoWebhookEndpoint = (
  app: Express,
  config: Config,
): void => {
  app.post(
    '/demo',

    // Mosaic middleware to validate Request signature
    verifyWebhookRequestMiddleware({
      webhookSecret: config.demoWebhookSecret,
      payloadJsonSchema: DemoWebhookRequestPayloadSchema,
    }),

    // Mosaic middleware to return a 401 response in case of an error
    handleWebhookErrorMiddleware,

    // Your actual business logic implementation
    async (
      req: Request<
        Dict<string>,
        Dict<string>,
        WebhookRequestMessage<DemoWebhookRequestPayload>
      >,
      res: Response,
      next: NextFunction,
    ) => {
      try {
        const response = generateWebhookResponse<DemoPayload>({
          payload: {
            demo: true,
          },
        });
        res.status(200).send(response);
      } catch (error) {
        handleWebhookErrorMiddleware(error, req, res, next);
      }
    },
  );
};

Using webhooks during development

As stated before, you need to configure a corresponding webhook URL for a service in the Administration Portal. This is straightforward when the service containing the webhook endpoint is already deployed and has a publicly accessible URL.

But during the development, the Managed Services are not able to just make requests to your running localhost services (e.g. http://localhost:11600/manifest), since they are not publicly accessible. To make this possible and to be able to test that your webhook actually works as intended in a more convenient way during the development, it is possible to use one of the many tunneling solutions to expose your localhost endpoint as a public URL, allowing managed services to call your development service instance.