Skip to content
Back to Resources
Guide

HubSpot and Stripe reporting: which closed deals never produced an invoice

Skopx Team
August 5, 2026
11 min read

It is the fourth working day of the month. Ana runs revenue operations. On her left screen is the HubSpot deals board, filtered to Closed Won, close date last quarter: 61 deals, $1.42m. On her right screen is Stripe, Payments, same date window, and the total underneath is a different number. Nobody is alarmed by that. Stripe bills on its own calendar, annual prepays land where they land, and the two totals were never going to agree.

The request is narrower than reconciliation. The controller, Marcus, sent one line in Slack: "Give me the list of deals we closed last quarter that we have never invoiced, and tell me which ones are actually a problem."

Ana already knows how the day goes, because she did this in April. Export deals to CSV. Export Stripe invoices to CSV. Match on email. Get roughly two dozen unmatched rows. Then open every one of those deal records and read: the note the rep left on the day of close, the last email in the thread, the attached order form. The matching takes twenty minutes. The reading takes the rest of the day, and the reading is the part Marcus is actually asking for.

What HubSpot shows on its own

HubSpot holds the intent side of this completely. Deal stage, close date, amount, line items, quote, owner, associated company and contacts, and the full activity timeline underneath. The official Stripe data sync brings Stripe customers, invoices, subscriptions and products into the CRM as objects, and payments collected through HubSpot's payment tooling show against the deal. The custom report builder handles cross object reporting, and where the associations exist you can build a deals report filtered on the absence of an associated invoice.

So the count is reachable in HubSpot. That is a fair thing to say and worth saying plainly. Where it stops is three specific places.

The association is doing all the work. If finance created the invoice directly in Stripe, against a customer that was never associated back to the deal, HubSpot has nothing to associate and reports the deal as uninvoiced. That row is a false positive, and it looks exactly like a real one.

Absence carries no reason. A deal with no invoice renders identically whether it is money quietly left on the table or a customer who asked in writing to start billing in October.

And the reason itself is written down, but written down as prose: a note on the deal, a line in the last email, a ticket someone in AP opened, a clause in the countersigned PDF sitting in the attachments. HubSpot stores every one of those faithfully. It does not report on them, because reporting means grouping and counting fields, and none of that is a field.

What Stripe shows on its own

Stripe is precise about what happened. Charges, invoices and invoice items, subscriptions and their schedules, credit notes, refunds, customers, and metadata hanging off most of it. Sigma lets you write SQL straight against those objects, and it is genuinely capable: every invoice finalized last quarter, with customer, status, amount and metadata, is a short query.

Where Stripe stops is structural, not a shortcoming. Stripe can only report on rows that exist. A deal that never produced an invoice produced no Stripe object at all: no draft, no failed charge, no void, no subscription in an odd state. From inside Stripe the deal is not a gap in a table, it is outside the dataset entirely. You cannot select the rows nobody inserted. And Stripe has never heard of a pipeline stage, so it has no view on which customers were supposed to appear and did not.

Why the gap exists

Half the evidence for this question is a record. Half of it is a sentence somebody typed.

The record half: the deal moved to Closed Won on 27 June, amount 48,000, owner Priya. The Stripe customer exists and its most recent finalized invoice is dated 4 March. Structured, queryable, trustworthy on both sides.

The sentence half is the note Priya left on 27 June: "Signed. Their finance freezes new spend until their FY starts 1 Oct, so we agreed to hold billing until then. Confirmed on the call with their CFO." That sentence is the complete answer for that row. It is not a property, not a picklist, not a column anyone would model in a warehouse, and nobody is going to retroactively backfill a "billing deferred" dropdown across a quarter of deals to make it reportable.

This is why the neighbouring product categories, both of which are good at what they do, land beside the question rather than on it.

Automation platforms are trigger based, and that is their strength. Zapier, Make, HubSpot workflows or a Stripe webhook can fire the moment a deal hits Closed Won, create the customer, draft the invoice, write the ID back onto the deal. Wire that up and the gap stops growing. It still cannot answer Marcus, because Marcus is asking about the past, and the defining property of these deals is that the event never happened. Silence does not fire a trigger.

BI tools will do the join better than a spreadsheet ever will. Power BI with Copilot, Looker Conversational Analytics, or anything sitting on HubSpot and Stripe replicas in the warehouse: ask in plain English for closed deals with no invoice and a well modelled semantic layer returns the list, handling keys, fan-out, currency and date grain properly. Where they stop is the evidence type. They read modelled sources, tables and columns. A note a rep typed is not a modelled source, and even when the raw text is synced into a text column, no dashboard is going to read it and decide what it means.

So this is not a reporting feature either product forgot to ship. It sits between the system that holds the intent and the system that holds the money, and what closes it is reading.

What the join actually requires

Two things: a key, and a judgement.

The key, strongest to weakest.

Stripe metadata carrying the HubSpot record ID, typically metadata.hubspot_deal_id. Exact, and free, but only present if somebody set it up at creation time. Most teams discover they wish they had done this on a day like Ana's.

A Stripe customer ID stored as a property on the HubSpot company or deal. Exact for the customer, ambiguous for which of that customer's invoices belongs to which deal once there are several.

The data sync association itself. Exact when the invoice was created through the supported path, absent when finance worked directly in Stripe.

Billing email. Right often enough to be tempting and wrong often enough to be dangerous. The deal contact is the champion who signed. The Stripe customer email is ap@ or billing@. They rarely match.

Company name plus amount plus a date window. A guess. Useful only as a candidate list for a human to confirm.

Then the judgement, which is the harder half. Assume the unmatched list is correct. It still contains at least four different situations, and only one of them is a problem.

What the row actually isWhere the evidence livesWhat it means
Billing was never raised, nobody picked it up after closeNothing written since the closed-won noteReal leak. Invoice now, then find the broken handoff
Billing deliberately deferred for a budget cycle or rampA note on the deal, or the last email in the threadCorrect as is. Belongs in deferred revenue, not a leak report
Renewal or expansion against an existing subscriptionThe Stripe subscription, whose amount changed on the right dateCorrect as is. No new invoice was ever expected
Marked Closed Won ahead of signature or POThe contract in deal attachments, or an AP ticketA forecast hygiene issue, not a billing one

Every verdict in that right-hand column requires somebody to read something. That is why this costs a day rather than a query.

How you would answer it with Skopx

Skopx sits over HubSpot and Stripe, alongside nearly 1,000 other tools and your databases, and you ask it in chat. The realistic sentence Ana types is not elegant, because the real question is not elegant:

Pull every HubSpot deal marked Closed Won between 1 April and 30 June. Match to Stripe invoices on metadata deal ID first, then the Stripe customer ID on the company, then billing email. For the ones with no invoice, read the deal notes and the last email on the thread and tell me why. Flag the ones where nothing explains it.

What comes back is 23 deals sorted into those four shapes, each with citations: a link to the deal, the Stripe query behind the match or the absence, and the quoted line from the note with its author and date. The four rows matched only on email and amount come back marked unconfirmed rather than folded silently into the total.

Once that answer is worth having every month, the next sentence turns it into a standing view. Ask for it as an internal app and you get a console: deal, amount, owner, days since close, match confidence, the quoted evidence line, and the verdict, grouped so the real leaks sit at the top. It reads live from both systems each time somebody opens it, and each viewer sees only what their own connected accounts can see, so a rep opening it sees their own deals.

There is exactly one write on that screen. Rows with no explanation carry a button, "Draft this invoice in Stripe", which opens a confirmation showing the customer, amount, line items and date it is about to create, and creates nothing until a person clicks confirm. That is the entire action surface. The console itself stores nothing: no records of its own, no queue, no 6am scheduled run, no alert when a new deal goes unbilled, no public link. It reads when someone opens it and it acts when someone clicks.

Honest limits

It cannot invent a key. When nothing on either side carries an ID, matching falls back to email, amount and date, and those rows come back labelled unconfirmed instead of quietly counted. The durable fix is upstream: write the HubSpot deal ID into Stripe metadata at customer creation, and next quarter's version of this question becomes trivial.

It does not watch. No trigger, no schedule, no alert, no monitoring. If you want the gap prevented at the moment of close rather than found ninety days later, that is trigger work, and a HubSpot workflow or an automation platform is the right tool for it. These are complementary jobs, not competing ones.

It reads what people wrote. When a rep closed a deal and typed nothing, the honest output is "no explanation found in HubSpot or email", which is a finding worth having but is not the answer to why.

It is not a ledger. This does not replace close reconciliation, deferred revenue schedules or anything an auditor signs. Amounts are read from source and the output is a worklist, not a statement.

Vague questions get vague answers. Name the date range, the pipelines and the Stripe account, the same way you would if you were briefing an analyst.

Skopx is $16 per seat per month on Team, including 2.3 million AI tokens per seat, and $5 for Solo. SOC 2 controls are in place.

The 23 rows were never really the deliverable. The six sentences that explain them are, and those sentences were sitting in HubSpot the whole time, in a shape no report could reach.

Share this article

Skopx Team

The Skopx engineering and product team

Related Articles

Stay Updated

Get the latest insights on AI-powered code intelligence delivered to your inbox.