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


1

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.

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

workflowDefinitionIdUUID (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

expiresAtISO 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

localeIETF 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.

2

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();
}
3

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

Tip: Place a Webhook Task at the end of the workflow to send a custom completion signal, including any relevant context or identifiers.

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

4

Congratulations!

You’ve successfully set up a basic TransactionLink integration.

Now, learn how to customize your workflow experience — from styling the hosted page to configuring dynamic logic and automating data collection using workflow tasks and parameters.

Last updated

Was this helpful?