> For the complete documentation index, see [llms.txt](https://docs.transactionlink.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.transactionlink.io/docs/workflows/quickstarts/run-the-workflow-on-a-transactionlink-hosted-page.md).

# Run the workflow on a Transactionlink-hosted page

**You’ll build a simple integration where the user clicks a button in your app and gets redirected to a hosted workflow page.**&#x20;

The example includes both client- and server-side code, and shows how to create a workflow execution and redirect the user to the prebuilt, secure interface hosted by Transactionlink. The workflow is displayed on a standalone page hosted by Transactionlink. You trigger the execution from your backend, then redirect the user to the `link`.

This setup is ideal when you want to get started quickly without building your own UI.

**Use this approach when:**

* You want to integrate fast, with minimal frontend work
* You're sending workflow links via email, SMS, or other channels
* You don’t need to embed the flow inside your app
* You want to offload UI hosting and focus on backend logic

***

{% stepper %}
{% step %}

## Set up the server

### **Create a Workflow Execution**

**Add an endpoint on your server that creates a Workflow Execution.**\
The response includes a `link`, which points to a standalone, secure page hosted by TransactionLink.\
Redirect the user to this `link` so they can complete the workflow in a separate tab or view.<br>

{% hint style="info" %}
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.
{% endhint %}

{% tabs %}
{% tab title="Node.js" %}
{% code lineNumbers="true" %}

```javascript
const express = require('express');
const axios = require('axios');

const API_BASE = process.env.API_BASE ?? 'https://api.transactionlink.io';

// --- Recommended: organization token (tot_) ---
const ORG_TOKEN = process.env.ORG_TOKEN ?? 'tot_<your-organization-token>';

// --- Legacy: workspace credentials (JWT) — deprecated, will be removed in a future release ---
const KEY = process.env.KEY ?? '<your-key>';
const SECRET = process.env.SECRET ?? '<your-secret>';

const WF_DEF_ID = process.env.WF_DEF_ID ?? '<your-workflow-definition-id>';

const api = axios.create({ baseURL: API_BASE });

const app = express();
app.use(express.json());

// Legacy: obtain a JWT access token using workspace credentials.
// Deprecated in favor of organization tokens (tot_).
async function authorizeWithJwt() {
    const { data } = await api.post('/auth/authorize', { key: KEY, secret: SECRET });
    return data.accessToken;
}

app.post('/create-workflow', async (_req, res) => {

    // Option 1 — Recommended: pass the organization token directly (no Bearer prefix)
    const authHeader = ORG_TOKEN;

    // Option 2 — Legacy (deprecated): obtain a JWT and pass it as a Bearer token
    // const jwt = await authorizeWithJwt();
    // const authHeader = `Bearer ${jwt}`;

    const payload = {
        workflowDefinitionId: WF_DEF_ID,
        expiresAt: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
        locale: 'en',
    };

    const { data } = await api.post('/workflows', payload, {
        headers: { Authorization: authHeader },
    });

    res.json({ link: data.link, workflowId: data.id });

});
```

{% endcode %}
{% endtab %}
{% endtabs %}

### 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 **30 days** 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 left blank, we detect the user’s browser language.
* Use this to enforce a specific language for localized flows.

{% endstep %}

{% step %}

## Build your page

{% hint style="info" %}
See full demo code at <https://github.com/transactionlink/examples>
{% endhint %}

### Obtain a redirection URL

**Use an API created on your server in previous step to receive a link for your workflow execution.** 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.

```javascript
async function getLink() {
    try {
        const response = await fetch('/create-workflow', { method: 'POST' });
        const data = await response.json()
        return data.link;
    } catch (err) {
        console.error(err);
        throw err;
    }
}
```

### Redirect to widget

**Use the link and redirect the user.** The flow will then continue in a widget hosted on our end.

{% tabs %}
{% tab title="Vanilla HTML/JS" %}

```html
<script>
async function redirectToWidget() {
    window.location = await getLink();
}

document.getElementById('open-widget')
    .addEventListener('click', redirectToWidget);
</script>

<!-- Button to trigger the widget -->
<button id="open-widget">Open Hosted Widget</button>
```

{% endtab %}

{% tab title="React component" %}

```tsx
let transactionLinkReady = false;
  
function EmbedWidget() {
  const embedWidget = async (): Promise<void> => {
    const token = await getToken();

    if (!transactionLinkReady) {
      window.transactionlink_ready = () => {
        transactionLinkReady = true;
        window.transactionlink!.setOptions({ token });
        window.transactionlink!.open();
      };

      const script = document.createElement('script');
      script.src = 'https://widget.transactionlink.io/transactionlink-widget.umd.js';
      document.head.appendChild(script);
    } else {
      window.transactionlink!.setOptions({ token });
      window.transactionlink!.open();
    }
  };

  return (
    <>
      {/* Button to trigger the widget */}
      <button onClick={embedWidget}>Open Embedded Widget</button>

      <div id="transactionlink-widget" />
    </>
  );
}
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

## 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

{% hint style="info" %}
**Tip:** Place a Webhook Task at the end of the workflow to send a custom completion signal, including any relevant context or identifiers.
{% endhint %}

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

{% hint style="danger" %}
Keep in mind: Webhooks (automatic or via task) are the recommended approach for real-time and scalable integration.
{% endhint %}
{% endstep %}

{% step %}

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

{% endstep %}
{% endstepper %}
