Qualify inbound signups with Twain and n8n

Mohamed ChahinJune 30, 20268 min read

Updated

Find the signups worth a fast reply. Twain researches each usable lead, a separate fit judge scores it against your ICP, and n8n routes the right ones to your team.

Your signups are not the problem. Knowing which ones to drop everything for — that is the problem.

Every inbound signup is a hand raise. But raw signups arrive flat: a real RevOps leader at a 200-person SaaS lands in the same queue as a student, a competitor poking around, and someone using a personal Gmail to kick the tires. Until someone manually digs in, every signup looks identical. So the good ones wait.

This workflow makes that first pass for you. A signup comes in, Twain researches the person and company, a separate fit judge scores the evidence against your ICP into Tier 1 / Tier 2 / No Fit, and n8n routes the selected fits to Slack and HubSpot with the reason attached. Your AEs can start with the signups worth a closer look.

The signup triage tax

Doing this by hand is slow, and the cost is hidden because it is spread across the whole team.

To qualify one signup properly, a human opens the CRM, searches LinkedIn, reads the company site, and forms a judgment on fit. Done well, that is roughly 15-20 minutes per signup. Multiply that across a busy inbound week and qualification quietly becomes a full part-time job.

So teams cut corners. They skim the email domain and guess. They lean on stale CRM fields that were last touched months ago. They batch-review signups once a day — by which point the hottest lead has already cooled, or signed with someone faster. Speed-to-lead is one of the most reliable predictors of conversion, and manual triage is where it goes to die.

The result is a funnel that treats a six-figure account and a tire-kicker exactly the same until a human gets around to telling them apart.

What you get

This workflow flips the default. Instead of every signup waiting on a human, each signup with a work email or LinkedIn profile is researched and scored when the workflow runs, and only the selected fits reach a human.

  • Real fits only. Tier 1 and Tier 2 signups get pushed to your team. Personal-inbox-only signups, off-ICP roles, and incomplete profiles are filtered out before they ever ping anyone.
  • Fresh research, not stale CRM. Twain researches the lead live against the web and LinkedIn at the moment of signup, so you act on who they are today — not a CRM field from last quarter.
  • Context, not just a name. Each alert carries the person, their title, their company, the fit tier, the fit summary, and Twain's full Thesis. No tab-hopping required.
  • Faster routing. The selected accounts surface when research and classification finish, without waiting for a manual CRM review.
  • Native push to your stack. Fits flow straight into Slack and HubSpot — fit tier and reason written as CRM fields you can route, segment, and report on.
  • Zero manual triage. Nobody opens the CRM to sort signups. The team spends its attention on replies, not on sorting.

For Demand-Gen and RevOps, the ROI is direct: you reclaim the hours that went into manual qualification, and you raise the conversion rate on the leads that mattered by getting to them first.

What the two steps cost

Twain researches each signup that has a work email or LinkedIn profile. Leads that pass the first warning check then reach a separate paid fit judge:

  • Twain generation: the workflow explicitly requests High mode through Generate/Sequence. A new High generation currently costs 2 Twain credits and returns Research plus a Thesis.
  • Fit classification: the included n8n AI Agent uses Anthropic Claude Sonnet 4.6 to apply your tier rules to Twain's person, company, warnings, Thesis, and research insights. It requires a separate Anthropic credential, and Anthropic usage is billed separately under your Anthropic account.

That separation matters when you estimate cost or change the classifier. Twain provides the research and Thesis; the Anthropic node makes the final Tier 1 / Tier 2 / No Fit decision using the rules in the workflow.

An AE still makes the judgment on a live deal. This workflow handles the repetitive first pass so the team can spend its time on the signups it should contact.

How the tiering works

Twain's Thesis explains the prospect's fit against your campaign brief. The workflow's separate fit judge turns that evidence into the tier your team routes on.

When a signup comes in, Twain researches the person and company and returns a Thesis plus current research insights. The separate fit judge scores that evidence into three buckets and returns a short analysis, a summary, and a tier:

  • Tier 1 — strong fit. Core ICP: the role, seniority, and company type all line up with who buys from you. Drop-everything leads.
  • Tier 2 — plausible fit. Adjacent or early-stage accounts worth a look but not an emergency.
  • No Fit. Off-ICP roles, B2C-only, students, job seekers, obvious mismatches. Logged, not escalated.

Two stages keep it sharp. After Twain's research finishes, the workflow checks its warnings for disqualifiers such as persona mismatches and incomplete profiles. Leads that pass go to the paid fit judge for the tier decision.

Before using the template, edit the product description and ICP tiers in the Stage B — Fit Judge node for your company. Keep those rules aligned with the campaign brief in Twain: the brief guides the research and Thesis, while the node's prompt defines the final tier criteria.

What lands in Slack

The difference between a noisy alert and a useful one is whether a human has to do more work after reading it.

A plain "new signup: alex@company.com" still forces someone to go research. A Tier 1 alert from this workflow lands ready to act on:

  • The fit tier, up front.
  • The person, their title, and their company.
  • A one-line reason citing the strongest fit signal.
  • Twain's full Thesis for the prospect.
  • A direct link into Twain for the full lead view.

An AE reads that in a few seconds and replies — no tabs, no CRM dive, no guesswork. That is what turns a signup notification into pipeline.

Import the template

The whole flow ships as a ready-to-import n8n template. Copy the sanitized workflow JSON below — secrets are removed and live IDs are replaced with placeholders. After import, attach your own Slack, HubSpot, Gmail, Anthropic, and Twain credentials, and point the campaign_id at the signup-triage campaign you created in Twain. Twain generation and the Anthropic fit judge are separate paid steps.

{
  "name": "Twain — Qualify & Route Free Signup",
  "settings": {
    "executionOrder": "v1",
    "availableInMCP": true,
    "binaryMode": "separate"
  },
  "nodes": [
    {
      "id": "e5b87045-866c-4179-8f5f-039aa20374be",
      "name": "Signup Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [240, 520],
      "parameters": {
        "httpMethod": "POST",
        "path": "signup-lead-qualify",
        "authentication": "headerAuth",
        "options": {}
      }
    },
    {
      "id": "e136bab6-5b4b-44b8-8661-d4eb4847cfad",
      "name": "Has usable contact?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [3280, 520],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 3
          },
          "conditions": [
            {
              "id": "usable-1",
              "leftValue": "={{ Boolean($json.has_work_email) || Boolean($json.has_linkedin_url) }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      }
    },
    {
      "id": "3071075f-8355-4380-82c0-baa8e2f57d58",
      "name": "Slack: Personal email",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.5,
      "position": [3660, 740],
      "onError": "continueRegularOutput",
      "parameters": {
        "authentication": "accessToken",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "n8n-workflows"
        },
        "text": "=:no_entry_sign:  *Twain — Qualify & Route Free Signup — skipped*\n\n*{{ $('Normalize Signup').item.json.displayName || $('Normalize Signup').item.json.email || 'Unknown signup' }}*\n:email:  {{ $('Normalize Signup').item.json.email || 'No email provided' }}\n\nNo work email, and the LinkedIn lookup didn't return a profile URL for this signup — so there's nothing to research or qualify. Enable a LinkedIn Lookup provider (or add a work email / LinkedIn URL upstream) to catch leads like this.\n\n<https://www.twain.ai/w|Open Twain workspace>",
        "otherOptions": {
          "includeLinkToWorkflow": false,
          "unfurl_links": false,
          "unfurl_media": false
        }
      }
    },
    {
      "id": "57f5a107-40c8-41c7-8df6-18dc5bab8278",
      "name": "Twain Generate Sequence",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [3660, 520],
      "onError": "continueErrorOutput",
      "parameters": {
        "method": "POST",
        "url": "https://public.api.twain.ai/v2/Generate/Sequence",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {\n  campaign_id: $('Normalize Signup').item.json.campaign_id,\n  contact: {\n    ...($('Normalize Signup').item.json.has_work_email ? { work_email: $('Normalize Signup').item.json.email } : {}),\n    ...($json.has_linkedin_url ? { linkedin_profile_url: $json.linkedin_url } : {}),\n  },\n  mode: \"HIGH\",\n  add_contact_to_campaign: true,\n} }}",
        "options": {
          "timeout": 180000
        }
      }
    },
    {
      "id": "ac4d8c53-4292-47ba-9cea-e5c59befd797",
      "name": "Stage A — Hard Disqualify?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [4040, 520],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 3
          },
          "conditions": [
            {
              "leftValue": "={{ $('Twain Generate Sequence').item.json.warnings.categories }}",
              "operator": {
                "type": "array",
                "operation": "contains",
                "rightType": "any"
              },
              "rightValue": "PERSONA_MISMATCH",
              "id": "8e8de89c-59d1-4dff-8271-81a3896c5f54"
            },
            {
              "leftValue": "={{ $('Twain Generate Sequence').item.json.warnings.categories }}",
              "operator": {
                "type": "array",
                "operation": "contains",
                "rightType": "any"
              },
              "rightValue": "INCOMPLETE_PROFILE",
              "id": "99e07edb-fc06-4ba3-86e2-4995a996d349"
            }
          ],
          "combinator": "or"
        },
        "looseTypeValidation": true,
        "options": {}
      }
    },
    {
      "id": "5a8ec23c-c547-489d-9e62-b9f54b76de37",
      "name": "Stage B — Fit Judge",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [4420, 520],
      "parameters": {
        "promptType": "define",
        "text": "=Prospect company: {{ $('Twain Generate Sequence').item.json.research?.company?.name || $('Normalize Signup').item.json.displayName || $('Normalize Signup').item.json.email_domain }}\nWebsite: {{ $('Twain Generate Sequence').item.json.research?.company?.website || '' }}\nCompany size: {{ $('Twain Generate Sequence').item.json.research?.company?.size || 'unknown' }}\nCompany description: {{ $('Twain Generate Sequence').item.json.research?.company?.description || '' }}\n\nIndividual: {{ $('Normalize Signup').item.json.displayName || '' }} — {{ $('Twain Generate Sequence').item.json.research?.person?.title || '' }} ({{ $('Normalize Signup').item.json.email }})\nHeadline: {{ $('Twain Generate Sequence').item.json.research?.person?.headline || '' }}\n\nQualification evidence:\n- Warnings: {{ JSON.stringify($('Twain Generate Sequence').item.json.warnings) }}\n- Thesis: {{ $('Twain Generate Sequence').item.json.thesis?.markdown || 'No thesis returned.' }}\n- Research insights:\n  - {{ (($('Twain Generate Sequence').item.json.research?.insights || []).map((item) => item?.insight).filter(Boolean).join('\\n  - ')) || 'No additional insights returned.' }}",
        "hasOutputParser": true,
        "options": {
          "systemMessage": "You qualify whether a PROSPECT company is an ICP fit for a B2B product. Be decisive and evidence-based.\n\n=== THE PRODUCT YOU QUALIFY FOR (edit this section for your company) ===\nProduct: Twain — https://twain.ai\nWhat it does: Twain is an AI sales-research-and-writing platform that turns real-time research into personalized, on-brand B2B outreach (cold email + LinkedIn), with native syncs to tools like Clay and HubSpot. If a web-search tool is connected, search the web (including the product URL) to confirm the current positioning, since it changes often.\n\n=== JOB-TO-BE-DONE THE PRODUCT SERVES ===\n\"When my top-of-funnel outreach hits a performance ceiling because the copy is too generic for high-value accounts, I want an automated system for highly personalized, relevant, scalable communications that natively syncs with my stack (e.g. Clay, HubSpot) so I can launch signal-based engagement and prove the ROI of Demand Gen — without manual prospecting or risking brand reputation through hallucinated AI.\"\nThe closer the prospect resembles a company that would have this job, the better the fit.\n\n=== ICP TIERS (edit for your company) ===\nTIER 1 (strongest) — ALL of: >=100 employees; HQ in Europe or North America; raised at least Series B; the individual prospect works in Demand Generation, Growth, GTM, or RevOps. Reference Tier-1 customers (look for resemblance): ashbyhq.com, xentral.com, everstage.com, graviteesource.com, rechargeapps.com, factorialhr.co, tracksuit.com.\nTIER 2 (good) — ALL of: >=11 and <5000 employees; a genuine B2B company with a B2B offering; at least 1 year old; NOT a lead-generation agency, recruiting agency, freelancer, or marketing agency. Reference Tier-2 customers: vumo.ai, nory.ai, getnnuvo.com, cotiss.com.\nNO FIT — fails the Tier-2 bar, or is a disqualified type (lead-gen/recruiting/marketing agency, freelancer, B2C-only, <1 year old, >5000 employees, or clearly off-ICP).\n\n=== HOW TO JUDGE ===\n1. Understand the product (above; use a connected web-search tool if available).\n2. Understand the prospect from the research provided in the user message (company size, description, region, funding/maturity, B2B model, the individual's role) plus the Thesis, research insights, and warnings; if a web-search tool is connected, also look up the prospect's website to confirm.\n3. Compare against the tiers and reference customers; assess the job-to-be-done fit.\n4. Pick the single best tier. If a hard Tier-1 criterion is unknown, do not assume it — fall to Tier 2 if Tier-2 criteria are met, else NO FIT.\n\nReturn ONLY the structured object: analysis (comprehensive reasoning citing the strongest concrete signals — size, region, funding, role, B2B model, resemblance to reference customers, JTBD match); summary (1-2 sentences); tier (exactly \"TIER 1\", \"TIER 2\", or \"NO FIT\")."
        }
      }
    },
    {
      "id": "0f7239d7-3799-4f65-a99c-9c60271da518",
      "name": "Claude Sonnet 4.6",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "typeVersion": 1.5,
      "position": [4324, 740],
      "parameters": {
        "model": {
          "__rl": true,
          "value": "claude-sonnet-4-6",
          "mode": "id",
          "cachedResultName": "Claude Sonnet 4.6"
        },
        "options": {
          "temperature": 0.1
        }
      }
    },
    {
      "id": "0248db4c-fdf0-477f-8d2d-9ecfd931dd06",
      "name": "Fit Verdict Schema",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [4532, 740],
      "parameters": {
        "jsonSchemaExample": "{ \"analysis\": \"RevOps leader at a Series-C B2B SaaS (~400 FTE, US) running outbound — closely resembles Everstage/Ashby; strong JTBD match.\", \"summary\": \"Strong Tier-1 fit: large NA B2B SaaS with a RevOps buyer and a clear personalization-at-scale need.\", \"tier\": \"TIER 1\" }"
      }
    },
    {
      "id": "c5a14b42-2b83-41bf-98f6-e4151bd5d5b0",
      "name": "Is High Fit?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [4800, 520],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "leftValue": "={{ $json.output?.tier ?? $json.tier ?? '' }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "rightValue": "TIER 1",
              "id": "4300371c-0841-48d9-8392-39f6f9dd308c"
            },
            {
              "leftValue": "={{ $json.output?.tier ?? $json.tier ?? '' }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "rightValue": "TIER 2",
              "id": "cef49b99-1cd3-4b4e-991e-40d38552f542"
            }
          ],
          "combinator": "or"
        },
        "options": {}
      }
    },
    {
      "id": "97d7a1ee-16d0-4b46-8c99-5a95e0459b2a",
      "name": "High Fit (A/B)",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [5180, 520],
      "executeOnce": false,
      "parameters": {}
    },
    {
      "id": "b3aaea39-c00c-4445-8e32-6f31f0d3ae22",
      "name": "Slack: Twain error",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.5,
      "position": [4040, 740],
      "onError": "continueRegularOutput",
      "parameters": {
        "authentication": "accessToken",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "n8n-workflows"
        },
        "text": "=:warning:  *Twain research failed*\n\n*{{ $('Normalize Signup').item.json.displayName || $('Normalize Signup').item.json.email || 'Unknown signup' }}*\n:email:  {{ $('Normalize Signup').item.json.email || 'No email provided' }}\n\n*Error:*\n{{ ((String($json.error?.message ?? $json.message ?? '').replace(/^\\s*\\d{3}\\s*-\\s*/, '').trim()).includes('\"message\":\"') ? (((String($json.error?.message ?? $json.message ?? '').replace(/^\\s*\\d{3}\\s*-\\s*/, '').trim()).split('\"message\":\"')[1] || '').split('\"')[0].replace(/\\\\/g, '')) : String($json.error?.message ?? $json.message ?? '').replace(/^\\s*\\d{3}\\s*-\\s*/, '').trim()) || 'Unknown error' }}\n\n<https://www.twain.ai/w|Open Twain workspace>",
        "otherOptions": {
          "includeLinkToWorkflow": false,
          "unfurl_links": false,
          "unfurl_media": false
        }
      }
    },
    {
      "id": "69d4ba16-d74b-462b-bede-17d1c697c093",
      "name": "Slack: High-fit alert",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.5,
      "position": [5560, 300],
      "onError": "continueRegularOutput",
      "notesInFlow": false,
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "n8n-workflows",
          "mode": "name"
        },
        "text": "=:fire:  *High-fit signup*\n\n*{{ $('Twain Generate Sequence').item.json.research?.person?.name || $('Normalize Signup').item.json.displayName || $('Normalize Signup').item.json.email }}*\n{{ [$('Twain Generate Sequence').item.json.research?.person?.title, $('Twain Generate Sequence').item.json.research?.company?.name].filter(Boolean).join(' @ ') }}\n\n:email:  {{ $('Normalize Signup').item.json.email }}\n{{ $('Twain Generate Sequence').item.json.research?.person?.linkedin_profile_url ? ':link:  <' + $('Twain Generate Sequence').item.json.research.person.linkedin_profile_url + '|LinkedIn profile>' : '' }}\n{{ $('Twain Generate Sequence').item.json.warnings?.has_warnings ? ':warning:  *Warnings:* ' + (($('Twain Generate Sequence').item.json.warnings.categories) || []).join(', ') + ($('Twain Generate Sequence').item.json.warnings.summary ? '  —  ' + $('Twain Generate Sequence').item.json.warnings.summary : '') : '' }}\n\n*Tier:* {{ $('Stage B — Fit Judge').item.json.output?.tier ?? $('Stage B — Fit Judge').item.json.tier ?? 'Unknown' }}\n*Fit:* {{ $('Stage B — Fit Judge').item.json.output?.summary ?? $('Stage B — Fit Judge').item.json.summary ?? 'No fit summary returned.' }}\n\n*Thesis:*\n{{ $('Twain Generate Sequence').item.json.thesis?.markdown || 'No Thesis returned.' }}\n\n<{{ $('Twain Generate Sequence').item.json.link }}|Open lead in Twain>",
        "otherOptions": {
          "includeLinkToWorkflow": false,
          "unfurl_links": false,
          "unfurl_media": false
        },
        "authentication": "accessToken"
      }
    },
    {
      "id": "63d3862e-16a6-492d-b3f3-cb6a28cb8e2a",
      "name": "HubSpot: Upsert Contact",
      "type": "n8n-nodes-base.hubspot",
      "typeVersion": 2.2,
      "position": [5560, 520],
      "onError": "continueRegularOutput",
      "parameters": {
        "authentication": "oAuth2",
        "email": "={{ $('Normalize Signup').item.json.email }}",
        "additionalFields": {
          "companyName": "={{ $('Twain Generate Sequence').item.json.research.company.name }}",
          "customPropertiesUi": {
            "customPropertiesValues": [
              {
                "property": "twain_fit_tier",
                "value": "={{ $('Stage B — Fit Judge').item.json.output?.tier ?? $('Stage B — Fit Judge').item.json.tier ?? '' }}"
              },
              {
                "property": "twain_fit_reason",
                "value": "={{ $('Stage B — Fit Judge').item.json.output?.summary ?? $('Stage B — Fit Judge').item.json.summary ?? '' }}"
              }
            ]
          },
          "jobTitle": "={{ $('Twain Generate Sequence').item.json.research.person.title }}",
          "linkedinUrl": "={{ $('Twain Generate Sequence').item.json.research.person.linkedin_profile_url }}",
          "websiteUrl": "={{ $('Twain Generate Sequence').item.json.research.company.website }}"
        },
        "options": {}
      }
    },
    {
      "id": "f47d6093-1629-4df4-83b8-a4570204acb7",
      "name": "Email Owning AE",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [5560, 740],
      "onError": "continueRegularOutput",
      "parameters": {
        "sendTo": "REPLACE_WITH_AE_EMAIL@yourcompany.com",
        "subject": "=High-fit signup: {{ $('Twain Generate Sequence').item.json.research.person.name }} ({{ $('Twain Generate Sequence').item.json.research.company.name }}) — {{ $('Stage B — Fit Judge').item.json.output?.tier ?? $('Stage B — Fit Judge').item.json.tier ?? 'Unknown' }}",
        "message": "=<p><strong>High-fit signup</strong></p><p><strong>Tier:</strong> {{ $('Stage B — Fit Judge').item.json.output?.tier ?? $('Stage B — Fit Judge').item.json.tier ?? 'Unknown' }}</p><p><strong>{{ $('Twain Generate Sequence').item.json.research.person.name }}</strong>{{ $('Twain Generate Sequence').item.json.research.person.title ? ' — ' + $('Twain Generate Sequence').item.json.research.person.title : '' }}{{ $('Twain Generate Sequence').item.json.research.company.name ? ' @ ' + $('Twain Generate Sequence').item.json.research.company.name : '' }}<br/>{{ $('Normalize Signup').item.json.email }}</p><p><strong>Fit summary:</strong> {{ $('Stage B — Fit Judge').item.json.output?.summary ?? $('Stage B — Fit Judge').item.json.summary ?? 'No fit summary returned.' }}</p><p><a href=\"{{ $('Twain Generate Sequence').item.json.link }}\">Open in Twain</a></p>",
        "options": {
          "appendAttribution": false
        }
      }
    },
    {
      "id": "c9efb69f-40a8-47d1-a5ea-5d0d3c29fa1c",
      "name": "Dedupe New Signups",
      "type": "n8n-nodes-base.removeDuplicates",
      "typeVersion": 2,
      "position": [1380, 520],
      "parameters": {
        "operation": "removeItemsSeenInPreviousExecutions",
        "dedupeValue": "={{ $json.uid }}",
        "options": {
          "scope": "node",
          "historySize": 100000
        }
      }
    },
    {
      "id": "40137c3e-58b3-4e19-b5a7-dd652841f51a",
      "name": "Schedule: BigQuery backfill",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [240, 690],
      "disabled": true,
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours"
            }
          ]
        }
      }
    },
    {
      "id": "de834cef-8461-456a-85b1-d13e50512155",
      "name": "BigQuery: New signups",
      "type": "n8n-nodes-base.googleBigQuery",
      "typeVersion": 2.1,
      "position": [620, 960],
      "disabled": true,
      "parameters": {
        "authentication": "serviceAccount",
        "projectId": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_GCP_PROJECT_ID"
        },
        "sqlQuery": "-- Backfill: new signups since last run. Alias columns to uid/email/displayName/signup_ts.\nSELECT uid, email, display_name AS displayName, signup_ts\nFROM `your_dataset.signups`\nWHERE signup_ts > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)\nORDER BY signup_ts",
        "options": {}
      }
    },
    {
      "id": "57cf8da0-6a06-4d71-b738-2a77db5dc5e3",
      "name": "Schedule: Firestore poll",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [240, 860],
      "disabled": true,
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 15
            }
          ]
        }
      }
    },
    {
      "id": "620d442a-5979-4807-b653-b866e197c1ec",
      "name": "Firestore: New customers",
      "type": "n8n-nodes-base.googleFirebaseCloudFirestore",
      "typeVersion": 1.1,
      "position": [620, 1180],
      "disabled": true,
      "parameters": {
        "authentication": "serviceAccount",
        "operation": "query",
        "projectId": "REPLACE_WITH_FIREBASE_PROJECT_ID",
        "query": "{\n  \"from\": [{ \"collectionId\": \"customers\" }],\n  \"orderBy\": [{ \"field\": { \"fieldPath\": \"created_at\" }, \"direction\": \"DESCENDING\" }],\n  \"limit\": 50\n}"
      }
    },
    {
      "id": "bc64fcb1-8681-4bfa-bb8c-3c36361ee5ad",
      "name": "Slack: Disqualified",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.5,
      "position": [4420, 740],
      "onError": "continueRegularOutput",
      "parameters": {
        "authentication": "accessToken",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "n8n-workflows"
        },
        "text": "=:no_entry:  *Signup disqualified at Stage A*\n\n*{{ $('Twain Generate Sequence').item.json.research?.person?.name || $('Normalize Signup').item.json.displayName || $('Normalize Signup').item.json.email }}*\n\n:email:  {{ $('Normalize Signup').item.json.email }}\n\n*Warnings:* `{{ (($('Twain Generate Sequence').item.json.warnings?.categories) || []).join(', ') || 'No warning categories returned' }}`\n{{ $('Twain Generate Sequence').item.json.warnings?.summary || '' }}\n\n<{{ $('Twain Generate Sequence').item.json.link }}|Open lead in Twain>",
        "otherOptions": {
          "includeLinkToWorkflow": false,
          "unfurl_links": false,
          "unfurl_media": false
        }
      }
    },
    {
      "id": "fe544299-13dc-4a0e-b658-d612eefec88e",
      "name": "Slack: Low fit (C)",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.5,
      "position": [5180, 740],
      "onError": "continueRegularOutput",
      "parameters": {
        "authentication": "accessToken",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "n8n-workflows"
        },
        "text": "=:heavy_multiplication_x:  *Not a fit (NO FIT)*\n\n*{{ $('Twain Generate Sequence').item.json.research?.person?.name || $('Normalize Signup').item.json.displayName || $('Normalize Signup').item.json.email }}*\n{{ [$('Twain Generate Sequence').item.json.research?.person?.title, $('Twain Generate Sequence').item.json.research?.company?.name].filter(Boolean).join(' @ ') }}\n\n:email:  {{ $('Normalize Signup').item.json.email }}\n{{ $('Twain Generate Sequence').item.json.research?.person?.linkedin_profile_url ? ':link:  <' + $('Twain Generate Sequence').item.json.research.person.linkedin_profile_url + '|LinkedIn profile>' : '' }}\n{{ $('Twain Generate Sequence').item.json.warnings?.has_warnings ? ':warning:  *Warnings:* ' + (($('Twain Generate Sequence').item.json.warnings.categories) || []).join(', ') + ($('Twain Generate Sequence').item.json.warnings.summary ? '  —  ' + $('Twain Generate Sequence').item.json.warnings.summary : '') : '' }}\n\n*Tier:* {{ $('Stage B — Fit Judge').item.json.output?.tier ?? $('Stage B — Fit Judge').item.json.tier ?? 'Unknown' }}\n*Fit:* {{ $('Stage B — Fit Judge').item.json.output?.summary ?? $('Stage B — Fit Judge').item.json.summary ?? 'No fit summary returned.' }}\n\n<{{ $('Twain Generate Sequence').item.json.link }}|Open lead in Twain>",
        "otherOptions": {
          "includeLinkToWorkflow": false,
          "unfurl_links": false,
          "unfurl_media": false
        }
      }
    },
    {
      "id": "ba507a33-13dc-4008-a4e8-2f918d80affa",
      "name": "Normalize Signup",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [1000, 520],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "uid",
              "name": "uid",
              "value": "={{ $json.body?.uid ?? $json.uid }}",
              "type": "string"
            },
            {
              "id": "email",
              "name": "email",
              "value": "={{ ($json.body?.email ?? $json.email ?? \"\").toLowerCase().trim() }}",
              "type": "string"
            },
            {
              "id": "displayName",
              "name": "displayName",
              "value": "={{ $json.body?.displayName ?? $json.displayName ?? \"\" }}",
              "type": "string"
            },
            {
              "id": "signup_ts",
              "name": "signup_ts",
              "value": "={{ $json.body?.signup_ts ?? $json.signup_ts ?? $now.toISO() }}",
              "type": "string"
            },
            {
              "id": "campaign_id",
              "name": "campaign_id",
              "value": "REPLACE_WITH_SIGNUP_TRIAGE_CAMPAIGN_ID",
              "type": "string"
            },
            {
              "id": "linkedin_url",
              "name": "linkedin_url",
              "value": "={{ ($json.body?.linkedin_url ?? $json.body?.linkedin_profile_url ?? $json.linkedin_url ?? $json.linkedin_profile_url ?? \"\").trim() }}",
              "type": "string"
            },
            {
              "id": "email_domain",
              "name": "email_domain",
              "value": "={{ ((($json.body?.email ?? $json.email ?? \"\").toLowerCase().trim()).split(\"@\")[1] ?? \"\").toLowerCase() }}",
              "type": "string"
            },
            {
              "id": "is_personal_email",
              "name": "is_personal_email",
              "value": "={{ [\"gmail.com\",\"googlemail.com\",\"outlook.com\",\"hotmail.com\",\"live.com\",\"msn.com\",\"yahoo.com\",\"ymail.com\",\"icloud.com\",\"me.com\",\"mac.com\",\"proton.me\",\"protonmail.com\",\"aol.com\",\"gmx.com\",\"gmx.net\",\"zoho.com\",\"pm.me\",\"mail.com\",\"yandex.com\"].includes(((($json.body?.email ?? $json.email ?? \"\").toLowerCase().trim()).split(\"@\")[1] ?? \"\").toLowerCase()) }}",
              "type": "boolean"
            },
            {
              "id": "has_linkedin_url",
              "name": "has_linkedin_url",
              "value": "={{ Boolean(($json.body?.linkedin_url ?? $json.body?.linkedin_profile_url ?? $json.linkedin_url ?? $json.linkedin_profile_url ?? \"\").trim()) }}",
              "type": "boolean"
            },
            {
              "id": "has_work_email",
              "name": "has_work_email",
              "value": "={{ Boolean((($json.body?.email ?? $json.email ?? \"\").toLowerCase().trim())) && ![\"gmail.com\",\"googlemail.com\",\"outlook.com\",\"hotmail.com\",\"live.com\",\"msn.com\",\"yahoo.com\",\"ymail.com\",\"icloud.com\",\"me.com\",\"mac.com\",\"proton.me\",\"protonmail.com\",\"aol.com\",\"gmx.com\",\"gmx.net\",\"zoho.com\",\"pm.me\",\"mail.com\",\"yandex.com\"].includes((((($json.body?.email ?? $json.email ?? \"\").toLowerCase().trim()).split(\"@\")[1] ?? \"\").toLowerCase())) }}",
              "type": "boolean"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "21178752-fe72-4012-a3cf-feb6390eef8d",
      "name": "Needs LinkedIn Lookup?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [1760, 520],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 3
          },
          "conditions": [
            {
              "id": "needs-li-1",
              "leftValue": "={{ !$('Normalize Signup').item.json.has_linkedin_url && Boolean($('Normalize Signup').item.json.email) }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      }
    },
    {
      "id": "23ac4183-d3c6-4d8e-8a22-77025934344b",
      "name": "LinkedIn Lookup — Findymail",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [2140, 300],
      "onError": "continueRegularOutput",
      "parameters": {
        "method": "POST",
        "url": "https://app.findymail.com/api/search/reverse-email",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { email: $('Normalize Signup').item.json.email, with_profile: true } }}",
        "options": {
          "timeout": 30000
        }
      }
    },
    {
      "id": "2e8c96f2-7a78-4849-90ae-43c8115954e5",
      "name": "Apply Resolved LinkedIn",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [2520, 300],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Resolve LinkedIn URL from the provider response.\n// Adjust the field below to match YOUR provider's response shape.\nconst resp = $json || {};\nconst linkedin_url = resp.linkedin_url ?? resp.linkedin_profile_url ?? resp.url ?? resp.contact?.linkedin_url ?? resp.profile?.linkedin_url ?? resp.person?.linkedin_url ?? resp.data?.linkedin_url ?? resp.response?.linkedin_url ?? \"\";\n\n// Carry forward all normalized fields, overriding only the LinkedIn ones.\nconst n = $('Normalize Signup').item.json;\nreturn {\n  ...n,\n  linkedin_url: linkedin_url,\n  has_linkedin_url: Boolean(linkedin_url)\n};\n"
      }
    },
    {
      "id": "99a2b760-f06b-4e79-a21a-9efcf97f1a31",
      "name": "Resolved Contact",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [2900, 520],
      "parameters": {}
    },
    {
      "id": "f5f07945-95b6-4181-b5c5-9be06207d7e4",
      "name": "LinkedIn Lookup — Apollo",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [2140, 80],
      "disabled": true,
      "onError": "continueRegularOutput",
      "parameters": {
        "method": "POST",
        "url": "https://api.apollo.io/api/v1/people/match",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { email: $('Normalize Signup').item.json.email } }}",
        "options": {
          "timeout": 30000
        }
      }
    },
    {
      "id": "5a14c68c-da13-4546-abdf-d3d4f988ea88",
      "name": "LinkedIn Lookup — People Data Labs",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [2140, -140],
      "disabled": true,
      "onError": "continueRegularOutput",
      "parameters": {
        "url": "=https://api.peopledatalabs.com/v5/person/enrich?email={{ encodeURIComponent($('Normalize Signup').item.json.email) }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 30000
        }
      }
    },
    {
      "id": "627f1cca-fbda-4f5f-af51-f47dd25c9a41",
      "name": "LinkedIn Lookup — Proxycurl",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [2140, -360],
      "disabled": true,
      "onError": "continueRegularOutput",
      "parameters": {
        "url": "=https://nubela.co/proxycurl/api/linkedin/profile/resolve/email?email={{ encodeURIComponent($('Normalize Signup').item.json.email) }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 30000
        }
      }
    }
  ],
  "connections": {
    "Signup Webhook": {
      "main": [
        [
          {
            "node": "Normalize Signup",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Twain Generate Sequence": {
      "main": [
        [
          {
            "node": "Stage A — Hard Disqualify?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: Twain error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Stage A — Hard Disqualify?": {
      "main": [
        [
          {
            "node": "Slack: Disqualified",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Stage B — Fit Judge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Stage B — Fit Judge": {
      "main": [
        [
          {
            "node": "Is High Fit?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Sonnet 4.6": {
      "ai_languageModel": [
        [
          {
            "node": "Stage B — Fit Judge",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Fit Verdict Schema": {
      "ai_outputParser": [
        [
          {
            "node": "Stage B — Fit Judge",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "High Fit (A/B)": {
      "main": [
        [
          {
            "node": "Slack: High-fit alert",
            "type": "main",
            "index": 0
          },
          {
            "node": "HubSpot: Upsert Contact",
            "type": "main",
            "index": 0
          },
          {
            "node": "Email Owning AE",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule: BigQuery backfill": {
      "main": [
        [
          {
            "node": "BigQuery: New signups",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "BigQuery: New signups": {
      "main": [
        [
          {
            "node": "Normalize Signup",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule: Firestore poll": {
      "main": [
        [
          {
            "node": "Firestore: New customers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Firestore: New customers": {
      "main": [
        [
          {
            "node": "Normalize Signup",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Signup": {
      "main": [
        [
          {
            "node": "Dedupe New Signups",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs LinkedIn Lookup?": {
      "main": [
        [
          {
            "node": "LinkedIn Lookup — Findymail",
            "type": "main",
            "index": 0
          },
          {
            "node": "LinkedIn Lookup — Apollo",
            "type": "main",
            "index": 0
          },
          {
            "node": "LinkedIn Lookup — People Data Labs",
            "type": "main",
            "index": 0
          },
          {
            "node": "LinkedIn Lookup — Proxycurl",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Resolved Contact",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Apply Resolved LinkedIn": {
      "main": [
        [
          {
            "node": "Resolved Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is High Fit?": {
      "main": [
        [
          {
            "node": "High Fit (A/B)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: Low fit (C)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LinkedIn Lookup — Findymail": {
      "main": [
        [
          {
            "node": "Apply Resolved LinkedIn",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LinkedIn Lookup — Apollo": {
      "main": [
        [
          {
            "node": "Apply Resolved LinkedIn",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LinkedIn Lookup — People Data Labs": {
      "main": [
        [
          {
            "node": "Apply Resolved LinkedIn",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LinkedIn Lookup — Proxycurl": {
      "main": [
        [
          {
            "node": "Apply Resolved LinkedIn",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dedupe New Signups": {
      "main": [
        [
          {
            "node": "Needs LinkedIn Lookup?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Resolved Contact": {
      "main": [
        [
          {
            "node": "Has usable contact?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has usable contact?": {
      "main": [
        [
          {
            "node": "Twain Generate Sequence",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: Personal email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

After import, point it at your environment: add the Twain API key as an n8n Header Auth credential, reconnect Slack, HubSpot, Gmail, and Anthropic, and replace REPLACE_WITH_SIGNUP_TRIAGE_CAMPAIGN_ID in Normalize Signup with the campaign you created in Twain. The Agent and campaign you set up in Twain are what teach the workflow your ICP — get those right and the tiers come out right. Campaign Brief covers shaping the campaign, and Deep Research covers the research layer this workflow runs on.

Who this is for

This pays off most when the top of the funnel is noisy and a fast human response is worth real money.

  • PLG with a sales-assist motion. Most signups are not buyers, but the ones that are should never sit unnoticed until the next CRM review.
  • Founder-led sales. The founder wants the high-signal accounts in Slack the moment they sign up, with enough context to reply well right away.
  • Demand-Gen and RevOps teams. You finally get fit scoring and reasoning as native CRM fields you can route, segment, and report on — without paying the manual-qualification tax to get them.
  • Multi-source lead capture. App signups, landing pages, webinars, and backfills all get the same first-pass qualification through one workflow.

Once a qualified signup books time, the demo-booking prep workflow picks up the next job: research the meeting and prepare the rep. If the trigger is an outbound signal rather than an inbound signup, use the LinkedIn signal outreach workflow.

If your motion is pure self-serve and nobody intends to follow up, this is overkill. But if your team asks "which of today's signups should we jump on?", this researches, classifies, and routes the answer for you.