From a98fd4193f0ddd33ad4471af9e203d47c0cd794a Mon Sep 17 00:00:00 2001 From: "rpascal@sdl.com" Date: Wed, 17 Jun 2026 09:20:41 +0300 Subject: [PATCH 1/2] LTLC-131643: Update Extensibility API and Webhooks documentation for clarity and examples. Fixed incorrect optinoal properties. --- .../api/Extensibility-API.v1.json | 8 +- .../docs/development/App-Descriptor.md | 23 +++-- .../docs/development/Webhooks.md | 88 +++++++++++++++++-- 3 files changed, 102 insertions(+), 17 deletions(-) diff --git a/articles/Extensibility/api/Extensibility-API.v1.json b/articles/Extensibility/api/Extensibility-API.v1.json index 0ef8ded..cb9a4e9 100644 --- a/articles/Extensibility/api/Extensibility-API.v1.json +++ b/articles/Extensibility/api/Extensibility-API.v1.json @@ -1291,7 +1291,8 @@ "description", "version", "releaseNotes", - "descriptorVersion" + "descriptorVersion", + "vendor" ], "properties": { "name": { @@ -1357,7 +1358,7 @@ "baseUrl": { "type": "string", "description": "The prefix for all calls to the app service.", - "example": "somehostname.net/service/v1" + "examples": ["somehostname.net/service/v1", "https://somehostname.net" ] }, "regionalBaseUrls": { "$ref": "#/components/schemas/RegionalBaseUrls" @@ -2913,7 +2914,8 @@ ], "properties": { "url": { - "type": "string" + "type": "string", + "examples": [ "/webhook-receiving-endpoint" ] }, "eventTypes": { "type": "array", diff --git a/articles/Extensibility/docs/development/App-Descriptor.md b/articles/Extensibility/docs/development/App-Descriptor.md index d18ea7b..1e1a4ac 100644 --- a/articles/Extensibility/docs/development/App-Descriptor.md +++ b/articles/Extensibility/docs/development/App-Descriptor.md @@ -10,9 +10,12 @@ The descriptor model defines attributes that provide basic information like `nam ## Version -The `version` field is used by Trados to detect newer versions of the app's descriptor. You should increase the version every time you make a change in the descriptor, otherwise, your changes won't reach the registered instance from Trados. +The `version` field is used by Trados to detect newer versions of the app's descriptor. -The version is periodically checked by Trados by performing GET descriptor requests. +> [!WARNING] +> **You must increment the version every time you make changes to the descriptor.** If the version is not updated, Trados will not detect your changes—they will be silently ignored until the version number changes. This is a common gotcha that can cause confusion when changes don't take effect. + +Trados periodically checks the descriptor by performing GET descriptor requests. When it detects a new version number, it will fetch the updated descriptor. If the version hasn't changed, Trados will assume the descriptor is unchanged and will not pick up any modifications you've made. ## Base URL @@ -32,26 +35,30 @@ For example, if we have in the descriptor: Trados will make scheduled GET requests to `https://foo.com/health` to check the health. ### Changing Base URL -For various reasons you might want to change the host of your App and that can be done from Trados management UI. + +For various reasons you might want to change the host of your App and that can be done from Trados management UI. When updating the host, you also have to update `baseUrl` to match the new host. You must still support old host as all previous installs will be calling on the `baseUrl` at the installed version. In order to be able to decomission the old host, you must make sure all consumers updated their installs to latest version. > [!WARNING] -> Because request authentication is based on Audience matching 'baseUrl', you must ensure that your authentication code can accept both old and new `baseUrl`. See [Request Authentication](Request-Authentication.md). +> Because request authentication is based on Audience matching 'baseUrl', you must ensure that your authentication code can accept both old and new `baseUrl`. See [Request Authentication](Request-Authentication.md). ## Standard Endpoints -An app must implement a set of standard endpoints that are defined in the descriptor schema under `standardEndpoints`. Not all endpoints are required, as you can see in the descriptor schema. +The `standardEndpoints` section is optional in the descriptor contract. However, it becomes required when the app defines `extensions`, since Trados needs to know how to interact with the app through those endpoints. If your app only registers `webhooks` and does not provide any extensions, you may omit this section entirely. + +When present, not all endpoints within `standardEndpoints` are required — refer to the descriptor schema for which ones are mandatory. -All endpoint paths need to start with the leading character `/` and are relative to `baseUrl`. +All endpoint paths need to start with the leading character `/` and are relative to `baseUrl`. -Standard endpoints are defined under the `Standard` tag. The actual path should be replaced with the one you defined in the descriptor. The expected Request and Responses are defined and should be used as reference. +In the contract, standard endpoints are defined under the `Standard` tag. The actual path should be replaced with the one you defined in the descriptor. The expected Request and Responses are defined and should be used as reference. ### Lifecycle Endpoint Additionally, in the `standardEnpoints` section we can find the lifecycle endpoint. This endpoint needs to handle different events sent by Trados (similar to webhooks). For instance, when the app is being installed on a certain account, Trados will send an `INSTALLED` event along with some data for that account. The app should react and save the received data. + - `appLifecycle` - is used for all types of events: `REGISTERED`, `UNREGISTERED`, `INSTALLED`, and `UNINSTALLED`. See the contract [here](../../api/Extensibility-API.v1-fv.html#/operations/Lifecycle). > [!NOTE] @@ -62,11 +69,13 @@ Additionally, in the `standardEnpoints` section we can find the lifecycle endpoi The list of provided extensions is described under `extensions`. An app can provide none, one, or more extensions. Any extension will have the generic set of properties: + - `extensionPointId` that specifies what extension point it extends. Can be only a value specified in the descriptor contract (ex: `lc.mtprovider`). - `extensionPointVersion` defines the version of the extension point it extends. The allowed value is defined in the descriptor contract (ex: `1.0`). - `name` is defined by the developer, to provide a friendly name for the extension. This is useful when the app provides multiple extensions. - `description` will have a summary describing the extension. - `configuration` defines the extension and the structure depends on the type of the extension that is implemented. + ```json { ... diff --git a/articles/Extensibility/docs/development/Webhooks.md b/articles/Extensibility/docs/development/Webhooks.md index 3db5ae7..8b6b642 100644 --- a/articles/Extensibility/docs/development/Webhooks.md +++ b/articles/Extensibility/docs/development/Webhooks.md @@ -1,6 +1,6 @@ # Webhooks -Apps can specify a list of webhooks that will be registered automatically when the app is installed on an account. That allows the app to specify a list of webhooks and consume them, allowing for asynchronous scenarios where the app can wait for events instead of polling constantly to check for a particular event or state improving performance both for the app and for Trados. +Apps can specify a list of webhooks that will be registered automatically when the app is installed on an account. That allows the app to specify a list of webhooks and consume them, allowing for asynchronous scenarios where the app can wait for events instead of polling constantly to check for a particular event or state improving performance both for the app and for Trados. ## Constraints @@ -11,13 +11,14 @@ Not all accounts have webhooks enabled. Installing of the app requiring webhooks ## Setup -The required list of webhooks must be specified in the descriptor in the `webhooks` property. +The required list of webhooks must be specified in the descriptor in the `webhooks` property. `webhooks` is an array of URLs and corresponding event types. You can specify a single URL for all webhook event types, or one URL for each event type, or any combination. This is done for maximum flexibility so you can decide if you want to ingest all webhooks through a single endpoint or have multiple endpoints maybe by event type or category, etc. -`url` can be an absolute URL or a path relative to `basePath`. +`url` can be an absolute URL or a path relative to `basePath`. Example of a `webhooks` property in the app descriptor: + ```json { ... @@ -37,20 +38,93 @@ Example of a `webhooks` property in the app descriptor: ... } ``` -That example will subscribe to `PROJECT.TASK.ACCEPTED` and `PROJECT.TASK.CREATED` events and will receive these events on the `/webhooks-endpoint` URL. + +This example will subscribe to `PROJECT.TASK.ACCEPTED` and `PROJECT.TASK.CREATED` events and will receive these events on the `/webhooks-endpoint` URL. + +Alternatively, you can split the events across multiple endpoints: + +```json +{ + ... + "webhooks": [ + { + "url": "/project-webhooks-endpoint", + "eventTypes": [ + { + "eventType": "PROJECT.CREATED" + }, + { + "eventType": "PROJECT.STARTED" + } + ] + }, + { + "url": "/task-webhooks-endpoint", + "eventTypes": [ + { + "eventType": "PROJECT.TASK.CREATED" + } + ] + } + ] + ... +} +``` + +In this example, `PROJECT.CREATED` and `PROJECT.STARTED` events are routed to `/project-webhooks-endpoint`, while `PROJECT.TASK.CREATED` events are sent to a separate `/task-webhooks-endpoint`. + +## Webhook-only app + +If you only need to subscribe to webhooks and do not require other extensibility features, you can register a webhook-only app. This creates a minimal app that does not require any additional endpoints. + +The following example shows a minimal descriptor that only requires a descriptor endpoint and a webhook receiver endpoint: + +```json +{ + "name": "my-extension", + "version": "1.0.0", + "description": "A sample extension", + "releaseNotes": "Initial release", + "descriptorVersion": "1.4", + "scopes": [ "TENANT_READ"], + "webhooks": [ + { + "url": "/webhook-endpoint", + "eventTypes": [ + { + "eventType": "PROJECT.CREATED" + }, + { + "eventType": "PROJECT.STARTED" + }, + { + "eventType": "PROJECT.TASK.CREATED" + } + ] + } + ], + "baseUrl": "https://your.site", + "vendor": { + "name": "Your Company", + "url": "https://your.site", + "email": "support@your.site" + } +} +``` + +When installed, this app will register the `/webhook-endpoint` URL to receive `PROJECT.CREATED`, `PROJECT.STARTED`, and `PROJECT.TASK.CREATED` events. No other endpoints are required. ## Webhook events and payloads -Webhooks for apps are sent in a batched format. +Webhooks for apps are sent in a batched format. -The webhook payload description can be found in our [Trados Cloud Platform API documentation](https://eu.cloud.trados.com/lc/api-docs/batched-webhooks). +The webhook payload description can be found in our [Trados Cloud Platform API documentation](https://eu.cloud.trados.com/lc/api-docs/batched-webhooks). Webhooks are grouped in batches by callback URL, so it is likely that events from different tenants will be included in the same batch. It is the responsability of the app developer to handle the events from the batch accordingly to their `accountId` from the event body. > [!NOTE] > **Note:** For *Webhook Authenticity*, ignore the described behavior in the above link and only consider the following chapter about *Signature Validation*. - ## Signature Validation Unlike webhooks created in the UI through the Applications, webhooks that are declared in the descriptor are received using app signature. The endpoint that receives the webhook should treat these as signed with JWS, just as any other endpoint in the app. See [Request Authentication](Request-Authentication.md) page for more details. From 83ef77e1ed665c6e7e2c2f5a0c0197ee3e5a7bcc Mon Sep 17 00:00:00 2001 From: "rpascal@sdl.com" Date: Wed, 17 Jun 2026 09:26:59 +0300 Subject: [PATCH 2/2] Fix example URLs in baseUrl description for clarity --- articles/Extensibility/api/Extensibility-API.v1.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/Extensibility/api/Extensibility-API.v1.json b/articles/Extensibility/api/Extensibility-API.v1.json index cb9a4e9..aa62e3f 100644 --- a/articles/Extensibility/api/Extensibility-API.v1.json +++ b/articles/Extensibility/api/Extensibility-API.v1.json @@ -1358,7 +1358,7 @@ "baseUrl": { "type": "string", "description": "The prefix for all calls to the app service.", - "examples": ["somehostname.net/service/v1", "https://somehostname.net" ] + "examples": ["https://somehostname.net/service/v1", "https://somehostname.net" ] }, "regionalBaseUrls": { "$ref": "#/components/schemas/RegionalBaseUrls"