Skip to main content

Workflow Integration

Workflow integration lets a workflow call tenant-side domain methods, execute an OpenLink service-call flow, or wait for an external system to return an asynchronous result. The Approval Process and Automation Task design canvases use the same contract; only the payload source and the runtime differences below change.

WorkflowDesign canvasPayload sourceCallback difference
Approval processApproval process design canvassource.type = 'approval' with the instance, node, trigger, history, and callback resultThe wait callback node has no timeout; the instance waits until the callback arrives
Automation taskAutomation task design canvassource.type = 'automation' with the definition, execution, node, trigger snapshot, and step outputThe wait callback node requires a timeout (at least 60 when the unit is seconds) and the execution fails when it expires

It is not a public Runtime API. The actual integration entry points are the Service Call and Wait Callback nodes on either design canvas:

ScenarioNode sequenceIntegration method
Call an internal Soln capabilityService Call (Studio script)Call Studio Runtime methods such as context.form.* and context.http.*
Call an external system synchronouslyService Call (integration flow)Execute a published inbound or outbound OpenLink flow whose trigger is Service Call
Wait for an asynchronous external resultService Call → Wait CallbackStart the external task, then complete the callback through an inbound OpenLink Webhook flow

Tenant data and integration execution records remain tenant-side. External systems use the public entry point configured by OpenLink and never call internal Runtime routes.

Prerequisites​

Open the target workflow and enter its design canvas: a process from Approval Process Management, or a task from Automation Tasks. Before configuring an integration, confirm that:

  1. The workflow is bound to the correct local form
  2. A Studio script asset and entry file are published before they are selected
  3. An OpenLink flow, its form binding, and an active flow version are enabled and published
  4. A service-call flow uses the same local form as the workflow
  5. An asynchronous callback flow is a published inbound Webhook flow using that same local form

Service-Call Payload​

Studio scripts and OpenLink service-call flows receive the same soln.runtime-context/v1 context, and the node inspector shows a payload preview for the current workflow.

An approval process sends the approval source:

type ApprovalServiceCallPayload = {
schema: 'soln.runtime-context/v1';
source: {
type: 'approval';
definitionId: string;
instanceId: string;
nodeKey: string;
nodeName: string;
trigger: {
action: 'create' | 'update' | 'delete';
changedFields: string[];
sourceDataStatus: string | null;
sourceApprovalStatus: string | null;
inheritedOutcome: 'approved' | 'rejected' | 'returned' | null;
};
history: Array<{
nodeKey: string;
nodeName: string;
nodeType: string;
status: string;
outcome: string | null;
detail: Record<string, unknown>;
startedAt: string;
completedAt: string | null;
}>;
callback: {
flowDefinitionId: string | null;
correlationKey: string | null;
outcome: 'approved' | 'rejected' | 'returned' | null;
payload: Record<string, unknown>;
receivedAt: string | null;
} | null;
};
form: {
id: string;
apiName: string;
name: string | null;
} | null;
record: {
id: string;
data: Record<string, unknown>;
system: Record<string, unknown>;
} | null;
runtime: {
locale: string;
timezone: string;
executionId: string;
traceId: string;
};
};

An automation task sends the automation source:

type AutomationServiceCallPayload = {
schema: 'soln.runtime-context/v1';
source: {
type: 'automation';
definitionId: string;
executionId: string;
nodeKey: string;
trigger: Record<string, unknown>;
steps: Record<string, { output: unknown }>;
};
form: { id: string; apiName: string; name: string | null } | null;
record: { id: string; data: Record<string, unknown> } | null;
result: unknown;
runtime: {
executionId: string;
};
};

Fields both workflows share:

FieldPurpose
record.idID of the record the workflow is working on; use it with record-targeted domain methods
record.dataRecord data snapshot when the service-call node runs, keyed by field API name
form.apiNameAPI name of the related form; use it with form-targeted methods
source.nodeKeyCurrent service-call node key
runtime.traceIdTrace ID for this node execution; suitable as an external idempotency key

Approval-only fields:

FieldPurpose
source.instanceIdCurrent approval instance ID
source.triggerRecord action and changed-field information that started the approval
source.historyExecution history before the current node
source.callbackLatest callback result when an upstream wait callback has completed

Automation-only fields:

FieldPurpose
source.definitionIdAutomation task definition ID
source.executionIdCurrent automation execution ID
source.triggerSnapshot of the trigger configuration
source.stepsStep output of the nodes that already ran
resultUpstream node result

Both payloads include the complete record.data snapshot. Do not find the current record through URL parameters.

Call Internal Domain Methods​

Configure The Service-Call Node​

  1. Add a Service Call node to the design canvas
  2. Set Call Method to Studio Script
  3. Select a published script asset and entry file
  4. Inspect the payload preview and verify every field API name used by the script
  5. Connect exactly one incoming and one outgoing route
  6. Save and publish the workflow

“Internal methods” are tenant domain methods exposed by Studio Runtime, such as context.form.get, context.form.update, and context.form.create. A script must not call legacy MCP tools or connect directly to the tenant database.

Example: Update The Current Record After Approval​

type StudioRuntimeContext = {
form: {
update(input: {
recordId: string;
data: Record<string, unknown>;
}): Promise<unknown>;
};
};

export default async function main(
payload: ApprovalServiceCallPayload,
context: StudioRuntimeContext,
) {
if (!payload.record?.id) {
return {
ok: false,
message: 'Approval record is unavailable.',
};
}

if (payload.source.trigger.inheritedOutcome !== 'approved') {
return { ok: true, skipped: true };
}

await context.form.update({
recordId: payload.record.id,
data: {
integrationStatus: 'approved',
approvalInstanceId: payload.source.instanceId,
},
});

return {
ok: true,
recordId: payload.record.id,
};
}

Any of these return values marks the service call as failed and blocks the approval instance at this node:

return { ok: false, message: 'Unable to update the record.' };
return { success: false, errorMessage: 'External validation failed.' };

A status of failed, failure, error, or rejected is also treated as failure. A thrown exception, script execution failure, or unavailable published entry file blocks the node as well. The execution log records the reason, and the service call can be retried after the issue is resolved.

Example: Call An External HTTP Service From A Script​

A small synchronous integration can call context.http.requestJson directly:

type StudioRuntimeContext = {
http: {
requestJson<T>(input: {
method: string;
url: string;
headers?: Record<string, string>;
json?: unknown;
timeoutMs?: number;
}): Promise<{ status: number; data: T }>;
};
};

export default async function main(
payload: ApprovalServiceCallPayload,
context: StudioRuntimeContext,
) {
if (!payload.record) {
return { ok: false, message: 'Approval record is unavailable.' };
}

const response = await context.http.requestJson<{
accepted: boolean;
referenceId?: string;
}>({
method: 'POST',
url: 'https://api.example.com/risk-checks',
headers: {
'Idempotency-Key': payload.runtime.traceId,
},
json: {
recordId: payload.record.id,
amount: payload.record.data.amount,
approvalInstanceId: payload.source.instanceId,
},
timeoutMs: 10_000,
});

if (!response.data.accepted) {
return {
ok: false,
message: 'Risk check was rejected.',
};
}

return {
ok: true,
referenceId: response.data.referenceId ?? null,
};
}

Do not hard-code production credentials in scripts. Configure external authentication through the OpenLink integration system. Prefer an OpenLink service-call flow when the integration needs reusable mapping, authentication, and execution auditing.

Prepare A Service-Call Flow​

  1. Open Integration Flows under the target OpenLink
  2. Create an inbound or outbound flow
  3. Set its trigger to Service Call
  4. Select the same local form used by the approval process
  5. Configure source, transform, target, and optional message nodes
  6. Save, publish, and enable the flow

The approval service-call selector only includes flows that:

  • are published and have an active version;
  • use the Service Call trigger;
  • use the same local form as the approval process; and
  • are inbound or outbound OpenLink flows

Configure The Node​

  1. Add a Service Call node on the design canvas
  2. Set Call Method to Integration Flow
  3. Select the published flow from the form-grouped options
  4. Connect the incoming and outgoing routes, then save and publish the workflow

At runtime, the node sends the full payload to OpenLink and waits for that service-call execution to finish:

  • Success completes the node and continues the workflow
  • Failure blocks the node and the workflow run
  • Retry runs the same execution and node again; it does not create another path

Example: Send A Purchase Order Synchronously​

Approval canvas:

Start → Approval → Service Call (flow: Send Purchase Order) → End

Outbound OpenLink flow:

Start → Source → Transform → Target (Studio script/external call) → End

The target reads the purchase order from the runtime record and calls the external system. That system must return its final success or failure during the current execution. If the final result will arrive later, use a Service Call followed by Wait Callback instead of holding a synchronous call open.

Wait For An Approval Callback​

When To Use It​

Use the following sequence when an external system accepts a job first and produces a final result after manual review, asynchronous processing, or third-party work:

Service Call → Wait Callback → Next node

Wait Callback must be directly connected after Service Call. Do not place a condition, message, or approval node between them.

Prepare The Callback Flow​

  1. Create an Inbound OpenLink flow
  2. Set its trigger to Webhook
  3. Use the same local form as the approval process
  4. Set its target action to Complete Workflow Callback
  5. Save, publish, and enable the flow
  6. Copy the Webhook URL shown by the flow

The callback flow must be a published inbound Webhook flow with an active version and enabled binding. Do not use a Service Call flow as the callback endpoint.

Configure The Wait-Callback Node​

  1. Connect Wait Callback directly after the node that starts the asynchronous job
  2. Select the inbound Webhook flow prepared above
  3. Configure a correlation-key template. A common value is {{data.record.id}}
  4. Pass the rendered key to the external system and require it to return the same key
  5. Connect exactly one outgoing route
  6. Save and publish the workflow

An approval wait callback has no timeout: the instance waits until the callback arrives. An automation wait callback always has a timeout (at least 60 when the unit is seconds), and the wait node plus that execution fail when it expires.

The key matches a pending wait inside the current tenant and callback flow. It must be stable and identify exactly one wait. If the same record can have concurrent waiting approvals, use a key that includes the approval instance or external task identity, using only template values supported by the inspector.

Example: Start An Asynchronous Job​

A Service Call node can create the external task through a Studio script:

export default async function main(
payload: ApprovalServiceCallPayload,
context: StudioRuntimeContext,
) {
if (!payload.record) {
return { ok: false, message: 'Approval record is unavailable.' };
}

const correlationKey = payload.record.id;
const response = await context.http.requestJson<{
taskId: string;
}>({
method: 'POST',
url: 'https://api.example.com/manual-reviews',
headers: {
'Idempotency-Key': payload.runtime.traceId,
},
json: {
correlationKey,
recordId: payload.record.id,
callbackUrl: 'https://soln.example.com/api/open-link/webhooks/review-callback',
},
timeoutMs: 10_000,
});

return {
ok: true,
correlationKey,
externalTaskId: response.data.taskId,
};
}

The correlationKey sent by the script must equal the key rendered by the Wait Callback template. Use the actual Webhook URL shown in the node inspector; do not copy the example URL.

External Callback Request​

After processing succeeds, the external system sends this JSON to the callback flow Webhook URL:

{
"callbackId": "review-result-8f1d31",
"correlationKey": "record-7c831b",
"status": "succeeded",
"result": {
"decision": "approved",
"externalTaskId": "review-2048",
"comment": "Risk review passed"
},
"error": null,
"completedAt": "2026-09-10T08:30:00.000Z"
}
FieldRequiredDescription
callbackIdYesExternal request identifier for this callback. Generate a unique value
correlationKeyYesMust exactly match the rendered Wait Callback key
statusYesMust be succeeded for an approval callback
resultYesCallback result object
result.decisionNoapproved, rejected, or returned
errorYesMust be null on success
completedAtNoValid ISO date-time or null

Approval callbacks do not accept failed or canceled. If the external job fails, keep it available for retry or encode a routable business outcome in the result of a successful callback according to the integration contract.

A matching callback completes the wait node and resumes the approval. No match produces WORKFLOW_CALLBACK_WAIT_NOT_FOUND; more than one pending match for the same flow and key produces WORKFLOW_CALLBACK_WAIT_NOT_UNIQUE.

Route Using The Callback Outcome​

A later service-call or message node receives the most recent result in source.callback:

export default async function main(payload: ApprovalServiceCallPayload) {
const callback = payload.source.callback;

if (callback?.outcome === 'rejected') {
return {
ok: false,
message: String(callback.payload.comment || 'External review rejected.'),
};
}

return {
ok: true,
externalTaskId: callback?.payload.externalTaskId ?? null,
};
}

To branch on the canvas, add a Branch after Wait Callback and select the Callback outcome field:

Service Call → Wait Callback → Branch (callback result)
├─ approved → next processing
└─ rejected / returned → alternate processing

Callback outcome is available only when the branch's upstream path has passed through a Wait Callback node.

Automation tasks branch with a Branch node instead: its condition reads record fields, and the callback output is available to later nodes through source.steps.

Complete Integration Examples​

Synchronous Integration​

Start
→ Approval
→ Service Call (Studio script or service-call OpenLink flow)
→ Message
→ End

Use this when the external service returns its final result in one execution. Failure blocks the approval instance until the issue is fixed and the node is retried.

Asynchronous Approval Integration​

Start
→ Approval
→ Service Call (create external job and send correlationKey)
→ Wait Callback (inbound Webhook + workflow_callback)
→ Branch (context.callback.outcome)
├─ approved → update record → End
├─ rejected → Message → End
└─ returned → Human processing → End

In this sequence:

  1. Service Call only creates the external job
  2. Wait Callback persists the pending wait and pauses the approval
  3. The external system calls the OpenLink Webhook
  4. OpenLink validates the callback contract and matches the wait
  5. The approval resumes and exposes the callback result to later nodes

Automation Task Examples​

Synchronous Automation Call​

Start (data change: order created)
→ Service call (studio script: call the external system)
→ Data processing (update the order)
→ End

Use this when the script or flow returns its final result in the same execution. A failure blocks the node and the automation execution until the issue is fixed and the node is retried.

Asynchronous Automation Callback​

Start (data change: order created)
→ Service call (studio script: create the external review and send correlationKey)
→ Wait callback (inbound Webhook with a timeout)
→ Data processing (write the callback outcome to the record)
→ End
  1. The service call only creates the external job
  2. The wait callback stores the pending wait and pauses the execution; its timeout must be valid, at least 60 when the unit is seconds
  3. The external system calls the OpenLink Webhook with the callback contract
  4. The execution resumes, and later nodes read the earlier step output from source.steps or write the result back with a data processing node

Failure Handling And Idempotency​

  • Use runtime.traceId or a stable business key as an external idempotency key
  • Generate a unique external callbackId for each callback request
  • correlationKey must exactly match the pending wait and must not match multiple waits
  • A Studio return with ok: false, success: false, or a failure status blocks the workflow
  • Failed OpenLink service-call executions also block the workflow and preserve execution logs
  • External systems must not call internal Runtime routes; the public callback entry point is the OpenLink Webhook
  • Do not use legacy MCP tools or direct tenant-database access from service-call scripts
  • Update tenant business records through Studio Runtime domain methods using the real record.id
  • Use synchronous calls only for bounded work; use Wait Callback for long-running work
  • Before publishing, test field API names, the correlation key, external authentication, callback payload, and failure routes with real tenant records