Skip to main content
Version: 2.5

External Task

External Tasks in tSM delegate execution to an external worker instead of running logic inside the process engine. This enables clean separation of responsibility, independent scaling, and integration with systems outside tSM.


When to Use an External Task

Use an External Task when:

  • the action should not run inside the process engine
  • you want clear separation of responsibility
  • the logic is implemented in a separate service (e.g. an integration connector or third-party system)
  • retries, failures, and scaling should be handled outside the engine

Do not use External Tasks for:

  • BPMN listeners (they are lifecycle hooks, not jobs)
  • simple in-process logic (use Service Tasks with scripts or delegates instead)
  • one bounded AI operation (use an Agent Task)
  • collaboration with an independently operated agent (use an A2A Agent Task)

How It Works

An External Task is a standard Service Task configured with the type External. Instead of running logic directly:

  1. The tSM Process Engine creates an external task instance with a specific topic
  2. An external worker subscribes to that topic and fetches the task via REST (fetchAndLock)
  3. The worker performs the work
  4. The worker completes (or fails) the task, optionally returning output variables

The external worker is a separately implemented service — it defines a strict contract: which topic it listens to, what input variables it expects, and what output it produces.


Task Templates and External Tasks

Because external workers expect a specific contract (topic, input variables, structure), you cannot set arbitrary properties — the configuration in the BPMN model must match exactly what the worker understands.

For this reason, external task workers are typically delivered together with a Task Template. In most cases, you do not need to configure external tasks manually. Instead, you:

  1. Select a Task Template from the palette in the tSM Designer (e.g. "CRM Enrichment")
  2. Fill in the template form — the template guides you through the required parameters
  3. The template generates the correct BPMN configuration automatically (topic, input mapping, defaults)

This approach:

  • prevents misconfiguration that would cause the worker to fail
  • provides a guided UI so you don't need to know the worker's internal contract
  • applies consistent defaults and validates inputs
  • makes external tasks accessible to process designers without deep technical knowledge
tip

If a Task Template is available for the external worker you want to use, always prefer the template over manual configuration. Manual setup is described below for reference and for cases where no template exists.


Manual Configuration

The rest of this page describes how to configure an External Task manually in the tSM Designer. This is the baseline — everything here also applies when a Task Template generates the configuration for you.

Step 1: Add a Service Task

Drag a Service Task into the process and give it a meaningful name (e.g. Enrich Customer).

An External Task uses the standard Service Task BPMN element — no custom elements are needed.

Step 2: Set execution type to External

In the Service Task properties:

  • Set Implementation / Type to External Task
  • Set Topic to a string identifying the operation

Example:

Topic: crm.customer.enrich

This topic is what the external worker will subscribe to.


Passing Input Data

External workers read process variables. All input for the external task must be provided as process variables.

Input Mapping

Use Input Mapping on the Service Task to:

  • define variables explicitly
  • compute values using expressions
  • keep the process readable

Example

In the Service Task → Input/Output section:

<camunda:inputOutput>
<camunda:inputParameter name="customerRequest">
<camunda:map>
<camunda:entry key="customerId">#{customerId}</camunda:entry>
<camunda:entry key="includeContracts">#{true}</camunda:entry>
</camunda:map>
</camunda:inputParameter>
<camunda:inputParameter name="requestedBy">#{requestOwner}</camunda:inputParameter>
</camunda:inputOutput>

Notes:

  • Expressions use SpEL syntax #{...}
  • Maps and lists are allowed
  • Values are evaluated when the task is reached
  • Variables are available to the external worker

What the External Worker Receives

When the worker fetches the task, it receives:

  • the topic
  • the process instance context
  • all mapped input variables

Example variables payload:

{
"customerRequest": {
"customerId": "CUS-123",
"includeContracts": true
},
"requestedBy": "support-l1"
}

Completing the External Task

After performing the work, the worker must:

  • complete the task
  • optionally set output variables

Example completion payload:

{
"customerEnrichment": {
"segment": "BUSINESS",
"activeContracts": 3
}
}

These variables become available to the rest of the process.


Error Handling and Retries

Recoverable errors

If the worker encounters a recoverable error:

  • report failure
  • let the process engine retry according to the retry policy

Non-recoverable errors

  • set retries to 0
  • optionally set error details

The process engine stops retrying and creates an incident.


Worker API (REST)

tSM exposes a Camunda 7 wire-compatible External Task API. The request and response payloads mirror the upstream Camunda REST contract, so an unmodified Camunda External Task Client works against tSM after changing only its base URL.

Base path: /api/v2/camunda on the service that hosts the process engine (tsm-process-definition on the platform). Behind the gateway the full path includes the service prefix:

https://<host>/tsm-process-definition/api/v2/camunda

Endpoints

Method and pathPurpose
POST /external-task/fetchAndLockAtomically fetch and lock tasks; supports long polling
POST /external-task/{id}/completeComplete a locked task, optionally with output variables
POST /external-task/{id}/failureReport a technical failure with retries and retryTimeout
POST /external-task/{id}/bpmnErrorReport a BPMN error (errorCode) handled by the model
POST /external-task/{id}/lockLock a task for a worker
POST /external-task/{id}/extendLockExtend an existing lock
POST /external-task/{id}/unlockRelease a lock
POST /process-instance/{id}/variablesSet or delete process variables (modifications, deletions)
GET /process-instance/{id}/variables/{name}/dataDownload a deferred File / Bytes variable

fetchAndLock returns the locked tasks, the binary endpoint returns the raw content; everything else returns 204 No Content. Errors use the Camunda-compatible body { "type": ..., "message": ... }.

Authentication and privileges

The worker calls the API as a regular tSM identity. Two credentials are supported:

  • X-API-Key — the recommended path. The gateway exchanges the key for a backend token.
  • Authorization: Bearer <token> — when the worker already holds an access token.
caution

Unlike internal service calls, this API does not accept the inter-service User-Agent bypass — the credential must be directly verifiable. An unauthenticated call gets 401, a call without the required privilege gets 403.

Required privileges:

PrivilegeNeeded for
!Process.ExternalTask.ExecuteThe whole worker API (fetch, complete, failure, lock, variables)
!Process.ExternalTask.Variable.ReadAdditionally for the binary variable download
!Process.ExternalTaskParent privilege covering the complete worker API

Bind the API key to a dedicated service account with a minimal role that holds only the !Process.ExternalTask* privileges — do not reuse an administrator account. Because the standalone setVariables request carries only a workerId, tSM accepts the write only while that worker owns an active lock in the target process instance.

Long polling

fetchAndLock is served asynchronously. Set asyncResponseTimeout (milliseconds) to keep the request open until a task appears; the engine answers immediately once a matching task is created. Omit it for a plain, non-blocking fetch.

Example

Request:

POST /tsm-process-definition/api/v2/camunda/external-task/fetchAndLock
X-API-Key: tsm_ak_…
Content-Type: application/json

{
"workerId": "invoice-worker",
"maxTasks": 5,
"usePriority": false,
"asyncResponseTimeout": 10000,
"topics": [
{
"topicName": "ai.agent.call",
"lockDuration": 60000,
"variables": ["aiAgentId", "aiInput"]
}
]
}

Response:

[
{
"id": "a1b2c3…",
"topicName": "ai.agent.call",
"workerId": "invoice-worker",
"processInstanceId": "…",
"lockExpirationTime": "2026-01-31T10:15:00.000+0000",
"retries": null,
"variables": {
"aiAgentId": { "value": "support-triage", "type": "String", "valueInfo": {} }
}
}
]

Completing the task:

POST /tsm-process-definition/api/v2/camunda/external-task/a1b2c3…/complete

{
"workerId": "invoice-worker",
"variables": {
"aiResponse": { "value": "{\"summary\":\"…\"}", "type": "Json" }
}
}

Writing a worker in Kotlin or Java

The module tsm-camunda-client is a thin factory over the standard Camunda External Task Client — it keeps the whole upstream worker API and only points it at the tSM endpoint and adds the credential header:

val client = TsmCamundaClient.externalTasks(
serviceBaseUrl = "https://tsm.example.com/tsm-process-definition",
apiKey = apiKey,
tenantId = "default", // optional, sent as X-Tenant-Id
)
.workerId("invoice-worker")
.asyncResponseTimeout(20_000)
.build()

client.subscribe("ai.agent.call")
.handler { task, service -> service.complete(task) }
.open()

Use TsmCamundaClient.externalTasksWithAccessToken(...) when the worker authenticates with a Bearer token instead of an API key. serviceBaseUrl may be given with or without the /api/v2/camunda suffix.

A plain Camunda client works too — point it at the full base URL and add the credential header yourself:

ExternalTaskClient.create()
.baseUrl("https://tsm.example.com/tsm-process-definition/api/v2/camunda")

Trying the API out

A ready-made Hoppscotch collection covering all endpoints (with automatic taskId capture) lives in the tsm-process-engine repository under tsm-camunda/src/test/resources/hoppscotch/, together with environment templates and a README. Import it, fill in the base URL and the API key, and run the requests against a deployed process.


Best Practices

  • Use Task Templates whenever available — they ensure correct configuration
  • Use clear, stable topic names
  • Keep input payloads reasonably small
  • Prefer structured objects over large strings
  • Treat External Tasks as integration boundaries
  • Give each worker its own service account with a minimal role (!Process.ExternalTask* only)

Summary

To use an External Task in tSM:

  1. Preferred: Select a Task Template for the external worker from the palette and fill in the form
  2. Manual: Create a Service Task, set it to External Task, define a topic, and configure input mapping
  3. An external worker fetches the task, performs the work, and completes it