“Odoo can already report on this.”
Where the lead-to-invoice chain is intact, partly. Joining on the contact detail works where it is not, which is most real databases.
For Odoo
Export leads and invoices from Odoo, join them on the customer, and rank sources on invoiced revenue.
No API key Nothing to install in OdooNothing to install No card requiredNo card
Odoo can hold both halves of the match — leads in its CRM, and the invoices that followed in its accounting — which makes it one of the few systems here that could answer attribution from a single database. In practice the chain from lead to opportunity to quotation to invoice is only intact where the sales team completed every step, so CloseRev joins the lead export to the invoice export on the contact detail instead, which works whether or not the chain was kept. Revenue with no traceable lead is reported as Direct / Unknown.
Last checked against Odoo's own documentation on September 25, 2026.
The gap
Opportunities get converted, quotations get sent, invoices get raised, and nobody can say which marketing produced any of it.
What was sold, to whom, and for how much.
The click, the keyword, the call, and what each one cost.
The file
Three things carry the match: who, how much, and when. Anything else is optional and only changes how the report can be sliced.
On the lead and on the invoiced customer. Joining on the contact detail works even where nobody linked the records inside Odoo.
As recorded when the lead was created.
Posted customer invoices. A confirmed quotation is not yet revenue.
For multi-company setups, which entity invoiced; and which team handled the lead.
Step by step
Written for somebody with Odoo open in the next tab. Report names vary by edition, so each step says what to look for.
One row per lead with the contact detail, source, campaign and created date — including leads that were lost.
Contact detail, amount, date. Draft and cancelled invoices are not revenue.
Where a lead was converted to an opportunity and on to a sale, the link helps; where it was not, the contact detail still joins them.
Weeks for a product business, months for a project or a contract.
The join runs on the phone and the email, normalised, with unmatched leads and invoices both kept visible.
What comes back
Revenue by channel, the count of sales behind each figure, and an honest bucket for the ones nobody could trace. Sample figures, from the worked example on the Industrial Manufacturers page — not from a Odoo account.
| Channel | Share | Sales | Revenue |
|---|---|---|---|
| Google Ads | 24 | $396,000 | |
| Trade shows | 19 | $468,000 | |
| Email marketing | 14 | $198,000 | |
| Direct / Unknown | 43 | $738,000 |
Unmatched sales stay in Direct / Unknown. They are never spread across the paid channels to make the total look better.
The argument
An integrated system makes it possible to follow a lead through to an invoice, and that possibility is only realised when every step was completed in order.
In practice a salesperson creates a new customer rather than converting the lead, a quotation is raised against a different contact, or the invoice goes to a company when the lead was a person.
Each break leaves the invoice without a source, and a report that follows internal links attributes it to nothing.
Joining on the contact detail reconnects them, and the report shows which matches came through the internal chain and which had to be rebuilt.
The share that had to be rebuilt is itself worth knowing. It measures how the sales process is actually used, as against how it was designed.
Many companies start with one part of an integrated system — often the CRM or the invoicing — and run the rest elsewhere for years.
A company using its CRM without its accounting has leads and no invoices in the same place; one using its accounting without the CRM has invoices and no sources.
The method works either way, because it needs two exports rather than one integrated setup: leads from wherever they are kept, invoices from wherever they are raised.
That also makes it robust to the next change of system, which in growing companies tends to come sooner than planned.
The step from quotation to invoice is where scope changes, discounts get applied and some confirmed orders are never invoiced at all.
Ranking sources on confirmed quotations measures the sales team's optimism; ranking on posted invoices measures the money.
Where both are exported, the gap between them per source is worth a look once. Sources that produce price-sensitive buyers usually show the widest one.
The report ranks on posted invoices and labels the basis, so the number can be defended when somebody asks.
Where customers are billed monthly or annually on a subscription, one sale produces a long series of invoices.
Credited invoice by invoice, the source that produced the customer gets paid again every month and looks far better than a source whose customers buy once.
Summing by customer across the window and keeping the first invoice apart from later ones gives both the acquisition figure and the retention figure without mixing them.
That split also shows which sources produce customers who stay, which is usually the more valuable finding. A source whose customers cancel after two months was a source of trials, however good its first-month figure looked.
Fair questions
Where the lead-to-invoice chain is intact, partly. Joining on the contact detail works where it is not, which is most real databases.
Then the invoices come from Odoo and the leads from wherever you keep them. The method needs two files, not one system.
Then most matches will come through the internal chain, and the report will show it.
No. It reads exported files, so your database, users and records stay where they are.
Both, if you use its CRM and its invoicing. Either on its own works too, with the other half from elsewhere.
Leads with a contact detail, source and date, plus posted customer invoices with amounts and dates.
Because they break whenever a step is skipped. Joining on the contact detail reconnects them.
Yes. They are what each source cost, and leaving them out flatters the busiest one.
Because scope and price change between the two, and some confirmed orders are never invoiced.
Summed by customer, first invoice apart from later ones, so a source is credited once for each customer it produced.
Yes, where the export carries them.
Long enough for your sale — weeks for products, months for projects and contracts.
Any invoice with no matching lead. Its size is stated rather than distributed.
A contact detail, a source and a date on one side; a contact detail, an amount and a date on the other. No ledgers, no products, no notes. Encrypted in transit and at rest and deleted with the import.
Invoiced revenue per source, first and recurring invoices apart, the share of matches rebuilt outside the internal chain, and everything unmatched kept visible.
By trade
What the report looks like once the export is in, written for each one.
Other systems
Running more than one system, or comparing? The method is the same and the columns are not.
Odoo and the other product names and logos on this page belong to their owners and are shown to identify the software a file comes from. CloseRev is not affiliated with or endorsed by them, and connects to none of them: it reads a file you export.
Start today
Nothing to install in Odoo, no API key, and no need to have been tracking anything until now. Last year works as well as this month.