n8n-nodes-blanktrail
v0.2.4
Published
Send HTTP requests through BlankTrail Proxy with a real browser network profile (TLS/JA3, HTTP/2, HTTP/3, headers).
Downloads
431
Maintainers
Readme
n8n-nodes-blanktrail
Send HTTP requests from n8n through BlankTrail Proxy, so they carry the network profile of a real browser — TLS (JA3/JA4), HTTP/2, HTTP/3, header order and connection parameters agreed as one configuration — instead of the Node.js profile that the built-in HTTP Request node cannot hide.
Switching an existing workflow is meant to be the replacement of one node: the
output shape (statusCode, headers, body) matches the built-in HTTP Request.
Install
In n8n: Settings → Community Nodes → Install → n8n-nodes-blanktrail.
You also need BlankTrail Proxy running and reachable from n8n. The quickest way is
the container: see deploy/docker/README.md in the product distribution.
Credential
| Field | What it is |
|---|---|
| Base URL | Control API. http://blanktrail:8891 inside Docker Compose, http://127.0.0.1:8891 on a desktop install. |
| API Key | BT_API_KEY, or the key shown in the dashboard. |
| Proxy User | User name for the proxy port itself. Default blanktrail. |
| Proxy Password | BT_PROXY_PASSWORD. |
The proxy password cannot be fetched automatically. The control API deliberately returns the proxy user name and never the password, so it has to be typed here. That is a decision of the product, not a gap in this node.
The credential's Test button calls /api/v1/license/status rather than /health:
health answers without authentication, so a wrong API key would pass the test and
only surface later, inside somebody's workflow.
Changing the connection later
Once a credential is attached to a node, the Create new credential button on that node is gone: the node shows a Credential to connect with dropdown with a pencil beside it instead. The pencil opens the saved credential for editing, and the dropdown still offers Create new. Nothing is broken — the button disappears precisely because a credential is already bound. Every credential is also listed under Overview → Credentials, which is where to go when the node is not open in front of you.
Two changes on the BlankTrail side make a saved credential stop working.
The container was reinstalled, and the key changed with it
The control API key lives in the /data volume, not in the container image or
in the environment. It is seeded once, when /data is first created, and read
from /data/auth.json afterwards — which is why editing BT_API_KEY on an
existing deployment changes nothing at all.
Recreate that volume — docker compose down -v, a wiped volume, a fresh host —
and the key is seeded from scratch. If BT_API_KEY is set in your compose file,
the same value comes back and the saved credential keeps working. If it is empty,
the product generates the key itself, so the new one is different and n8n goes
on sending the old one.
Read the new key out of the installation:
docker compose logs blanktrail | grep "initial API key"It is also written to /data/initial-credentials.txt. Paste it into the API
Key field of the existing credential rather than creating a second one: every
node already pointing at that credential is then fixed at once.
The installation moved to another server
Then it is Base URL that changes. The address is resolved from inside the n8n
container, not from your desktop: under Docker Compose it is the service name of
the proxy, http://blanktrail:8891. http://127.0.0.1:8891 only works where n8n
and the proxy share a network namespace, such as a desktop install — inside the
n8n container, localhost is n8n itself.
What a stale connection looks like
| Message | What it means |
|---|---|
| cannot reach the installation at … — the connection was refused (ECONNREFUSED) | The address is shaped right, but nothing answers on it: the container is not running, or the control API port is not published to n8n. |
| cannot reach the installation at … — the host name does not resolve (ENOTFOUND) | The name in Base URL means nothing on the network n8n sits in: the installation moved, or the name is not the Compose service name. |
| the control API rejected the key | The installation answers, but the API Key is not its key — typically the /data volume was recreated and the key with it. |
The first two name the address they tried, so the message itself says which credential field to look at.
How it differs from the built-in HTTP Request
| | Built-in | This node |
|---|---|---|
| TLS fingerprint | Node.js | selected browser |
| HTTP/2 · HTTP/3 | Node.js defaults | browser-shaped |
| Header order | library order | browser order |
| Certificate of the proxy | needs NODE_EXTRA_CA_CERTS | fetched over the API and used directly |
| Proxy credentials | manual | from the credential |
Because the node fetches the CA itself, it works without NODE_EXTRA_CA_CERTS.
Set that variable anyway if other nodes in the same workflow talk through the proxy.
Never switch on "Ignore SSL issues" to make things work: it disables certificate checking entirely, including for genuine external certificates.
Ports and profiles
The node ensures a port rather than opening one: if the port is already open, it is used as it is and its configuration is not touched.
The profile belongs to the port, not to the request. Two executions running at the same time and asking for different browsers would otherwise reconfigure the one port under each other, and the request would quietly leave with somebody else's fingerprint. So Browser and Operating System apply only when this node is the one that opens the port.
The same rule covers Upstream Proxy and Chain Proxy, added for the same
reason. Upstream Proxy is the exit proxy the port's traffic leaves through —
SOCKS5 or HTTP, credentials included in the URL if it needs them
(socks5://user:pass@host:1080); empty means a direct connection. Chain Proxy
is an extra first hop placed before it (http://host:3128); empty means none.
Both are read only when this node opens the port, exactly like Browser and
Operating System.
On the single-thread plan there is one port. Asking for a second one fails with a message saying the limit is reached — close a port you no longer use, or raise the plan.
The BlankTrail node
Besides replacing the HTTP Request node, the package ships a second node — BlankTrail — for the control API itself: opening and closing ports, listing fingerprint profiles, and reading installation status. Where the HTTP Request node opens a port only as a side effect of sending a request, this node exposes each operation directly, so a workflow can prepare a port, inspect it, or tear it down as its own step.
| Resource | Operation | What it does |
|---|---|---|
| Port | Open | Open a port |
| Port | Close | Close a port |
| Port | List | List open ports |
| Port | Get | Get the configuration of a port |
| Port | Suggest | Suggest a free port number |
| Profile | List | List available profiles (one page; Browser and Limit narrow it, total says how many there are) |
| Profile | Get Applied | Get the profile applied to a port |
| Status | Health | Check that the installation responds |
| Status | License | Get the license status |
| Status | Solver | Get Challenge Breaker capacity |
Example: open a Chrome/Windows port, then request through it
- BlankTrail — Resource
Port, OperationOpen, Port10001, and under Options set Browser tochrome, Operating System towindowsand Spoof Headers totrue. The output carriescurrent_profile, the fingerprint the port actually got, so the run can be checked rather than assumed. - BlankTrail HTTP Request — Port
10001(or{{ $('BlankTrail').item.json.port }}, which also works when step 1 asked the server for a free port), Method and URL as needed, and under Headers aUser-Agent— any value,{"User-Agent": "n8n"}will do. The port is already open with the profile from step 1, and an already-open port is used exactly as it is — so this second node just sends the request through it, replacing thatUser-Agentwith the profile's own.
Spoofing rewrites the User-Agent, it does not add one
Both spoofing options are off by default, matching the product, and both only
rewrite a User-Agent that the request actually sends. Neither adds a missing
one. That is why the two halves of the example have to be set together: with no
User-Agent in the request there is nothing to rewrite, and the request leaves
carrying the TLS and HTTP/2 of a real Chrome and no User-Agent at all — a
combination that stands out more than an honest Node.js one.
- Spoof User Agent rewrites that one header.
- Spoof Headers sends the browser's whole header set in its order, and
rewrites the
User-Agentas well — so it covers Spoof User Agent, and one of the two is enough.
Both are settings of the port, so they live on the Open step and, like
Browser and Operating System, apply only to the run that actually opens it.
Opening the port as its own step is only needed when a workflow reuses one port
across several HTTP Request nodes, or inspects it (Get, Profile → Get
Applied) before sending anything. A single BlankTrail HTTP Request node already
opens a port on its own when neither is needed.
Idle ports close themselves. Open takes an Idle Seconds option, default
1800 (30 minutes); after that much inactivity the installation closes the port
on its own, and 0 turns this off. This is not a convenience knob — a scenario
that opens a port and then fails before reaching Close would otherwise leave it
standing forever, and on the single-thread plan the port limit is exactly the
kind of thing that goes unnoticed for a hundred runs and then fails abruptly: the
error surfaces on a later, unrelated execution that merely asked for a free port,
not on the run that actually leaked it.
Open is idempotent: a port that is already open is returned as it is, with its
current configuration, and is not reconfigured. Re-running the workflow therefore
does not fail with "port already open", and on a one-port plan it does not report
the limit as reached against the very port it asked for. The flip side is that
Browser, Operating System and Specific Profile apply only to the run that
actually opens the port — to change the profile of a standing port, close it
first.
Both runs return the same shape, so an expression written against the first one
keeps working on the second: port, protocol, status and current_profile
(name, browser, os, user_agent) are always there. status is the one
value that differs — opened on the run that opened the port, already_open on
a run that found it standing — which is how a workflow can tell the two apart.
The idempotent run also passes the port's stored configuration through, so the
output additionally carries fields such as mode and browser. Beware that
this top-level browser is the filter the port was opened with, not the
browser it ended up with — the applied one is current_profile.browser, and on
an unlucky pick the two genuinely differ.
The two nodes disagree about how long a port lives — read this before scheduling
Open arms an auto-close (Idle Seconds, 1800 by default). The HTTP Request
node does not: it ensures the port, so when the idle timer has already closed
it, that node silently re-opens it — without the Browser and Operating System
filters set on the Open step, because those live on the Open node, not on the
request. A scheduled workflow then keeps working and quietly leaves with a
different fingerprint than the one it was built around, with nothing in the
output to say so.
Two ways to avoid it, pick one:
- Repeat
BrowserandOperating Systemon the BlankTrail HTTP Request node as well, so a re-opened port comes back with the same profile; or - set
Idle Seconds: 0onOpen, so the port never closes itself, and close it explicitly withPort → Closeat the end of the run.
The second one trades the leak the idle timer prevents for a stable fingerprint —
use it only where a Close step is guaranteed to run.
The same goes for the two spoofing options, and only the second way covers them:
the HTTP Request node has no Spoof Headers field to repeat, so a port it
re-opens comes back with spoofing off and the User-Agent written in
Headers goes out untouched, next to a browser TLS fingerprint. Where the
spoofing matters, pin the port with Idle Seconds: 0 and close it explicitly.
SOCKS5 ports and the HTTP Request node
Open can create a socks5 port, but the BlankTrail HTTP Request node
cannot send through one: it builds an HTTP proxy agent. Use http for ports that
node will use, and reach for socks5 only when some other client (a browser, a
scraper, another tool) connects to the port directly.
Licence
MIT
