Inbound lead qualification in n8n — evaluate webhooks, branch on status / next_action

I kept hitting the same wall with inbound lead workflows: a pile of IF nodes and Code nodes trying to guess whether a form submission was actually qualified. Criteria changed, and the workflow broke.

So I wired a pattern that separates decision from orchestration:

  • n8n handles triggers, CRM updates, Slack, email

  • EchoStack evaluates the payload against your criteria and returns structured JSON you can branch on

The pattern

Webhook / HubSpot / Typeform
  → EchoStack Evaluate
  → Switch on status
      QUALIFIED  → assign AE, update CRM, book meeting
      PARTIAL    → nurture / ask for missing fields
      FAILED     → archive or tag disqualified
      ESCALATE   → Slack alert / human review

The Evaluate node returns status, next_action, missing_fields, and extracted fields — so your Switch node stays simple.

Try it

1. Install the community node

  • Self-hosted: npm install n8n-nodes-echostack (restart n8n after install)

2. Set up credentials

You need an EchoStack org API key (esk_…) and an evaluation_id. The eval quickstart walks through both in a few minutes.

In n8n, create an EchoStack API credential:

Field Value
Base URL api.getechostack.com
Organization API Key esk_… from Dashboard → API Keys
Evaluation ID UUID from POST /v1/evaluations

Use Test on the credential — it validates without consuming evaluation quota.

3. Import the starter workflow

Download and import: example-workflow.json

In n8n: ⋯ → Import from file, attach your credential on the EchoStack Evaluate node, then run once with the pinned test data.

Full walkthrough: n8n integration guide

npm: n8n-nodes-echostack

Sample output (what you branch on)

After Evaluate, your Switch can use:

  • {{ $json.status }}QUALIFIED, PARTIAL, FAILED, ESCALATE

  • {{ $json.next_action }}BOOK_MEETING, GATHER_INFO, NURTURE, DISQUALIFY, ESCALATE_NOW, CLOSE

  • {{ $json.missing_fields }} — fields still missing on PARTIAL

PARTIAL is worth routing explicitly — it means required fields are missing, not a soft pass. We send those to nurture, not straight to a senior AE.

Production tips

  • Replace the Webhook with HubSpot → On Form Submission (or Typeform, etc.) — keep Evaluate + Switch as-is

  • Dedupe webhook retries before calling Evaluate

  • Transcript mode works too if you’re scoring post-call conversations

Question for the community: How are you handling lead qualification today — Code nodes, LLM agents, CRM-native scoring, something else? Curious what triggers and branch patterns people are using.

Happy to help if you hit install or credential issues — reply here

1 Like

On the branch pattern, the thing that’s held up best for me is treating a single status field on the CRM record as the state machine and branching the Switch on that plus a next_action, rather than re-deriving qualification from the raw payload on every run. Same shape you’ve got. The payoff is that any node can read where a record sits without recomputing it, and a stuck lead can be replayed just by looking at its status.

The line I’d dig into is “dedupe webhook retries before calling Evaluate,” because that’s exactly where these setups break and it’s easy to skim past. CRM webhooks are typically at-least-once delivery, by default. HubSpot, for example, retries a slow or failed delivery up to 10 times over roughly 24 hours (https://developers.hubspot.com/docs/api-reference/webhooks-webhooks-v3/guide), so the same submission will reach Evaluate more than once unless something stops it. Two things that have worked for me, both cheap to add.

  • Respond to the webhook right away, then evaluate. The HubSpot timeout is only 5 seconds, and an Evaluate call plus branching can run past that, which triggers a retry and a second run all on its own.
  • Make the dedup key durable instead of in-memory. Writing the result back onto the record (an evaluated_at, or a run id) and short-circuiting when it’s already set makes a replay a no-op even across a restart. The status field you’re already keeping can double as that guard.

Curious how EchoStack handles re-evaluation when criteria change. If I update the eval and the same lead comes back through, does it re-score, or is it pinned to the first result? That’s the case I’d most want to design around.

Great callout, especially the HubSpot 5s timeout. Dedupe is exactly where these setups break.

CRM as state machine: Agree. EchoStack Evaluate is stateless. It returns status + next_action for that run. Write those to the CRM record and branch from there. Downstream nodes read the record, not the raw payload.

Respond fast + durable dedup:

  1. Ack the webhook immediately, evaluate async. The starter workflow uses lastNode for easy testing. For HubSpot prod, respond before Evaluate.
  2. Write evaluated_at (or reuse your status field) back to the CRM and short-circuit if already set. Survives n8n restarts; in-memory dedup doesn’t.

Re-evaluation when criteria change: No pinning. Every evaluate call re-scores against the current manifest. For criteria changes, I’d recommend a new evaluation_id, run both for a week, then cut over. To re-score stuck leads, clear the CRM guard and replay.

Appreciate the HubSpot retry detail. I’ll update the starter workflow notes to call out respond-first explicitly.

The “respond first, evaluate async” pattern and the evaluated_at guard in CRM are exactly the right moves for production. One thing that could make this even tighter: the first webhook response can include a preliminary status: "pending" in the payload, so any downstream system (like your CRM) knows the lead is in-flight and skips duplicate submissions while the EchoStack evaluate completes.