Every month, the finance team at a mid-market SaaS company ran the same sprint.
Day one: export open contracts from a Google Sheet maintained by the RevOps team, because the Salesforce data was incomplete after a migration two years ago. Pull raw usage records from the product database via a CSV an engineer generated manually. Cross-reference both against NetSuite to find which customers hit usage tiers, which had active credit memos, and which had custom discounts buried in a contract amendment from six months ago.
Day two: the engineer delivers a corrected CSV. The first export had duplicate event rows for one customer, which inflated their API call count by 11,000 requests. Back to cross-referencing.
Day three: build the draft invoice spreadsheet. QA pass. Catch a second error: a customer whose contract amendment from two months ago had a different ID format than the one in NetSuite, so the discount did not apply.
Day four: key the invoices into NetSuite. Send. Field two emails from customers querying their amounts. One was correct but confusing. One was wrong.
Four days. Every month. For a team of three people who had better things to do.
The Three-System Problem#
To produce one invoice, the finance team needed three separate systems, none of which talked to each other.
NetSuite held the customer master, open invoices, credit memos, and ERP IDs. It was the system of record, but it did not know about usage.
Google Sheets held the active contract terms: committed usage tiers, custom discounts, and amendment history going back three years. The sheet existed because the deals were complex enough that a standard Salesforce contract field could not capture the nuance. Every account manager had added rows.
A Postgres database held raw event logs from the product. Every API call, every usage event, timestamped and stored. Getting anything useful out of it required a SQL query and an engineer willing to run one on request.
Nobody could open a single screen and see what a customer owed. A person had to assemble that number by hand, every month, across all three.
What Connecting the Sources Actually Looked Like#
Enso connected to all three in the same day. NetSuite and Postgres were credential-based. Google Sheets required a one-time share with the Enso service account.
The first thing Enso surfaced: the customer ID formats did not match. NetSuite used numeric IDs. The Google Sheet used a company name slug the account team had invented. Postgres used a UUID from the product’s user table. Three systems, three formats.
Enso ran a fuzzy match pass on first sync and presented a mapping table for human confirmation. The finance lead spent 20 minutes reviewing 89 matches, correcting 4 where the fuzzy match was wrong, and confirming the rest. That mapping became the permanent translation layer.
After that, the nightly sync ran automatically.
The Reconciliation Logic#
Billing at a usage-based SaaS company is not a lookup. It is a calculation with branches.
A customer billed at $0.05 per API call with a $500 monthly minimum and a 15% discount for contracts over $5,000 per month requires several checks before a number can be put on an invoice: total usage from the product database, applicable tier from the contract sheet, minimum commit check, discount eligibility based on projected total, and any open credit memos from NetSuite.
Enso executes these checks deterministically, in sequence, for every customer. The logic is written once, reviewed by the finance team, and runs the same way every time.
The output is a reconciliation view: every customer, their usage, the tier applied, the invoice amount, and a status. Exceptions get flagged with a reason. The team does not need to search for problems; they appear at the top of the queue with enough context to resolve them without opening NetSuite, the spreadsheet, or the database separately.
The Exceptions That Still Need a Human#
Automation handles the straightforward cases. The cases that still need judgment are the ones where the data is technically correct but the right answer requires knowing something about the customer relationship.
The most common exception in this team’s workflow: a customer who crossed a usage tier mid-month. Their contract specified proration on crossover. Enso calculated the split automatically and flagged it for confirmation before pushing to NetSuite, because a split invoice looks unusual to the customer and the finance team wanted to make sure the calculation matched what sales had promised verbally.
Other common exceptions: customers who paused usage mid-month after a support incident and expected a credit, or customers whose contract amendment was sitting in the sheet but had not yet been signed. Enso surfaces these. The human decides. Then approves.
That review took 30 minutes in month one. By month three, with the exception patterns becoming familiar, it was closer to 15.
What Changed, and What Did Not#
NetSuite is still the ERP. The Google Sheet is still how the RevOps team manages contract terms. The Postgres database still logs every event.
What changed: nobody manually exports, cross-references, or keys data anymore. The reconciliation happens overnight. The finance team arrives on the first of the month to a queue of 85 invoices ready to approve and 4 exceptions to review, rather than four days of work ahead of them.
The engineer who used to run the CSV export on request does not do that anymore. That was not a small ask, it happened 12 times a year.
| Before | After | |
|---|---|---|
| Time to close billing | 4 days | 30 minutes of review |
| Engineer dependency | Monthly CSV export | None |
| Billing errors caught post-send | 4 to 6 per month | 0 in first quarter |
| Customer ID mismatches | Found manually, occasionally missed | Flagged on first sync, permanent mapping |
| Amendment tracking | Manually maintained in spreadsheet | Versioned, diffed on each sync |
| Total hours per month | 22 hours across the team | 2 hours |
Why This Works for Complex Billing#
The reason this kind of automation fails when teams try to build it themselves in scripts or Zapier workflows is versioning.
Billing rules change. Customers renegotiate. A script that calculates invoices correctly in January may produce wrong numbers in April after three amendments without anyone noticing. There is no diff. There is no audit trail.
Enso versions the contract rules. When the Google Sheet changes, Enso shows what changed, when, and which customers are affected. The finance team approves the rule change before it applies to live billing. Transactions that ran before the amendment date stay on the old version.
That is the thing that makes finance teams comfortable enough to stop running the manual backup. The audit trail exists. The logic is inspectable. And if a customer disputes an invoice, the team can show exactly which rule version produced that number and when it was approved.
What’s Next#
The billing reconciliation fixed one problem. It also changed what the team thought was possible.
The Google Sheet holding contract terms is the last manual piece in the chain. It works, but it carries the same fragility the original process had: one person’s naming convention, one wrong row, one amendment that did not get saved. The team is now migrating those contract terms into Enso directly, so billing rules are owned and versioned by the system that runs them.
The second project: collections. Invoices used to go out four days late every month. Now they go out on the first. That freed up enough capacity to actually act on overdue accounts before they age. The team is building a dunning workflow in Enso that pulls AR aging data from NetSuite and sends sequenced follow-ups automatically, with a manual hold list for accounts the sales team is actively managing.
The pattern is the same in both cases: find the spreadsheet doing mission-critical work, and replace it with something that runs reliably and leaves an audit trail.
The team spent four days a month on billing reconciliation because the data lived in three systems with no shared schema, no normalized IDs, and no automated path between them.
Connecting those systems took one day. The reconciliation has run automatically every month since. If your billing process looks anything like this, see how Enso handles usage-based billing or get a demo with your own data .

