What AI Lead Scoring Actually Is
Strip away the vendor pitch and AI lead scoring is a pattern-matching problem dressed up as a sales tool. You feed it data about a lead (firmographic, behavioral, demographic) and it returns a number or category that estimates how likely that lead is to convert, respond, or hit some other revenue-related event. The “AI” part is usually a gradient boosted model, a logistic regression, or increasingly a language model that reasons over unstructured notes from sales calls. None of that is magic. What makes it useful is that the same scoring logic can be applied to thousands of leads in milliseconds and updated as new data comes in.
The core technical pipeline looks like this. Features get extracted from a CRM or product database, those features get passed to a model, the model outputs a score, and the score lands somewhere a sales rep or automated workflow can act on. The interesting design choices are around the feature engineering and the integration layer, not the model itself. A logistic regression on good features will often beat a neural network on bad ones.
The orchestration side, which is where n8n fits in, handles everything around the model. It pulls new leads from your CRM via webhook or scheduled poll, transforms the data into the format your model expects, calls the scoring service, writes the score back to the CRM, and triggers downstream actions like a Slack notification or a HubSpot workflow. The Python side handles the actual scoring service, which is usually a small FastAPI app exposing a /score endpoint. Together this is a clean separation of concerns. n8n owns the workflow, Python owns the inference.
Setup and Authentication
For the Python side, you need a working Python environment (3.10 or later is the current safe range for the libraries we will use) and pip. Create a virtual environment and install the core dependencies.
fastapi uvicorn scikit-learn pandas pydantic
You will also want joblib for serializing the trained model and python-dotenv for managing secrets. A typical requirements file for a small scoring service weighs in at six to ten packages. Keep it lean, every extra dependency is a future maintenance cost.
For n8n, the current self-hosted version installs cleanly on a small VPS with Docker. Pull the image, expose port 5678, and set the encryption key as an environment variable. If you are using n8n.cloud, sign up and grab an API key from the settings panel. The same API key gets used for both the REST API and the webhook authentication header. Treat it like any other service token and rotate it on the same cadence you rotate your other credentials.
Authentication between n8n and your Python scoring service depends on your threat model. For internal services on a private network, a shared API key in a header is usually enough. For anything exposed to the public internet, put a reverse proxy with basic auth in front, or add a proper JWT layer. The most common production mistake is leaving a FastAPI service wide open on port 8000 with no auth and a debug flag still set to true. Turn debug off and require a key before you expose anything beyond localhost.
For the CRM side, you will need API credentials. HubSpot uses a private app access token. Salesforce uses OAuth with a connected app. Pipedrive uses an API token tied to a user account. Whichever you pick, generate a token with the minimum scopes required to read leads and write a custom property, no more.
First Working Example
Let us build a minimal but real version. The Python scoring service is a FastAPI app with one endpoint. The training script fits a logistic regression on a small synthetic dataset and saves the model to disk. The n8n workflow polls a webhook source, sends lead data to the service, and writes the score back.
The training script reads a CSV with columns like company_size, industry, email_opens, website_visits, demo_requested, and converted. The target column is converted, a binary flag. Fit the model, evaluate it on a holdout set, and save it. You should expect an AUC somewhere in the 0.7 to 0.9 range depending on how realistic your synthetic data is. Real production scores vary widely, anything above 0.75 on a meaningful test set is usually a sign the features are doing real work.
The scoring service loads the saved model at startup and exposes a single POST /score endpoint. The request body is a JSON object with the feature values. The response is a JSON object with the score and a probability. Keep the request schema narrow and versioned. The most common breakage in production is the CRM sending a field the model never saw during training, so validate inputs against a Pydantic schema and return a clear 422 error.
The n8n workflow is straightforward. A Cron trigger fires every fifteen minutes. An HTTP Request node calls your CRM API to fetch new or updated leads since the last run. A Code node reshapes the data into the feature vector. Another HTTP Request node POSTs that vector to your Python service. A final HTTP Request node writes the returned score back to a custom field on the lead. Add a Slack node at the end for high-score leads so a rep gets pinged in real time.
To run it locally, start the FastAPI service with uvicorn, open n8n in your browser, import the workflow JSON, and execute the workflow manually the first time. Watch the execution pane for failed nodes. The most common failure on a first run is a 401 from the CRM, which almost always means the token is wrong or the scopes are missing.
Key Settings That Matter
The dials most people ignore are not in the model, they are in the surrounding pipeline.
First, the score threshold. A score is only useful if you set a threshold for action. Above X, route to a senior rep. Between X and Y, drop into a nurture sequence. Below Y, disqualify. Without thresholds, scores are decoration. Pick a starting threshold by sorting scored leads by predicted probability and finding the natural break where conversion rates shift, then revisit it every quarter.
Second, the recency weighting. A lead that visited your pricing page yesterday is more interesting than one who did so three months ago. Decay behavioral features with an exponential time weight before feeding them into the model, or include recency as its own feature. A simple half-life of fourteen days works well for most B2B funnels, but the right number depends on your sales cycle length.
Third, the missing-value policy. Real leads are messy. Half of them will not have a company size, a third will not have a job title, most will have incomplete behavioral history. Decide explicitly how the model handles missing values. Imputation is the usual answer for numeric fields, a separate “unknown” category for categoricals. The wrong answer is letting scikit-learn silently drop rows with NaN, which means half your leads never get scored.
Fourth, the model refresh cadence. Lead behavior drifts. A model trained on last year’s data underperforms this year. Retrain monthly at minimum for stable B2B funnels, weekly for high-velocity inbound flows. Track the score distribution over time, if the mean score on new leads shifts more than a few points week over week, your model is probably stale.
Fifth, the data contract between n8n and the Python service. Pin the schema in Pydantic and reject anything that does not match. Loose contracts are the single biggest source of silent production bugs in this kind of pipeline. A lead with a missing feature should fail