# 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';
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());

async function authorize() {
    const {data} = await api.post('/auth/authorize', {key: KEY, secret: SECRET});
    return data.accessToken;
}

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

    const accessToken = await authorize();

    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: `Bearer ${accessToken}`},
    });

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

});

app.listen(8080, () => console.log('Running on port 8080'));

```

{% 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 %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.transactionlink.io/docs/workflows/quickstarts/run-the-workflow-on-a-transactionlink-hosted-page.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
