Run the workflow inside your application
Learn how to render the workflow directly inside your application using an iframe for a seamless user experience.
You’ll build a simple integration where the user completes a workflow directly inside your app.
The example includes both client- and server-side code, and shows how to create a workflow execution and render the interface using the embedded mode. The workflow UI is securely loaded in an <iframe>
, allowing you to fully integrate the process into your product without redirecting the user to an external page.
This setup is ideal for cases where you want full control over the user experience and visual consistency with your product.
Use this approach when:
The workflow is part of an onboarding, verification, or registration flow
You want to avoid any external redirection
You need to match the look & feel of your existing UI
You're building internal tooling or back-office processes with light user input
Set up the server
Create a Workflow Execution
Add an endpoint on your server that creates a Workflow Execution.
The response includes a token
, which is used on the client side to initialize and mount the widget.
Return the token
in your response so it can be passed to the widget in the frontend.
We recommend creating a new execution each time a user starts a workflow, to ensure that the flow is fresh and state is isolated for every attempt.
const express = require('express');
const axios = require('axios');
const { v4: uuidv4 } = require('uuid');
const app = express();
app.use(express.json());
app.post('/workflow-execution', async (req, res) => {
const executionPayload = {
recordId: uuidv4(),
workflowDefinitionId: uuidv4(),
expiresAt: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
locale: 'en',
};
const execution = await axios.post(
'https://api.transactionlink.io/workflows',
executionPayload,
{
headers: {
Authorization: `Bearer ${ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
}
);
res.send({
token: execution.data.token,
workflowId: execution.data.id,
});
});
app.get('/workflow-status', async (req, res) => {
const workflowId = req.query.workflowId;
const execution = await axios.get(
`https://api.transactionlink.io/workflows/${workflowId}`,
{
headers: {
Authorization: `Bearer ${ACCESS_TOKEN}`,
},
}
);
res.send({
status: execution.data.status,
workflowId: execution.data.id,
});
});
app.listen(8080, () => console.log('Running on port 8080'));
Set the workflow to execute
workflowDefinitionId
– UUID (required)
The ID of the workflow you want to execute. This refers to a predefined flow configured in your TransactionLink Dashboard.
📌 You can find the workflowDefinitionId
in the Dashboard by viewing the workflow’s details.
Set an expiration time
expiresAt
– ISO 8601 timestamp
Defines when the workflow execution becomes invalid.
Must be between 30 minutes and 24 hours from creation.
If omitted, defaults to 1 hour.
🔐 Use this to enforce time-limited actions (e.g. document upload, identity verification) and avoid stale sessions.
Set the interface language
locale
– IETF language tag (e.g. en
, pl
, de
)
Controls the language of the user-facing interface.
If set to
auto
or left blank, we detect the user’s browser language.Use this to enforce a specific language for localized flows.
Build your page
Create HTML element for widget
Add an HTML on your page that will be replaced with an embedded Transactionlink widget.
Place either <transactionlink-widget />
or <div id="transactionlink-widget" />
element
<transactionlink-widget />
Obtain an ID and a token
Use an API created on your server in previous step to receive an execution ID and token for your workflow. You can trigger an HTTP call for the token when your application needs to launch the widget - on a button click, page load, or in any other scenario that is specific to your use case.
async function createWorkflowExecution() {
try {
const response = await fetch('/workflow-execution', { method: 'POST' });
const data = await response.json()
return data;
} catch (err) {
console.error(err);
throw err;
}
}
Initialize widget
Append the widget script to the page and define a callback function as window.transactionlink_ready
. It will be executed when the script is ready, so that you can pass the previously obtained token and launch the widget.
<script>
async function launchWidget() {
const { workflowId, token } = await createWorkflowExecution();
window.transactionlink_ready = () => {
transactionlink.setOptions({ token });
transactionlink.open();
};
const script = document.createElement('script');
script.src = 'https://widget.transactionlink.io/transactionlink-widget.umd.js';
document.head.appendChild(script);
}
document.getElementById('launch-widget')
.addEventListener('click', launchWidget);
</script>
<!-- Button to trigger the widget -->
<button id="launch-widget">Launch Embedded Widget</button>
<transactionlink-widget />
Await for workflow status (optional)
If you want your app to be notified when the workflow has been completed, you can poll for workflow status. It may be useful to close the Transactionlink widget and update your UI.
Update your launchWidget
function as follows:
async function launchWidget() {
const { workflowId, token } = await createWorkflowExecution();
window.transactionlink_ready = () => openWidget(workflowId, token);
const script = document.createElement('script');
script.src = 'https://widget.transactionlink.io/transactionlink-widget.umd.js';
document.head.appendChild(script);
}
async function isWorkflowFinished(workflowId) {
try {
const response = await fetch(`/workflow-status?workflowId=${workflowId}`);
const data = response.json();
return data.status === 'COMPLETED';
} catch (err) {
console.error(err);
return false;
}
}
async function openWidget(workflowId, token) {
transactionlink.setOptions({ token });
transactionlink.open();
let finished = false;
// Wait until workflow is finished - ask the server for status periodically
while (!finished) {
// Wait 2s between calls
await new Promise((resolve) => setTimeout(resolve, 2000));
finished = await isWorkflowFinished(workflowId);
}
// Close widget when workflow is finished
transactionlink.close();
}
Get notified about workflow status
There are two ways to notify your backend when something happens in the workflow
1. Webhook Task
You can add a Webhook Task directly in your workflow. This lets you send a custom HTTP POST request to your server at any point during the flow — with a payload you fully control.
Use it when:
You want to notify your backend during a specific step in the process
You need to trigger external logic or update another system
You want to send dynamic data collected earlier in the flow
You want to notify your server when the process is completed — with your own payload
Both options can be used together or independently — depending on your use case.
2. Polling the API (Long polling)
As an alternative, you can implement long polling from your backend. This involves periodically calling the TransactionLink API to check the status of a specific workflow execution.
While not real-time, long polling can be useful when:
You cannot expose a public webhook endpoint
Your infrastructure requires strict control over incoming connections
You need fallback logic when webhooks are unavailable or blocked
Keep in mind: Webhooks (automatic or via task) are the recommended approach for real-time and scalable integration.
Last updated
Was this helpful?