Skip to content
Sales & Marketing

Custom CRM

Lead capture by campaign, one owner per lead, and payment proof approved by somebody other than the person who collected it — for services businesses that buy their leads.

Scroll to zoom · drag to pan · F to resize

The dashboard. Leads counted by the status they actually sit in rather than by a stage somebody remembered to drag them into, above each rep measured against their own monthly target — and the achieved figures come from recorded payments, not from the person being measured.

Who it's for

Built for a specific kind of business

Services businesses that buy their leads — agencies, software houses and consultancies running paid campaigns into a sales team of five to fifty. The point where a spreadsheet stops working is usually when two reps ring the same lead, or when nobody can say which campaign the paying customers came from.

Business type
Digital agency, software house, IT services or consulting firm
Team size
5 to 50 people working leads
Where leads come from
Paid social and search campaigns, landing pages, referrals and inbound forms
Runs today on
A shared inbox, a WhatsApp group and a spreadsheet per rep
Decision maker
Founder, sales manager or head of growth

The problem

What this actually costs you today

In the words operators use, not the words software vendors use.

  • Two reps ring the same lead, and the client hears about it

    Without a single owner recorded against a lead, contact is a race. The embarrassing version is two people from the same company calling the same prospect within an hour, which reads to the prospect as disorganisation rather than enthusiasm.

  • Nobody can say which campaign produced the paying customers

    Ad platforms report the leads they delivered. They cannot report which of those leads eventually paid, because that happens weeks later in a different system. Without attribution carried all the way through to the payment, budget decisions are made on cost per lead rather than cost per customer.

  • A dead lead and an untouched lead look identical

    A lead nobody has called yet and a lead that was called, quoted and rejected both sit in the list looking the same. Without an explicit status and a reason, the pipeline is just an undifferentiated pile that grows until everyone stops reading it.

  • Payment collection happens on WhatsApp and lands nowhere

    A client sends a bank transfer screenshot to the rep's phone. The rep says it arrived. Nobody else sees the proof, nobody checks it against the bank, and the only record is in a chat thread on one person's handset.

  • The rep who takes the money is the rep who confirms it arrived

    When the person recording a payment is also the person approving it, the figures are only as reliable as the individual. That is not a statement about anyone's honesty — it is that there is no mechanism, so a mistake and a fabrication leave the same trace.

  • Targets live in a spreadsheet nobody opens mid-month

    A monthly number set in a meeting and reviewed at month end is not a target, it is a post-mortem. By the time the gap is visible there are no days left to close it.

What it does

Grouped by who uses it

Feature lists organised by technical module tell you nothing about whether the software fits your team. These are grouped by the person who lives in them.

Lead capture and campaign attribution

Marketing

Every lead records where it came from, so the question that actually matters — which campaign produced customers, not which produced leads — stays answerable.

  • A Page per capture point — a Facebook lead ad, a Google Ads landing page, an Instagram bio link, the pricing form
  • Source recorded separately from Page — Facebook Ads, Google Ads, Website, Referral, LinkedIn, Cold Outreach
  • Service Type on every lead, so demand by service line is visible
  • Name, phone, email and free-text remarks
  • Pages managed in the app, so a new campaign needs no developer
  • Filter the whole queue by page, assigned user, status, name, phone or lead id

Ownership and the lead lifecycle

Sales rep

A lead has exactly one owner and exactly one status, and the status decides which actions are even offered.

  • Statuses — New, Assigned, Paid, Closed, Lost, Invalid, Canceled
  • Assign To Me, so taking ownership is one click and is recorded
  • Lost, Invalid and Cancel available on a new lead, or on one already assigned to you
  • Edit restricted to a new lead, or one assigned to you
  • Actions disappear once a lead reaches a terminal status — a Closed lead offers only Details
  • Every action takes a comment, which becomes part of the lead's history

Lead history

Sales manager

Each lead carries its own chronological record, so what happened is a matter of record rather than recollection.

  • Every status change recorded with the message that describes it
  • The comment the rep left at the time, kept alongside
  • Who performed the action, and exactly when
  • Visible on the lead itself, not buried in a global log

Payments and proof

Finance

Payments hang off the lead that produced them, carry their evidence, and are approved by somebody other than the person who entered them.

  • Payment recorded against the lead, so revenue inherits the lead's campaign attribution
  • Amount and currency — USD, EUR or PKR
  • Payment type and the account it landed in
  • Up to three image attachments per payment, for transfer slips and receipts
  • Approve and Reject, restricted to Admins — the rep who recorded it cannot approve it
  • A pending payment can be withdrawn; an approved one cannot

Targets

Sales manager

A monthly number per person, set in advance and dated, so history stays intact when the number changes.

  • Target amount per user, with an effective-from date
  • A new target is a new dated row rather than an edit to the old one
  • Created-by and created-at on every target
  • Reps see their own target; managers see everyone's
  • Set, edited and removed by Admins only

Achievements

Sales manager

The same number, measured, month by month — computed from payments rather than typed in by the person being measured.

  • Target, achieved and achievement percent per user per month
  • Achieved figures derived from recorded payments, not self-reported
  • Month-by-month history for every rep
  • Reps see their own row; managers see and filter the whole team
  • A dashboard leaderboard ordered by achievement percent

Users and access

Admin

Three roles, deliberately coarse. The distinctions that matter are between what a rep may do to their own leads and what only an Admin may do to money and to configuration.

  • SuperAdmin, Admin and User as the three roles
  • Accounts activated and deactivated rather than deleted
  • Only a SuperAdmin reaches the user screen at all
  • A new account can be created as Admin or User — never as another SuperAdmin
  • Reps are scoped to their own targets and achievements automatically

Screenshots

The actual interface

Every screenshot uses generated demo data. No real customer, student or member record appears anywhere on this site.

  • The leads queue. Look at the action buttons rather than the rows — a New lead offers Edit, Assign To Me, Lost, Invalid and Cancel; a Paid lead offers Payments; a Closed or Invalid lead offers nothing but Details. The status decides what can be done next, not the person looking at it.

  • A lead and its history. The history is the point — every status change carries the message that describes it, the comment the rep left at the time, and who did it. "I called them last week" stops being a claim.

  • Creating a lead. Source, Service Type and Page are three separate fields on purpose — the channel, what they want, and the exact ad or form they arrived through. Collapsing them into one is what makes attribution useless later.

  • Payments against one lead. Each row carries the account it landed in, its attachments, and who entered it — and the Approve and Reject buttons only appear for an Admin, on a payment still marked New.

  • Recording a payment. Three attachment slots, because the evidence for one payment is routinely a transfer screenshot, a bank slip and a receipt — and a payment whose proof lives in a WhatsApp thread is not evidence at all.

  • Monthly targets, one dated row per person per month. Raising somebody's number writes a new row with a new effective-from date rather than overwriting the old one, so last quarter's performance is still measured against last quarter's target.

  • Achievement against target, by month and by rep. The achieved column is computed from payments actually recorded, which is the only version of this table anyone will trust.

  • The capture points, as data. Launching a campaign means adding a row here, not a deployment — and because every lead references one, "which ad is actually producing customers" is a filter rather than a project.

  • The user list, reachable by a SuperAdmin and nobody else. Accounts are deactivated rather than deleted, so the leads and payments a departed rep created keep pointing at a real person.

  • Creating an account. The role list offers Admin and User and not SuperAdmin — the one role that can reach this screen cannot be handed out from it.

Scroll to zoom · drag to pan · ←/→ to browse · F to resize

Roles & permissions

Who can do what

Publishing this table is unusual. It is also the fastest way to show that the system was designed for a real deployment rather than for a demo.

Three roles, and the boundaries that matter are narrower than the role names suggest. Two of them are worth naming up front: a rep can record a payment but cannot approve one, and a rep's view of targets and achievements is filtered to themselves rather than merely uncluttered.

Capabilities by role. Each row is a capability; each column is a role.
Capability SuperAdmin Everything Admin Leads, money, setup User Sales rep
SuperAdmin: View Admin: View User: Own
SuperAdmin: View Admin: View User: View
SuperAdmin: Edit Admin: Edit User: Edit
SuperAdmin: Own Admin: Own User: Own
SuperAdmin: Edit Admin: Edit User: Edit
SuperAdmin: Own Admin: Own User: Own
SuperAdmin: View Admin: View User: Own
SuperAdmin: Edit Admin: Edit User: Edit
SuperAdmin: Full Admin: Full User: —
SuperAdmin: Edit Admin: Edit User: Edit
SuperAdmin: View Admin: View User: Own
SuperAdmin: Full Admin: Full User: —
SuperAdmin: View Admin: View User: Own
SuperAdmin: Full Admin: Full User: —
SuperAdmin: Full Admin: — User: —
  • Full: Create, edit and remove
  • Edit: Create and update, with the limits noted on each row
  • View: Read only
  • Own: Only their own records — leads assigned to them, or their own figures
  • No access

Demo access and customisation

See it running

A demo environment with seeded data is available on request during a call. Every lead name, phone number, email address and payment in it — and in every screenshot above — is generated. No real customer data appears anywhere on this page.

Deploy as-is, or fit it to your process

As-is
1 to 2 weeks
Customised
6 to 10 weeks

Sources, service types, capture pages, users and targets are all data and need no development. What usually does need work is the lead status set, the approval chain around payments, and pulling leads in automatically from the ad platforms rather than entering them — which is the first thing most teams ask for once the rest is running.

FAQ

CRM — questions we get asked

  • Is this a pipeline CRM with deal stages and forecasting?

    No, and it is worth being clear about it. There is no kanban board, no weighted forecast and no per-stage probability. A lead moves through a small set of explicit statuses, one owner at a time, and the measurement is a monthly target against payments actually collected. That fits a services business working inbound campaign leads on a short cycle. If you run long, multi-stakeholder enterprise deals over many months, this is the wrong shape and we would tell you so rather than sell it to you.

  • Can leads come in automatically from our ad campaigns?

    As it stands, leads are entered into the system and tagged with the Page they came through. Automatic ingestion from Facebook lead ads, Google Ads or a landing-page webhook is a customisation, and it is the most commonly requested one. The data model is already right for it — a Page exists per capture point, so ingestion is a matter of wiring each source to its Page.

  • Why can a rep record a payment but not approve it?

    Because a control that one person can complete end to end is not a control. A rep records what the client sent, with the transfer slip attached; an Admin checks it against the bank and approves or rejects it. Until that happens the payment sits as New and does not count towards anyone's achievement. It costs one extra click and it is the difference between a revenue figure you can rely on and one you hope is right.

  • What stops two reps working the same lead?

    A lead has one assigned user at a time. Taking a lead is an explicit action — Assign To Me — and it is written to that lead's history with the time and the person. Editing and the closing actions are restricted to a lead that is either still untouched or already yours, so the second rep finds the controls simply are not there.

  • Can a rep see everyone else's numbers?

    No. The targets and achievements screens filter themselves to the signed-in rep, and the organisation-wide total on the dashboard is shown only to Admins. A manager sees the whole team and can filter to any individual.

  • Who owns the data?

    Your data is yours, without qualification — and the deployment is yours to place. Run it on your own server, or on ours, whichever your policy requires.

Under the hood — for your IT team

Technical architecture

How it is built

An ASP.NET Core API with a React single-page front end, served from IIS. The two ideas that shape the product are that a lead has exactly one owner and one status — with the status deciding which actions exist — and that recording money and confirming money are deliberately different people.

  1. Clients

    • Sales deskReact SPA, role-aware UI
    • Manager viewsSame app, team-wide figures
    • Capture pointsAd forms and landing pages
  2. Application

    • ASP.NET Core APIREST, hosted on IIS
    • Status + ownership gateWhat is legal, and for whom
    • Payment approvalRecorded and confirmed by different roles
  3. Measurement

    • Dated targetsNew row per change, never an overwrite
    • Achievement rollupComputed from approved payments
    • Campaign attributionPage and Source carried to the money
  4. Data

    • SQL ServerLeads, payments, targets, users
    • Lead historyAppend-only, one row per action
    • Payment attachmentsTransfer slips and receipts
A lead carries its campaign reference all the way to the payment, and the payment is approved by somebody other than the person who recorded it — so cost per customer is computable and the revenue figure has a second pair of eyes on it.
Stack, layer by layer
  • API ASP.NET Core (C#), REST

    The team's deepest language, and the role claims the front end reads come straight out of its identity model.

  • Database SQL Server

    Leads, payments and targets are a relational problem with money in it, where transactional integrity beats schema flexibility.

  • Front end React + TypeScript on the CoreUI admin framework

    An internal tool used all day by a small team is a place to spend effort on workflow rather than on a bespoke design system.

  • Auth JWT held in the browser, carrying identity and role

    The UI can hide what a role cannot do without a round trip, while the API still checks every request independently.

  • Attribution Page and Source recorded on the lead and inherited by its payments

    Cost per customer is only computable if the campaign reference survives all the way to the money.

  • Hosting IIS on Windows Server

    Most clients at this size already run Windows hosting, or want something they can point at and manage.

Data model highlights
  • Lead as the root record, carrying owner, status, Page, Source and Service Type
  • LeadHistory as an append-only row per action — message, comment, actor and timestamp
  • Page as the campaign or form a lead arrived through, managed in the app rather than in code
  • Payment belonging to a Lead, with currency, account, up to three attachments and its own New / Approved / Rejected status
  • Target as a dated row per user, so a change never rewrites the period it replaced
  • Achievement as target against achieved per user per month, computed from recorded payments
  • User with a role and an active flag — deactivated rather than deleted, so historical records keep a real author
Integrations
  • Lead capture from campaign and landing-page forms, recorded against the Page that produced them
  • Image attachments on payments for transfer slips and receipts
Deployment topology

Single-tenant — one deployment and one database per client, on IIS over SQL Server, on a small VM or an existing Windows host.

Notable engineering decisions

The trade-offs we made, and why

This is the section that separates a system somebody built from a system somebody screenshotted.

A status that decides what you can do, not just what you see

The usual way to build a lead list is to render every action on every row and check permissions when one is clicked. It is easier to write, and it produces a screen where half the buttons return an error.

Here the status is the gate. A lead sitting at New offers Edit, Assign To Me, Lost, Invalid and Cancel. Once it is Paid the closing actions are gone and Payments appears. Once it is Closed, Invalid or Canceled there is nothing but Details. The row does not show you something and then refuse it.

Two conditions combine to decide each action, and it is worth being precise because the combination is the whole design:

  • Status, which says what is legal for a lead in that state.
  • Ownership, which says whether you are the person it is legal for.

So Edit is offered on a lead that is still New — nobody owns it yet — or on one already assigned to you. The same pair governs Lost, Invalid and Cancel. The effect at a desk is that a rep opening a colleague’s lead sees a read-only record, not a set of controls that will reject them.

The cost of doing it this way is that the rules live in two places — the front end decides what to render, the API decides what to accept — and the two have to agree. That duplication is deliberate. The alternative, deciding only on the server, produces a screen full of buttons that fail, and reps stop trusting the interface within a week.

Recording money and confirming money are different jobs

A rep records a payment: amount, currency, which account it landed in, and up to three attachments, because the evidence for one payment in this market is routinely a transfer screenshot plus a bank slip plus a receipt.

That payment is created as New. It counts towards nothing. An Admin opens it, checks the attachments against the account, and either approves or rejects it. Only Admins see those two buttons, and only while the payment is still New.

This is the oldest control in bookkeeping and it is nearly always the thing a small CRM leaves out, because when the team is six people it feels like bureaucracy. It stops feeling like bureaucracy the first time a figure is disputed. The point is not that anyone is suspected — it is that without the separation, an honest mistake and a dishonest entry leave exactly the same trace, so neither can be ruled out.

One deliberate asymmetry: a payment still marked New can be withdrawn by whoever is looking at it, because a payment entered against the wrong lead should be easy to take back before it has been relied on. Once approved it is fixed. The reversible window closes at the moment somebody else has vouched for it.

Attribution has to survive all the way to the money

Ad platforms will tell you what a lead cost. They cannot tell you what a customer cost, because the payment happens weeks later somewhere they cannot see.

So every lead carries three separate fields, and separating them is the whole trick:

  • Page — the exact capture point. Facebook Lead Ad — Web Dev is a different Page from Facebook Lead Ad — Marketing, even though both are Facebook.
  • Source — the channel in the general sense: Facebook Ads, Google Ads, Website, Referral, LinkedIn, Cold Outreach.
  • Service Type — what they actually want, which is what makes demand by service line visible.

Payments belong to leads, so a payment inherits all three without anyone re-entering them. Filtering the queue by Page and status is therefore enough to answer which specific ad produced paying customers rather than merely enquiries — and Pages are rows in the database, managed in the app, so launching a campaign and being able to measure it are the same action instead of a feature request.

Targets are dated rows, not a number you overwrite

The obvious way to store a monthly target is a field on the user that you update when it changes. It works until somebody asks how a rep did in March, after the number was raised in June.

A target is a row: a user, an amount, and an effective-from date. Raising somebody’s number writes a new row. The old one stays exactly as it was, which means every historical month is still measured against the target that was actually in force at the time, and a rep cannot be retroactively judged against a figure set after the fact.

Achievement is then computed rather than entered — target against what the payments for that month actually add up to. The person being measured does not type in the measurement, which is the only reason the leaderboard is worth displaying.

Book a walkthrough of CRM.

We will show you the working system, tell you honestly what would need changing for your operation, and give you a range before you commit to anything.

30 minutes · We reply within one business day.

WhatsApp Book a call