VendorStacks
← All posts
Engineering8 min read

A vendor-enrichment workflow in n8n

Stax
VendorStacks research desk

n8n is the right tool for vendor enrichment for an unglamorous reason: the work is mostly loops, branches, and rate-limit discipline, and n8n makes all three visible. You can see the batch size. You can see which rows took the found: false branch. You can see where the workflow stalled. That's worth more than a native integration.

Here's a workflow that takes a list of domains, enriches each with its vendor stack, scores it, and writes the result somewhere useful — built entirely from stock nodes.

The shape

Trigger (Schedule / Webhook / Sheets)
  └─> SplitInBatches (size 10)
        └─> HTTP Request  → GET /v1/check?url={{domain}}
              └─> IF  {{ $json.found }}
                    ├─ true  → Set (score + evidence) → CRM / Sheets
                    └─ false → Set (status: "unknown") → review list
        └─> Wait (12s)  ─┘  loop back to SplitInBatches

Five node types, one loop. Everything interesting happens in the HTTP Request node and the IF condition.

Node 1 — the trigger

Whatever produces domains. A Schedule Trigger reading a Google Sheet works for a one-off list; a Webhook node works if your form handler or CRM pushes new accounts as they arrive. The only requirement is that each item reaching the next node has a domain field. Normalize it early — strip https://, strip www., and drop free-mail domains (gmail.com, outlook.com, yahoo.com and friends) before you spend anything on them. A Code or Set node with a small filter expression handles this in one step.

Node 2 — SplitInBatches

Put the batching node before the HTTP call, not after. Batch size 10 is a good default. This is what lets you respect the rate limit and recover from a mid-run failure without re-processing the whole list.

Node 3 — the HTTP Request node

The core of the workflow. Configuration:

  • Method: GET
  • URL: https://api.vendorstacks.com/v1/check?url={{ $json.domain }}
  • Authentication: send a header Authorization with value Bearer vr_live_…. Use n8n's credential store (a generic header-auth credential) rather than pasting the key into the node — it keeps the key out of exported workflow JSON.
  • Timeout: 120000 ms. This one matters. Indexed domains answer in well under a second, but a domain that isn't in the index triggers a live scan of the company's public web presence, and that takes 15–90 seconds. n8n's default timeout will kill those requests halfway through and you'll conclude the API is broken when it isn't.
  • Continue on fail: on. A single bad domain shouldn't halt a 500-row run.

If you want to guarantee sub-second responses and never wait on a scan, point the URL at https://api.vendorstacks.com/v1/company/{{ $json.domain }} instead. Same response shape, same 1 credit, but it only ever reads the index — no live scan, no long wait. That's the right choice for workflows on a tight execution budget, and the wrong choice when you actually want coverage on domains nobody has looked up yet.

Node 4 — IF on the found flag

Condition: boolean, {{ $json.found }} is true.

The false branch deserves a real destination, not a dead end. found: false means no public evidence was located for that domain — not that the company uses no vendors. Send those rows to an "unknown" list a human can triage. Scoring them as zero is the single most common way teams poison an otherwise good dataset.

There's a billing consequence too, and it's in your favour: lookups are 1 credit each, but you're only billed for successful results. A found: false, a failed scan, and a zero-result reverse lookup all cost nothing.

Node 5 — the scoring Set node

On the true branch, flatten the response into the fields you actually want to store. A Set node with expressions does it without any code:

domain          = {{ $json.domain }}
sms_vendors     = {{ ($json.vendor_stack.sms_messaging || []).join(", ") }}
payments        = {{ ($json.vendor_stack.payments || []).join(", ") }}
vendor_count    = {{ Object.values($json.vendor_stack || {}).flat().length }}
evidence_url    = {{ ($json.subprocessor_urls || [])[0] }}
confidence      = {{ $json.vendor_confidence }}
scanned_at      = {{ $json.scanned_at }}
has_competitor  = {{ JSON.stringify($json.vendor_stack).toLowerCase().includes("twilio") }}

Store evidence_url alongside the vendors. A CRM field that says "uses Twilio" gets argued with; a field that says "uses Twilio — see their own published page" does not.

Node 6 — the loop back, and the rate limit

Wire the end of the branch back to SplitInBatches, with a Wait node in between. The default rate limit is 60 requests per minute. With a batch size of 10, a 10-second wait per batch keeps you comfortably under it; 12 seconds gives you margin for retries.

Handle the error codes explicitly rather than letting them fall through as generic failures:

  • 429 rate_limited — you exceeded 60 rpm. Increase the Wait, don't retry immediately.
  • 429 daily_scan_limit — free plans allow 5 live scans a day. Either switch the URL to /v1/company (index-only, never scans) or top up.
  • 402 payment_required — out of credits. The response carries a topup_url; surface it in a notification rather than silently dropping rows.
  • 502 scan_failed — a live scan couldn't complete. Charged zero credits. Safe to retry later.

Scheduling it

Once the workflow runs cleanly, the highest-value version is the scheduled one. Point the trigger at your customer or pipeline list, run it monthly, and store vendor_stack and scanned_at each time. Diffing this month against last month gives you the two triggers worth interrupting a human for: a competitor appearing in a customer's stack, and a prospect adding a category you sell into.

For the full field reference see the API docs; for the equivalent pipeline written as code rather than nodes, see the Meta Ads scoring pipeline. Grab a key and you get 25 free credits to test the workflow on a real list before you decide anything.

About the author

Stax is the pangolin who fronts the VendorStacks research desk — a fitting mascot for a company that reads layered stacks for a living. Posts under this byline are written by the VendorStacks team.

VendorStacks is the subprocessor disclosure data API — structured vendor stacks with quoted evidence, 25 free credits to start.

Get an API key