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:
- Log in to LeanIX account
- Navigate to Administration
- Click on "Webhooks" in the sidebar
- 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
Event Types and Payloads
The following is the list of supported event types that you can subscribe to:
- 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 or the retention period ended)
- RELATION_CREATED – New relation between Fact Sheets is created (or recovered from archive)
- RELATION_UPDATED – Relation between Fact Sheets is updated
- RELATION_ARCHIVED – Relation between Facts Sheets is archived
- RELATION_DELETED – Relation between Fact Sheets is deleted (e.g. when a Fact Sheet type is removed completely from the data model or the retention period ended)
- RELATION_SWITCH - Only the target Fact Sheet of the Relation changed
- FACT_SHEET_TAG_ADDED – Tag is added to a Fact Sheet
- FACT_SHEET_TAG_REMOVED – Tag is removed from a Fact Sheet
- FACT_SHEET_FIELD_UPDATED – Field on a Fact Sheet is updated
- DOCUMENT_CREATED – Resource / document is added to a Fact Sheet
- DOCUMENT_UPDATED – Resource / document is updated on a Fact Sheet
- DOCUMENT_DELETED – Resource / document is deleted from a Fact Sheet
- SUBSCRIPTION_CREATED – Subscription is added to a Fact Sheet
- SUBSCRIPTION_UPDATED – Subscription is updated on a Fact Sheet
- SUBSCRIPTION_DELETED – Subscription is deleted from a Fact Sheet
- INTEGRATION_RUN_FINISHED – An integration run finished
- INTEGRATION_RUN_ABORTED – An integration run aborted
- CONFIGURATION_CREATED – A configuration of a integration was created
- CONFIGURATION_UPDATED – A configuration of a integration was updated
- CONFIGURATION_DELETED – A configuration of a integration was deleted
- CONFIGURATION_ACTIVATED – A configuration of a integration was activated
- CONFIGURATION_DEACTIVATED – A configuration of a integration was deactivated
- POLL_RESULT_CREATED – A survey has been responded to
- POLL_RESULT_UPDATED – The response of a survey has been changed
In general, the payload for the above event types contains information on the event, the user which triggered the event, meta information e.g. such as the type of the Fact Sheet and the actual Fact Sheet data like fields and relations.
Though the actual data is specific to the event type, the examples for the most popular events give an idea how the events look like.
You can also test this using the Webhook Tester tool already mentioned above.
{
"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"
}
{
"type":"CONFIGURATION_CREATED",
"userId":"64875bf3-76e2-4378-a2fb-9f8ea74d2b20",
"connectorId":"foobar",
"workspaceId":"2fe9d275-50cb-418d-b6ec-7e4723a25422",
"connectorType":"baz",
"processingMode":"partial",
"integrationType":"Integration API",
"connectorVersion":"1.0.0",
"processingDirection":"inbound"
}
{
"type":"CONFIGURATION_UPDATED",
"userId":"64875bf3-76e2-4378-a2fb-9f8ea74d2b20",
"connectorId":"foobar",
"workspaceId":"2fe9d275-50cb-418d-b6ec-7e4723a25422",
"connectorType":"baz",
"processingMode":"partial",
"integrationType":"Integration API",
"connectorVersion":"1.0.0",
"processingDirection":"inbound",
...
}
{
"type":"CONFIGURATION_DELETED",
"userId":null,
"connectorId":"foobar",
"workspaceId":"2fe9d275-50cb-418d-b6ec-7e4723a25422",
"connectorType":"baz",
"processingMode":"partial",
"integrationType":"Integration API",
"connectorVersion":"1.0.0",
"processingDirection":"inbound"
}
{
"type":"CONFIGURATION_ACTIVATED",
"userId":"64875bf3-76e2-4378-a2fb-9f8ea74d2b20",
"connectorId":"foobar",
"workspaceId":"2fe9d275-50cb-418d-b6ec-7e4723a25422",
"connectorType":"baz",
"processingMode":"partial",
"integrationType":"Integration API",
"connectorVersion":"1.0.0",
"processingDirection":"inbound"
}
Error handling
In case the Target URL is not reachable, misconfigured or responses with a http status different than 200, 202 and 204, webhooks retries to deliver the event after 50 seconds again and again until the delivery was successful. Webhooks always tries to guarantee each event reaches the configured Target Url.
In case the user has activated the Ignore Errors check box this behavior changes and all those events will simply omitted.
If your webhook delivery log shows the error java.lang.IllegalStateException: Already Executed, it is very likely that your processing logic took a long time and it did not complete before the webhook retry interval. Please make sure that your processing logic returns an http status before the webhook delivery is retried.
Automatic deactivation
In order to save CPU usage, webhooks deactivates automatically all subscriptions which points to a Target URL that is not reachable or responses with a non-successful http code for more than 10 days. This means a subscription with a "broken" target endpoint will be deactivated after 10 days and the admin needs to activate the subscription manually after its configured Target URL works again.
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 = 'POST';
delivery.targetUrl = delivery.targetUrl += '?test=1';
delivery.payload = {
text : 'Hello World'
};
A common manipulation is to restrict webhook generation to just a single factsheet type. For example, if webhooks should only be produced for Applications, this can be achieved with the following code:
delivery.active = false;
if (delivery.payload.factSheet.type == 'Application' ) {
delivery.active = true;
}
Be aware, that for performance reasons, we cache the evaluation context of the callback of your subscription for a certain amount of time. Make sure to initialise new variables correctly, because otherwise they might still contain the assign value from a previous callback execution of your subscription.
JavaScript Supported
Webhooks use the Nashorn Engine version 15.3
It is compatible with ECMAScript 5.1 also many new features from ECMAScript 6 more details in the documentation.
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.
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 [...]'
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}'
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.