# Connect n8n to Atlas Labs

> To connect n8n to Atlas Labs, use a **Webhook** node as the trigger and an **HTTP Request** node with a **Bearer Auth** or **Header Auth** credential for calls. A self-hosted n8n on its default port will be refused, because Atlas Labs only delivers to public HTTPS on port `443`, so put it behind a tunnel or a reverse proxy first.

Canonical: `https://automationatlas.io/labs/docs/tools/n8n/` · Checked: `2026-10-15`

Atlas Labs is a fictional company's API; every figure in every response is simulation data.

*Reference table — Atlas Labs API fields and n8n's documented node names, not part of the fictional simulation. n8n docs checked `15 Oct 2026`.*

| At a glance | Value |
| --- | --- |
| Receive events | **Webhook** node, HTTP Method `POST`, use the **Production URL**; Options → **Raw Body** on for signature checks; Respond “Immediately” answers `200` before later nodes run, and what Atlas Labs sees when a later node fails under the other Respond modes is not something we have confirmed |
| Call the API | **HTTP Request** node; shortcut: Parameters → **Import cURL** with any curl snippet from these docs |
| Where the key goes | Authentication → Generic Credential Type → **Bearer Auth** (paste only `lab_off_YOUR_KEY`) or **Header Auth** (Name `Authorization`, Value `Bearer lab_off_YOUR_KEY`) |
| Body | Send Body → **JSON**; n8n sets `Content-Type: application/json` |
| `Idempotency-Key` | Send Headers → `Idempotency-Key` = the `id` the Code node outputs after parsing the raw body, or the Webhook node's `webhook-id` header referenced by node name, `$('Webhook').item.json.headers['webhook-id']` (check the node name in your workflow); both equal the event `id`. Not `$json.headers[…]`: in the HTTP Request node `$json` is the previous node's output, the GET-order response |
| Webhook host | n8n Cloud URLs on `*.app.n8n.cloud` are recognised; self-hosted needs public `https://` on port `443` |
| Retries | Settings → **Retry On Fail**, **Max Tries**, **Wait Between Tries** (short); for `429` prefer a **Wait** node or HTTP Request **Batching** |
| OpenAPI import | None in the HTTP Request node; only Import cURL |

> **Start a sandbox and your keys are filled in for you.** This page uses `lab_off_YOUR_KEY` and `lab_fac_YOUR_KEY`. After you [start a sandbox](https://automationatlas.io/labs/#start-sandbox-heading), the “Connect it” panel's n8n tab shows the credential values and an Import cURL command with your real keys.

- Atlas Labs → Your workflow: signed webhook, to the Production URL
- Your workflow → Atlas Labs: HTTP Request, Bearer Auth credential
- Your workflow → Your apps: next node

Both legs are optional: a workflow can only listen, only call, or do both.

## How do I receive Atlas Labs events in n8n?

1. **Add a trigger → Webhook.** Set HTTP Method to `POST`. Copy the **Production URL**; the Test URL only works while “Listen for Test Event” is active.
2. **Options → Raw Body: on**, so the Code node can verify the signature against the exact bytes. **Respond** decides what Atlas Labs sees. At “Immediately” n8n answers `200` “Workflow got started” before any other node runs, well inside the `5`-second window, but a failure later in the workflow is then invisible to Atlas Labs. The other Respond modes (“When Last Node Finishes”, or a Respond to Webhook node) answer after the run, which must then finish inside those `5` seconds; what status a failed run returns in those modes is not something we have confirmed, so test it once with the ping and read `GET /webhooks/{id}/deliveries`.
3. **Subscribe the Production URL** with either key. On n8n Cloud the host is `*.app.n8n.cloud`, which Atlas Labs recognises. Self-hosted: the URL must be public `https://` on port `443`, so `http://localhost:5678/webhook/…` is refused with `port_not_allowed`; put n8n behind a tunnel or reverse proxy and check n8n's `WEBHOOK_URL` setting, which decides the public address its Production URL shows.
   - Method: `POST`
   - URL: `https://automationatlas.io/labs/api/v1/webhooks`
   - Headers: `Authorization: Bearer lab_off_YOUR_KEY`; `Content-Type: application/json`
   - Body: `{"url": "https://<your-n8n-host>/webhook/atlas-labs-sales-order-accepted", "events": ["office.sales_order.accepted"]}`
4. **Publish (activate) the workflow** so the Production URL is registered, then send the test ping (`POST /webhooks/{id}/test`). The subscription turns `active` once n8n answers `2xx`.
5. **Verify and parse in a Code node.** In a Code node placed directly after the Webhook node, headers arrive in `$json.headers`; with Raw Body on, the unparsed string arrives under the body (community reports vary by version, so check once in your n8n). Paste the `verify` function below without its two `import` lines and the `export` keyword, replace `process.env.ATLAS_WEBHOOK_SECRET` with the secret from a credential or variable you keep in n8n rather than a `whsec_` value pasted into the code, and throw when it returns `false`; how `node:crypto` is loaded inside an n8n Code node, and whether your instance allows it, is not something we have confirmed (n8n also has a Crypto node). Then parse the string so later nodes have fields: `const event = JSON.parse(raw)` and return `id`, `type` and `data.sales_order_id`. With Respond at “Immediately” the throw only stops the execution; Atlas Labs already received `200` and will not retry. Whether the throw reaches Atlas Labs as a non-`2xx` under the other Respond modes, and so triggers the retries after `10 s`, `60 s` and `300 s`, is not something we have confirmed.

*JavaScript · `snippets/07-verify-webhook.mjs` · excerpt: the header comment and the `verify` function, as in the tested file; the server part is cut where `// …` stands · verified against the repo's own signer*

```javascript
// Receive and verify an Atlas Labs webhook (Node 20+, no dependencies).
// Standard Webhooks: headers webhook-id, webhook-timestamp, webhook-signature.
// signature = "v1," + base64(HMAC-SHA256(key, `${id}.${timestamp}.${rawBody}`)), where key is
// the base64-decoded part of your whsec_ secret. During the 24 h after rotate-secret the header
// holds two space-separated values ("v1,<new> v1,<old>"); accept either.
import { createHmac, timingSafeEqual } from "node:crypto";
import { createServer } from "node:http";

const SECRET = process.env.ATLAS_WEBHOOK_SECRET; // whsec_...
const TOLERANCE_S = 300;

export function verify(secret, headers, rawBody, nowS = Math.floor(Date.now() / 1000)) {
  const id = headers["webhook-id"];
  const timestamp = headers["webhook-timestamp"];
  const header = headers["webhook-signature"];
  if (!id || !timestamp || !header) return false;
  if (Math.abs(nowS - Number(timestamp)) > TOLERANCE_S) return false; // stale or replayed
  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const expected = createHmac("sha256", key).update(`${id}.${timestamp}.${rawBody}`, "utf8").digest();
  return header.split(" ").some((part) => {
    const [version, value] = part.split(",");
    if (version !== "v1" || !value) return false;
    const given = Buffer.from(value, "base64");
    return given.length === expected.length && timingSafeEqual(given, expected);
  });
}

// …
```

## How do I call the Atlas Labs API from n8n?

1. **Add node → HTTP Request.** Set Method and URL, for example `GET https://automationatlas.io/labs/api/v1/office/sales-orders/<id>` to read the accepted order's `quantity` and `due_sim_day`.
2. **Authentication → Generic Credential Type → Bearer Auth.** Paste only the key; n8n sends `Authorization: Bearer …`. Or choose **Header Auth** with Name `Authorization` and Value `Bearer lab_off_YOUR_KEY`. Create two credentials, “Atlas Labs Head Office” and “Atlas Labs Factory”, and reuse them.
3. **For a write, Send Body → JSON** and **Send Headers** → `Idempotency-Key` mapped to the Code node's `id` output, or to the Webhook node's header by node name, `$('Webhook').item.json.headers['webhook-id']` (check the node name in your workflow); both are the event id. In this node `$json` is the previous node's output, the GET-order response, so `$json.headers["webhook-id"]` would be empty here. Set the key in a node before the request rather than with an expression that changes on each try, so retries reuse it.
   - Method: `POST`
   - URL: `https://automationatlas.io/labs/api/v1/factory/work-orders`
   - Authentication: Bearer Auth credential holding `lab_fac_YOUR_KEY`
   - Send Headers: `Idempotency-Key` = the Code node's `id` (the event id)
   - Send Body (JSON): `{"sales_order_id": "<sales_order_id from the Code node>", "quantity": <from the order>, "due_date": <due_sim_day from the order>}`
   **Preset matters.** Pick a preset with a manual bridge (Standard or Full) or this write returns `409 already_bridged`, because Starter bridges automatically.
4. **Shortcut: Parameters → Import cURL.** Paste a curl command from the [REST page](https://automationatlas.io/labs/docs/tools/rest-api/); it overwrites the node's configuration. Values import as strings, so switch the body from “Using Fields Below” to “Using JSON” to keep numbers as numbers.
5. **Treat `409 already_bridged` as success** in an IF node (`201` or `409` with `error.code` = `already_bridged`); anything else should fail the execution.

## Idempotency and retries

- Money and stock routes are idempotent through `Idempotency-Key`, so **Retry On Fail** is safe when the same key is sent on each try.
- The node's **Wait Between Tries** is short, so for `429 rate_limited` read the `Retry-After` header and use a **Wait** node, or space calls with HTTP Request **Batching** (Items per Batch, Batch Interval). The numeric limits of Max Tries and Wait Between Tries are not stated on the node's docs page.
- A reported n8n bug (`1.35.0`) skips retries when **On Error** is set to a Continue option; keep On Error at its default while retrying.

## Common errors

*Reference table — Atlas Labs error codes as seen from n8n, not part of the fictional simulation.*

| You see | Why, and the fix |
| --- | --- |
| `422 webhook_url_rejected`, `port_not_allowed` | The URL has a port other than `443` (a local n8n on `5678`). Expose it over public HTTPS first. |
| `422 webhook_url_rejected`, `hostname_not_public` | `localhost`, a LAN name or a private IP. Use a tunnel or your n8n Cloud URL. |
| `403 wrong_system` | The wrong credential on the node. Head Office routes want the `lab_off_` credential, Factory routes the `lab_fac_` one. |
| `401 unauthorized` | A Header Auth credential whose Value lacks the word `Bearer`, or a key that is unknown, revoked or deleted with its sandbox. An expired sandbox answers writes with `423 sandbox_expired` instead. |
| `422 idempotency_key_reused` | The same key reached a different request. Map a stable value such as the event `id`. |
| Webhook never fires | The workflow is not active, or you subscribed the Test URL. Activate it and subscribe the Production URL. |

## Gotchas

- **No OpenAPI import.** The HTTP Request node offers Import cURL only; community requests for OpenAPI import are open. Earlier Atlas Labs docs said otherwise; this page is the correction.
- **Local n8n is refused.** Atlas Labs delivers only to public HTTPS on port `443`, never follows redirects, and reads at most `1 KB` of your response.
- **Raw body, no fields.** Whether Raw Body exposes the exact bytes in your version is reported inconsistently in the n8n community; verify once with the test ping before relying on it. While it is on, `id` and `data.sales_order_id` are not mappable until the Code node parses the string; map them from that node's output.
- **Test vs Production URL.** They differ; subscribe only the Production URL.
- **Payloads are small.** Atlas Labs webhook bodies are at most `4,096` bytes, far under n8n's default limit.

## Download the workflow

- [bridge-accepted-sales-order-to-work-order.json](https://automationatlas.io/labs/downloads/templates/n8n/bridge-accepted-sales-order-to-work-order.json) — Webhook → Code (verify) → IF → HTTP Request (GET order) → HTTP Request (`POST /factory/work-orders`) → IF (`201` or `409 already_bridged`). Reads `ATLAS_LABS_*` variables you set in n8n. Needs a manual-bridge preset (Standard or Full). (Structurally validated only, not imported into a running n8n)

**What the template needs.** It reads its settings from `$env.ATLAS_LABS_BASE_URL`, `$env.ATLAS_LABS_OFFICE_KEY`, `$env.ATLAS_LABS_FACTORY_KEY` and `$env.ATLAS_LABS_WEBHOOK_SECRET`, and calls `require('crypto')` in a Code node, so it is written for a self-hosted n8n. From the second major version of n8n, `N8N_BLOCK_ENV_ACCESS_IN_NODE` defaults to `true` (its breaking-changes page says so), so set it to `false` for `$env` to work in the Code node; that page also advises credentials rather than environment variables for sensitive data. Importing built-in modules is off unless `NODE_FUNCTION_ALLOW_BUILTIN` is set, so set it to `crypto`. Code nodes run on task runners by default in that version; whether the main-process variable is enough with the default internal runner is not something we have confirmed, and with an external runner the setting belongs in the runner's own environment. n8n's Code node page says Cloud includes `crypto`, but whether you can set environment variables on Cloud is not something we have confirmed, so on Cloud you would replace the `$env` lookups with values you type in yourself. Leave the Webhook node's Raw Body option off: unlike the hand-built recipe above, the template verifies the signature against the parsed body re-serialised with `JSON.stringify`. Atlas Labs builds each webhook body with `JSON.stringify`, so it is compact JSON with no extra whitespace and re-serialising reproduces the same bytes. That n8n then delivers the parsed JSON at `$json.body` is not something we have confirmed against n8n's own documentation.

## Questions people ask

**Why does my local n8n webhook get `port_not_allowed` or `hostname_not_public`?** Atlas Labs only delivers to public HTTPS on port `443`. Use n8n Cloud, or put self-hosted n8n behind a tunnel or reverse proxy with a public hostname.

**How do I import the workflow?** Download the JSON above and import it from the n8n editor, then open each HTTP Request node and attach your credentials. The file was checked for n8n's export shape but has not yet been imported into a live n8n; tell us if a node needs a nudge.

**Can n8n import the Atlas Labs OpenAPI file?** No. The HTTP Request node only imports a curl command. Paste one from the [REST page](https://automationatlas.io/labs/docs/tools/rest-api/).

**Which credential type should I use?** Bearer Auth, with the key alone. Header Auth works too if you type `Bearer ` before the key yourself.

**Does the Webhook node verify Standard Webhooks signatures?** No. Its own Authentication options (Basic, Header, JWT) are for callers who can send credentials, which Atlas Labs does not. Verify in a Code node as shown.

**Why didn't Atlas Labs retry when my Code node threw?** Because Respond was “Immediately”, so n8n had already answered `200`. The other Respond modes (“When Last Node Finishes”, or a Respond to Webhook node) answer after the run; we have not confirmed what status a failed run returns in them, so test once with the ping and check `GET /webhooks/{id}/deliveries`. Either way the run must finish within `5` seconds.

Sources (checked `15 Oct 2026`): docs.n8n.io, HTTP Request node, HTTP Request credentials, Webhook node, node settings (Retry On Fail), HTTP Request common issues, rate limits. Atlas Labs facts from the API source, observed live.

[Zapier](https://automationatlas.io/labs/docs/tools/zapier/) · [Make](https://automationatlas.io/labs/docs/tools/make/)
