Corporate Finance (SaaS) 600 Employees 9 Weeks

Building a Finance Team's Single Source of Truth with dbt and Snowflake

47 Excel models, 3 different ARR numbers, and a month-end close that took 12 business days. The CFO couldn't tell the board a straight number. Here's how we untangled it.

The Challenge

This B2B SaaS company had grown fast - from 80 employees to 600 in four years. Their finance operations hadn't kept up. The finance team maintained 47 separate Excel workbooks for revenue recognition, each owned by a different analyst, each with slightly different formulas for calculating ARR, MRR, churn, and expansion revenue.

The result? Three different ARR numbers floating around the company. Sales reported $18.2M based on their Salesforce pipeline calculations. Finance said $17.8M using their Excel models. And the board deck showed $18.0M because someone on the FP&A team had literally averaged the two numbers and called it "the adjusted figure." When the external auditors found out, they weren't amused.

Month-end close was a 12-business-day marathon. The first 3 days were spent manually pulling data from Salesforce, Stripe, and NetSuite into Excel. The next 4 days were reconciliation - figuring out why the Stripe billing data didn't match the Salesforce contract amounts (it was almost always a mid-month upgrade that got recorded differently in each system). The final 5 days were review cycles and corrections.

The company was preparing for a Series D raise. Their prospective investors wanted clean financials, and the CFO knew that showing up with three different revenue numbers wasn't going to fly. They needed one source of truth, and they needed it fast.

What We Built

We built a Snowflake-native financial data model using dbt (data build tool). The core principle: every metric gets defined exactly once, in version-controlled SQL, with tests that run on every build.

Data ingestion: We used Hevo Data to sync Salesforce opportunities, accounts, and contract objects into Snowflake on a 15-minute schedule. Stripe billing events (subscriptions, invoices, charges, refunds) landed via Hevo's native Stripe connector. NetSuite GL data synced nightly - the finance team didn't need real-time GL numbers, and the NetSuite API rate limits made nightly the practical choice anyway.

dbt model architecture: We built 134 models organized into three layers:

  • Staging (38 models): One-to-one mappings from source tables. Column renaming, type casting, basic cleaning. Every staging model has a _loaded_at timestamp and a not_null test on the primary key.
  • Intermediate (52 models): This is where the heavy lifting happens. The subscription model joins Salesforce contracts with Stripe subscriptions and resolves the discrepancies using a priority hierarchy (Stripe is the source of truth for billing amounts, Salesforce for contract dates and customer metadata). The revenue waterfall model calculates MRR movements - new, expansion, contraction, churn, reactivation - for every customer in every month.
  • Mart (44 models): Business-ready tables. fct_monthly_recurring_revenue, fct_annual_recurring_revenue, fct_net_revenue_retention, dim_customers, dim_subscriptions. These feed directly into the dashboards and the board reporting pack.

Testing: 280+ dbt tests across the entire DAG. Not just not_null and unique - we wrote custom schema tests for business rules. Examples: ARR can never be negative. MRR movements must sum to zero across all customers in a given month (what comes in must go out or stay). A customer can't have both a "churn" and "expansion" event in the same month. These tests catch data quality issues before the finance team ever sees them.

Documentation: Every metric definition lives in dbt docs. When the CFO asks "how do we calculate net revenue retention?", the answer isn't buried in a cell formula in Sarah's laptop - it's in a version-controlled YAML file with a clear SQL definition that the auditors can review.

Results

12 → 3.5 Days Month-end close cycle
One ARR Number Consistent across all teams (finally)
280+ Automated data quality tests
Auditor Approved "Best data we've seen at this size"

Tech Stack

Snowflake dbt Hevo Data Salesforce Stripe NetSuite

What We Learned

  • The Salesforce-Stripe reconciliation was the hardest part. Salesforce records contract amounts at the deal level. Stripe records billing at the subscription item level. When a customer upgrades mid-cycle, Salesforce shows one amendment record while Stripe shows a prorated charge, a credit, and a new subscription line. We spent 2 full weeks just getting this join logic right, and it accounted for the entire $400K discrepancy between the sales and finance ARR numbers.
  • Finance teams actually love dbt docs. We were worried the finance analysts would resist moving from Excel (which they knew) to SQL models (which they didn't). Turns out, dbt's generated documentation site won them over. They could see the DAG, read the metric definitions in plain English, and trace any number back to its source. The FP&A lead said it was the first time she could explain the ARR calculation to a board member without opening Excel.
  • 280 tests sounds like overkill - until you catch a $200K error. In week 6, a Salesforce admin changed a picklist value on the opportunity stage field. Without our dbt test that validated expected stage values, that change would have silently broken the pipeline and excluded roughly $200K of closed-won revenue from the ARR calculation. The test caught it within 15 minutes of the next dbt run.
  • Don't migrate all 47 Excel models at once. We started with ARR and MRR only, got those blessed by the CFO and the auditor, then added churn and expansion, then net revenue retention. Each metric got its own sign-off cycle. Trying to replace everything at once would have been a political minefield.

Finance Team Drowning in Excel?

Multiple revenue numbers, painful month-end closes, auditors asking hard questions - we've built the fix before. Let's talk about your data.

Start a Conversation