Webhooks

Here we describe how you can listen to events in LeanIX using Webhooks.

What are Webhooks?

Webhooks allow you to receive information about events as they happen in near real-time in LeanIX. You can then extend, customize, and integrate LeanIX with your own extensions or even with other applications. LeanIX can deliver events in two ways:

  • PUSH – Events are sent as HTTP POST requests to a server provided by you. This is commonly described as a webhook, e.g. on the PBWiki Webhooks page. It is the preferred way to receive LeanIX events.
  • PULL – If an environment does not allow exposing a public HTTP server to receive events, they can also be retrieved by repeatedly calling a REST endpoint on the LeanIX API. This way of polling events is helpful e.g. when your application is running behind a firewall. Events are retained on the server for 30 days, but it is recommended to retrieve them more regularly.

We have built our own Slack Bot using Webhooks, check out the example here.

To test PUSH Webhooks before setting up scripts, the Webhook Tester tool is an excellent utility that helps you see data come across as various events happen in our system. To test PULL Webhooks manually, a command line HTTP client like curl can be used.

Configuring Webhooks

Webhooks are configured in the LeanIX Administration. Here are the basic steps:

  1. Log in to LeanIX account
  2. Navigate to Administration
  3. Click on "Webhooks" in the sidebar
  4. Click the "New webhook" button

Configuration is simple: Select the event types you want to receive, provide an optional name and select the desired delivery type. Depending on the delivery type, different options are available.

Configure a Webhook

Configure a Webhook

Event Types and Payloads

We'll return various data based on each event type. Below is sample data for each event type. You can also see this using the Webhook Tester tool mentioned above.

  • FACT_SHEET_CREATED – New Fact Sheet is created (or recovered from archive)
  • FACT_SHEET_UPDATED – Fact Sheet is updated (incl. one of the relations)
  • FACT_SHEET_ARCHIVED – Fact Sheet is archived
  • FACT_SHEET_DELETED – Fact Sheet is deleted (e.g. when a Fact Sheet type is removed completely from the data model)

In general, the payload for the above event types contains information on the event, the user which triggered the event, meta information such as the type of the Fact Sheet and the actual Fact Sheet data like fields and relations.

{
  "createdAt": "2017-12-20T12:24:33Z",
  "factSheet": {
    "completion": "{...}",
    "createdAt": "2017-12-20T12:24:32.913Z",
    "displayName": "Webhooks Example",
    "documents": "[...]",
    "fields": "[...]",
    "fullName": "Webhooks Example",
    "id": "62c40a1a-7626-45b9-a6d0-4a399df32a09",
    "level": 1,
    "naFields": "[...]",
    "name": "Webhooks Example",
    "relations": "[...]",
    "rev": 0,
    "status": "ACTIVE",
    "subscriptions": "[...]",
    "type": "Application",
    "updatedAt": "2017-12-20T12:24:32.915Z"
  },
  "id": 5660,
  "transactionSequenceNumber": 65871,
  "type": "FactSheetCreatedEvent",
  "userId": "c5dc97b3-7c1d-42c1-b0b5-dc8be4e06030",
  "workspaceId": "cdc70f10-be33-48ed-bd66-e20028d0e6f4"
}
{
  "createdAt": "2017-12-20T12:25:04.572Z",
  "factSheet": {
    "completion": "{..}",
    "createdAt": "2017-12-20T12:24:32.913Z",
    "displayName": "Webhooks Example",
    "documents": "[...]",
    "fields": [
      {
        "data": {
          "keyword": "businessCritical",
          "type": "SingleSelect"
        },
        "dataType": null,
        "name": "businessCriticality"
      },
      {
        "data": {
          "type": "StringValue",
          "value": "Example Description"
        },
        "dataType": null,
        "name": "businessCriticalityDescription"
      }
    ],
    "fullName": "Webhooks Example",
    "id": "62c40a1a-7626-45b9-a6d0-4a399df32a09",
    "level": 1,
    "naFields": "[...]",
    "name": "Webhooks Example",
    "relations": "[...]",
    "rev": 2,
    "status": "ACTIVE",
    "subscriptions": "[...]",
    "type": "Application",
    "updatedAt": "2017-12-20T12:25:04.540Z"
  },
  "id": 5668,
  "transactionSequenceNumber": 65885,
  "type": "FactSheetUpdatedEvent",
  "userId": "c5dc97b3-7c1d-42c1-b0b5-dc8be4e06030",
  "workspaceId": "cdc70f10-be33-48ed-bd66-e20028d0e6f4"
}
{
  "affectedFactSheetIdAndRev": [
    {
      "id": "62c40a1a-7626-45b9-a6d0-4a399df32a09",
      "rev": 2
    }
  ],
  "comment": "Example Comment",
  "createdAt": "2017-12-20T12:25:29.165Z",
  "fsIdAndRev": {
    "id": "62c40a1a-7626-45b9-a6d0-4a399df32a09",
    "rev": 2
  },
  "id": 5670,
  "transactionSequenceNumber": 65890,
  "type": "FactSheetArchivedEvent",
  "userId": "c5dc97b3-7c1d-42c1-b0b5-dc8be4e06030",
  "workspaceId": "cdc70f10-be33-48ed-bd66-e20028d0e6f4"
}
{
  "createdAt": "2017-12-20T15:00:11.768Z",
  "fsIdAndRev": {
    "id": "62c40a1a-7626-45b9-a6d0-4a399df32a09",
    "rev": 2
  },
  "id": 5672,
  "transactionSequenceNumber": 65895,
  "type": "FactSheetDeletedEvent",
  "userId": "c5dc97b3-7c1d-42c1-b0b5-dc8be4e06030",
  "workspaceId": "cdc70f10-be33-48ed-bd66-e20028d0e6f4"
}

Advanced: Access further Event Types

Beside the listed event types, we have other event types available that are currently not configurable via the UI. Instead, please use the /services/webhooks/v1/subscriptions endpoint for this, e.g.

curl --request POST \
  --url https://app.leanix.net/services/webhooks/v1/subscriptions \
  --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJz [...]'
  --data '{
    "deliveryType": "PULL",
    "tagSets": [
      [
        "pathfinder",
        "FACT_SHEET_CREATED"
      ]
    ],
    "workspaceId": "<WS_ID>"
  }

Examples of other available events are login events ( ["MTM","USER_ACCESS_WORKSPACE"] ), fact sheet views ( ["pathfinder", "FACT_SHEET_VIEWED"] ) or nightly workspace statistics ( ["pathfinder", "WORKSPACE_STATISTICS"] and ["MTM", "WORKSPACE_STATISTICS"]).

{
  "createdAt": "2018-09-21T15:43:22.038Z",
  "actor": "{}",
  "id": "3ad693a7-4fe3-47cf-80d7-b26c5fa2d0c1",
  "status": "FINISHED",
  "type": "USER_ACCESS_WORKSPACE",
  "user": {
            "id": "3b36751d-8bab-4781-9cb0-cd3f96d1875a", ...
          },
  "workspace": {
            "id": "97b10c0a-1fdf-4bf9-b197-edd29803ad44", ...
            },
  "instance": "{}",
  "links": "{}",
  "payload": {
    "ipAddress": "84.189.92.190",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"
  }
}
{
  "createdAt": "2017-12-20T12:24:33Z",
  "factSheetId": "8e421182-06d7-4b25-9740-d8a4bae2d75c",
  "id": 5660,
  "transactionSequenceNumber": 65871,
  "type": "FactSheetViewedEvent",
  "userId": "c5dc97b3-7c1d-42c1-b0b5-dc8be4e06030",
  "workspaceId": "cdc70f10-be33-48ed-bd66-e20028d0e6f4"
}
{
  "createdAt":"2018-08-16T22:15:26.435Z",
  "type":"WorkspaceStatisticsEvent",
  "workspaceId":"97b10c0a-1fdf-4bf9-b197-edd29803ad44",
  "wss": {
    "workspaceId":"97b10c0a-1fdf-4bf9-b197-edd29803ad44",
    "createdAt":"2018-08-16T22:15:24.456Z",
    "version":4,
    "factSheets": "{}",
    "relations": "{}",
    "events": "{}",
    "tagging": "{}",
    "comments": "{}",
    "bookmarks": "{}",
    "reports": "{}",
    "todos": "{}",
    "exports": "{}",
    "completion": "{}"
  }
}
{
  "createdAt": "2018-09-23T12:51:56.784Z",
  "id": "2715584d-33f9-4f00-a5c0-5cff638b9dc7",
  "application": "mtm",
  "version": "1",
  "status": "FINISHED",
  "type": "WORKSPACE_STATISTICS",
  "actor": "{ }",
  "workspace": "{ }",
  "instance": "{ }",
  "links": "[ ]",
  "payload": {
    "createdAt": "2018-09-23T12:51:56.711Z",
    "permissions": {
      "totalCount": 11,
      "status": {
        "NOTINVITED": "{ }",
        "INVITED": "{ }",
        "ANONYMIZED": "{ }",
        "ACTIVE": "{ }",
        "ARCHIVED": "{ }",
        "REQUESTED": "{ }"
      }
    },
    "version": 1,
    "workspaceId": "52daf065-d505-456c-b7d8-5498cd1113c5"
  }
}

List of Webhook Events

Webhook Events

FACT_SHEET_CREATED
FACT_SHEET_VIEWED
FACT_SHEET_RECOVERED
FACT_SHEET_DELETED
FACT_SHEET_TYPE_FIELD_CREATED
FACT_SHEET_A_C_E_ADDED
FACT_SHEET_FIELD_UPDATED
FACT_SHEET_TYPE_DELETED
FACT_SHEET_TYPE_CREATED
FACT_SHEET_ARCHIVED
FACT_SHEET_TAG_ADDED
FACT_SHEET_TYPE_FIELD_DELETED
FACT_SHEET_A_C_E_REMOVED
FACT_SHEET_TAG_REMOVED
FACT_SHEET_ACCESS_CONTROL_LIST_UPDATED
FACT_SHEET_UPDATED

WORKSPACE_STATISTICS
WORKSPACE_UPDATED
WORKSPACE_CREATED
WORKSPACE_REFRESH
WORKSPACE_INITIALIZED
WORKSPACE_DELETED

TAG_GROUP_DELETED
TAG_GROUP_TAG_ADDED
TAG_GROUP_UPDATED
TAG_GROUP_TAG_REMOVED
TAG_GROUP_CREATED

LIFECYCLE_PHASE_CHANGED
LIFECYCLE_PHASE_CHANGING_SOON

RELATION_ARCHIVED
RELATION_RECOVERED
RELATION_CREATED
RELATION_INTENTIONALLY_NOT_SET_REMOVED
RELATION_UPDATED
RELATION_VALIDITY_UNTIL_CHANGED
RELATION_VALIDITY_FROM_CHANGED
RELATION_SWITCH
RELATION_INTENTIONALLY_NOT_SET_ADDED
RELATION_DELETED

RELATION_TYPE_CREATED
RELATION_TYPE_FIELD_DELETED
RELATION_TYPE_CONSTRAINING_RELATION_ADDED
RELATION_TYPE_FIELD_CREATED
RELATION_TYPE_DELETED

SUBSCRIPTION_DELETED
SUBSCRIPTION_ROLE_CREATED
SUBSCRIPTION_ROLE_UPDATED
SUBSCRIPTION_ROLE_DELETED
SUBSCRIPTION_CREATED
SUBSCRIPTION_UPDATED

DOCUMENT_CREATED
DOCUMENT_UPDATED

COMMENT_CREATED
COMMENT_REPLY_ADDED
COMMENT_DELETED
COMMENT_UPDATED

TODO_CREATED
TODO_UPDATED
TODO_DELETED

INTENTIONALLY_NOT_SET_ADDED
INTENTIONALLY_NOT_SET_REMOVED

BOOKMARK_DELETED
BOOKMARK_VIEWED
BOOKMARK_BASE
BOOKMARK_UPDATED
BOOKMARK_CREATED

QUALITY_SEAL_APPROVED
QUALITY_SEAL_BROKEN

EXTERNAL_ID_FIELD_DEFINITION_UPDATED
EXTERNAL_ID_FIELD_DEFINITION_CREATED
EXTERNAL_ID_FIELD_DEFINITION_DELETED

ACCESS_CONTROL_ENTITY_BASE
ACCESS_CONTROL_ENTITY_DELETED
ACCESS_CONTROL_ENTITY_UPDATED
ACCESS_CONTROL_ENTITY_CREATED

REPORT_DELETED
REPORT_INSTALLED

TAG_CREATED
TAG_UPDATED
TAG_DELETED

FULL_EXPORT_FINISHED
LANGUAGE_CREATED
REPORT_VIEWED
CONSTRAINING_RELATIONS_UPDATED
DOCUMENT_DELETED
LANGUAGE_UPDATED
TRANSACTION_COMMITTED

Using PUSH Webhooks

For events to be delivered using PUSH, a Target URL needs to be configured for the Webhook. An HTTP POST requests with the event payload in the body will be sent to this URL every time an event occurs in LeanIX. Your server is expected to return an HTTP status code of 200 to signal that an event was received successfully. The results for event deliveries from the last 30 days are listed on the Webhook details page, below the configuration fields.

Security

For the target URL, we support either HTTP or HTTPS, so you can have security by using an SSL-enabled URL. Please remember that your endpoint is going to be wide-open on the internet, and you might not want others to be able to submit random data to your systems. At this time, aside from trying to keep the URL private we recommend two ways to protect your Webhooks endpoint:

A. Make use of the Authentication-Header which we allow to be set, e.g. using Basic Auth.
B. Append a token to the query string of the URL which you check in your code.

Advanced: Manipulate Payload using Callback

We provide an option to define a callback which allows you to manipulate the payload using JavaScript code before it is sent out to the defined URL. See our example of the LeanIX Slack Bot as a great use case which makes use of this callback.

Within the JavaScript code, the object delivery is available in the global scope. It has, among others, the properties payload, targetMethod or targetUrl. You can manipulate these properties right from your code.

var payload = delivery.payload;

delivery.targetMethod = 'GET';
delivery.targetUrl = delivery.targetUrl += '?test=1';

delivery.payload = {
  text : 'Hello World'
};

Using PULL Webhooks

When events are retrieved using PULL, there is only one additional configuration option: maxBatchSize. It defines a soft upper limit for the amount of data in each batch of events. Usually, batches will contain less than the specified amount of data. However, when a single event exceeds the given value, it will be delivered anyways. By default, maxBatchSize is set to 512 kB.

Webhooks Java SDK

While PULL Webhooks can be consumed using any HTTP client, we also provide a Java SDK for convenient use in Java applications. When using Maven, it can be added to a project with the following dependency declaration in pom.xml:

<dependency>
    <groupId>net.leanix</groupId>
    <artifactId>leanix-webhooks-sdk-java</artifactId>
    <version>0.8.0</version>
</dependency>

To use the SDK, you simply create instances of ApiClient and SubscriptionsApi, with an API Token for authentication:

ApiClient client = new ApiClientBuilder()
    .withBasePath("https://app.leanix.net/services/webhooks/v1")
    .withTokenProviderHost("app.leanix.net")
    .withApiToken("JqcSfeB7sO3Bd9dEDcSOXfjs6G6ORCsT6G9fBHCc")
    .build();

SubscriptionsApi subscriptionsApi = new SubscriptionsApi(client);

In the following, we will document both: How the API can be consumed on the command line using curl and how the Java SDK can be used for the same task.

Retrieving Events

For webhooks using PULL, events can be retrieved using an HTTP GET request to the /services/webhooks/v1/subscriptions/<subscriptionId>/events endpoint. As for all requests to the LeanIX API, an access token has to be provided. See the Authentication documentation for details. Please keep in mind that events are retained on the server for 30 days, after this period they are not available for retrieval anymore.

curl --request GET \
  --url https://app.leanix.net/services/webhooks/v1/subscriptions/888eaaf3-c72f-411c-a78a-d382ba9b2f75/events \
  --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJz [...]'
UUID subscriptionId = UUID.fromString("c016b83a-aad2-4072-b419-9cb625d91072");
PullResultResponse response = subscriptionsApi.getSubscriptionEvents(subscriptionId, false, 10);

ObjectMapper mapper = new JSON().getContext(null);

for (PullEvent pullEvent : response.getData().getEvents()) {
  String eventString = pullEvent.getEvent();
  ObjectNode eventNode = (ObjectNode) mapper.readTree(eventString);
  // work with the event
}

The request will return a JSON result that is shaped as follows:

{
  "data": {
    "events": [
      {
        "event": "{...}",
        "cursor": {
          "offset": 178
        }
      },
      {
        "event": "{...}",
        "cursor": {
          "offset": 179
        }
      }
    ],
    "nextCursor": {
        "offset": 182
    }
  },
  "errors": [],
  "status": "OK",
  "total": 3,
  "type": "PullResult"
}

The event objects themselves are the same as described above. Additionally, each event is annotated with a cursor that can be used later to "rewind" the webhook to the given event. Also, for the whole batch of events, there is a nextCursor.

Moving the Cursor

After retrieving and processing a batch of events successfully, the cursor of the webhook needs to be moved forward. Otherwise, the next GET request would return the same events as before. This is to make sure that even when your application is malfunctioning temporarily, it will eventually receive all events. When all goes well, the cursor should be moved to the given nextCursor, using an HTTP PUT request to the /services/webhooks/v1/subscriptions/<subscriptionId>/cursor endpoint.

curl --request PUT \
  --url https://app.leanix.net/services/webhooks/v1/subscriptions/888eaaf3-c72f-411c-a78a-d382ba9b2f75/cursor \
  --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJz [...]' \
  --header 'Content-Type: application/json' \
  --data '{"offset": 182}'
Cursor nextCursor = response.getData().getNextCursor();
subscriptionsApi.updateSubscriptionCursor(subscriptionId, nextCursor);

Alternatively, the cursor can be moved forward automatically when events are retrieved. This behavior can be enabled by adding autoCommit=true as a query string parameter to the GET request above. While this is convenient to use, it cannot guarantee that all events are successfully received and processed by your application, both in case of network issues or problems in your application.

Intended Usage

PULL webhooks are intended to be used in a loop:

  • make a request to retrieve some events,
  • process the events,
  • on success, make a request to advance the cursor (or rely on autoCommit),
  • reiterate.

The request to retrieve events takes another parameter, timeout, which specifies the number of seconds to wait for events before returning an empty response. This prevents your loop from generating too much traffic when no events are generated in the workspace. The timeout parameter has a minimum allowed value of 1 and defaults to 10. Events will always be returned as soon as possible, the timeout affects only the case when no events after the cursor are currently present.

Concurrency

Please note that due to the cursor mechanism of a PULL webhook and the loop pattern described above, it does not make sense to use the same webhook with several HTTP clients concurrently. While we detect and refuse processing of concurrent requests to the same subscription id, it is ultimately up to the users of a webhook to make sure only one client uses the same webhook. Create one webhook per client.

Webhooks


Here we describe how you can listen to events in LeanIX using Webhooks.

Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.