# n8n

> Send records from any of n8n's apps to Dimension Labs with a workflow and the universal API, on n8n Cloud or your own n8n.

n8n connects to thousands of apps ([n8n's integrations page](https://n8n.io/integrations/) lists 2,000+ as of 2026-10-26). If a tool you use has an n8n trigger, such as a new form response, review, ticket or spreadsheet row, a workflow can send each new record to the [universal API](/api/tracker). It runs on n8n Cloud or on your own n8n, with nothing else to host.

n8n fits sources where one trigger event is one piece of customer text: a form or survey response, a review, a feedback submission, the first message of a ticket. For full multi-message conversations, or to load history, use the app's own guide or [Any other system](/integrations/other-sources), which pull every message in order.

## What you need

- An n8n Cloud account or a self-hosted n8n.
- An n8n trigger for your app that returns the customer's text and a date.
- A Dimension Labs API key for a new **Universal API** source. See [Your API key](/api/tracker#your-api-key).

## 1. Choose the trigger

1. In the workflow editor, add the trigger node for your app, for example a new response, new review or new row.
2. Run it once and check the output has three things: the customer's text, an id for the record, and when it was written.

Use one source in Dimension Labs per app, so its data is easy to filter later.

## 2. Build the message in a Code node

n8n's Code node can't make HTTP requests ([Using the Code node](https://docs.n8n.io/build/code-in-n8n/using-the-code-node)), so the work is split: a Code node builds each message, and an HTTP Request node sends it.

1. Add a **Code** node after the trigger. Set the language to **JavaScript** and the mode to **Run Once for All Items**.
2. Paste this code, then edit the `pick` lines so each one reads the right field from your trigger's output, and set `SOURCE` to the app's name.

```js
// n8n Code node (JavaScript, Run Once for All Items).
// Builds one Dimension Labs message per input item. The HTTP Request node sends it.
const SOURCE = 'jotform';                 // the app's name, kept in platformJson

// Edit the right-hand side of each line to match your trigger's fields.
const pick = (d) => ({
  text: d.text,                           // the customer's words
  recordId: d.id,                         // the record's own id: becomes sessionId
  customerId: d.customerId,               // optional: your internal customer id, never an email or name
  writtenAt: d.createdAt,                 // when it was written, ideally with a time zone
  extra: { rating: d.rating },            // anything else to filter or join on later
});

const clean = (s) => String(s).replace(/:/g, '-');   // ids can't contain colons
const out = [];

for (const item of $input.all()) {
  const { text, recordId, customerId, writtenAt, extra } = pick(item.json);
  if (!text || !String(text).trim()) continue;       // skip empty text
  const ts = typeof writtenAt === 'number' ? (writtenAt < 1e12 ? writtenAt * 1000 : writtenAt) : Date.parse(writtenAt);
  if (Number.isNaN(ts)) throw new Error(`Can't read the date "${writtenAt}". Map a date with a time zone.`);
  const sessionId = clean(`${SOURCE}-${recordId}`);
  out.push({
    json: {
      endpoint: Date.now() - ts > 60 * 60 * 1000 ? 'trackhistorical' : 'track',
      message: {
        text: String(text),
        userId: customerId ? clean(customerId) : sessionId,
        incoming: true,
        sessionId,
        conversationId: sessionId,
        dimensionlabs_timestamp: ts,
        platformJson: { source: SOURCE, ...extra },
      },
    },
  });
}

return out;
```

The code returns one item per record, each with the message and its endpoint: a record written more than an hour ago goes to `/trackhistorical`, as every guide does. A date given as a number in seconds is turned into milliseconds. Records with empty text are dropped.

## 3. Send it

Add an **HTTP Request** node after the Code node ([HTTP Request node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest/)):

1. **Method:** `POST`.
2. **URL:** switch the field to an expression and enter `https://tracker.dimensionlabs.io/{{ $json.endpoint }}`
3. **Authentication:** none. The API key goes in the query string.
4. Turn on **Send Query Parameters**, choose **Using Fields Below**, and add:

| Name | Value |
|---|---|
| `platform` | `universal` |
| `v` | `11.1.0-rest` |
| `type` | `incoming` |
| `apiKey` | your Dimension Labs API key |

5. Turn on **Send Body**. Set **Body Content Type** to **JSON** and **Specify Body** to **Using JSON**, and set the JSON field to the expression `{{ JSON.stringify($json.message) }}`. n8n sends `Content-Type: application/json`.

`JSON.stringify` escapes quotes and line breaks in the customer's text, which is why this guide builds the message in code rather than typing the JSON by hand: a review with a quotation mark in it would break hand-typed JSON.

n8n runs the HTTP Request node once per item, so each record is its own `POST`. Run the node once with a test record. A `200` response means the message reached Dimension Labs.

## 4. Turn it on

- **Turn the workflow on.** Each new record is sent as it arrives, to `/track`, and is enriched automatically every day.
- **If n8n sees a record late** (more than an hour after it was written, for example on a polling trigger), the code sends it to `/trackhistorical`. Run a [backfill](/dimensions/backfill) from the **Backfill** tab to enrich those.
- **History:** a trigger only sees new records. Export your history from the app and [upload a file](/data/upload-a-file) (check it with the [file checker](/data/check-a-file)), then run a backfill.
- **Re-runs are safe:** if you retry an execution, duplicates are removed automatically.

**Personal data.** Don't put email addresses, names or phone numbers into `extra`: it is sent as `platformJson`. Redact the text if customers include personal details. See [PII redaction](/data/pii-redaction).

## A record with several messages

If the trigger returns a whole conversation (a ticket with replies, a chat transcript), each message needs its own `POST`, in order, at least one second apart, with replies from your side marked `incoming: false` and `type=outgoing`. That is easier and more reliable in a script: see the app's guide, or [Any other system](/integrations/other-sources).

## n8n reference

- [n8n integrations](https://n8n.io/integrations/)
- [HTTP Request node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest/)
- [Using the Code node](https://docs.n8n.io/build/code-in-n8n/using-the-code-node)
- [Code node common issues](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/common-issues)
- [Understand n8n's data structure](https://docs.n8n.io/build/work-with-data/understand-n8ns-data-structure)

Checked against n8n's documentation on 2026-10-26.
