Dec 2022 – Feb 2023
SWELast edited
End-to-End Client Acquisition System
This project engineered the core operations infrastructure for BlinkWeb, a CRM and client-acquisition engine that integrated the Norwegian Brønnøysund Register and three phone registries (1881, 180, Gule Sider) via REST APIs to automate data enrichment and contact verification.
Geospatial lead-matching algorithms reduced false positives in candidate identification, and automated outreach plus real-time contract generation eliminated manual handoffs end-to-end.
The system saved approximately 450 hours per year and reduced third-party API costs by roughly $10,000, serving as core infrastructure behind BlinkWeb's operations in 2024.
Affiliation
BlinkWeb
Keywords
- CRM
- Data Enrichment
- Data Validation
- Python
- REST APIs
- MySQL
- Web Scraping
▸ Deepdive
Introduction
I started my company right after high school in June 2022. The hard part was never building the product, it was finding clients to build for. The daily routine was to manually pull newly registered companies from Brønnøysundsregisteret, cross-reference each one against 1881, Gule Sider, and 180 to recover a phone number, strip out anyone on the do-not-call registry, and only then assemble a call list. Repeated every morning, the workflow consumed several hours before the first dial. This project automates that pipeline end-to-end.
Problem Definition
The acquisition funnel for a B2B service business in Norway starts with a single question: which companies were registered yesterday, and how do I reach them legally today? Each candidate company is characterised by a tuple , organisation number, legal name, industry code, phone number, and do-not-call status. Brønnøysundsregisteret exposes the first four fields (with frequently missing), while the public directories 1881, Gule Sider, and 180 expose but not the registry metadata. The do-not-call flag is published separately by Brønnøysundsregisteret as the Reservasjonsregisteret. A usable lead is any with all five fields resolved and . The system must produce, every day and without human intervention, the set
where is the set of companies registered in the previous 24 hours.
Approach

The pipeline runs as a daily scheduled job and decomposes into four stages: registry ingestion, phone-number enrichment, do-not-call filtering, and persistence.
Registry Ingestion
The first stage queries the open Brønnøysundsregisteret REST API for all units registered in the last 24 hours, filtered by the industry codes relevant to the service offering. Each response is paginated JSON, and the relevant fields are unpacked into an in-memory candidate set . The registry phone number is present for roughly a third of new companies; the remaining two thirds carry and have to be enriched downstream.
Phone-Number Enrichment
For every with , a Selenium-driven Google Chrome session searches 1881, Gule Sider, and 180 in turn using and as the query keys. Each directory returns at most one candidate number per source, giving a per-company vector . The enriched number is resolved by majority vote with the registry value as a tiebreaker:
When all four sources disagree, the registry value wins if present; otherwise the highest-confidence directory hit is taken. Scrapes run with randomised user agents and request spacing to stay within each directory’s rate limits.
Do-Not-Call Filtering
The Reservasjonsregisteret is queried in batch for every in the enriched set, returning the binary flag . Companies with are dropped before persistence; calling them is illegal under Norwegian marketing law (markedsføringsloven § 12), so the filter is enforced at the pipeline boundary rather than at the call-centre boundary, eliminating any chance of a reserved number leaking into a call list.
Persistence
The surviving leads are written to a MySQL database with a uniqueness constraint on , so reruns are idempotent and the table doubles as a deduplication layer across days. Each row carries a timestamp, the source vector , and the resolved , which lets the CRM front-end surface the call list and lets the enrichment stage be audited after the fact when a number turns out to be wrong.
Results
The pipeline replaced a multi-hour daily manual workflow with a scheduled job that runs unattended and produces a deduplicated, legally compliant call list every morning.
- Roughly 450 hours per year reclaimed from manual list-building, redirected into actual client conversations and product work.
- Approximately per year in avoided costs, measured against the two realistic alternatives: hiring a part-time researcher to do the same work by hand, or paying for the commercial 1881 REST API at the volume the business required.
Future Work
The current enrichment stage is brittle in the way every Selenium-based scrape is brittle: the moment 1881, Gule Sider, or 180 reshuffles their DOM, the corresponding extractor breaks silently and loses a coordinate. A natural next step is to wrap each directory extractor in a schema check that compares the parsed response against a stored fingerprint and raises before a malformed row reaches the voting stage, so a layout change degrades the pipeline gracefully instead of poisoning the lead list.
A more fundamental redesign is to replace the majority-vote resolver with a learned model over that predicts and outputs both a resolved number and a calibrated confidence. The current voter treats every source as equally reliable, but in practice 1881 dominates on established businesses while the registry value dominates on companies less than a week old, and a model conditioned on company age and industry would capture that structure directly. Confidence scores would also let the call list be sorted by reachability rather than by registration date, which is the single largest remaining lever on conversion rate from the top of the funnel.