QuickBooks Online connection

Each builder connects their own QuickBooks company through a state-protected OAuth flow, with tokens stored encrypted and refreshed automatically before they expire.

What it is

An OAuth start route that mints a CSRF state row, a callback that validates it, exchanges the code, stores encrypted tokens plus the company id, and captures the company name. Sandbox and production differ only in which credentials and API host are used — the same code serves both, with the API minor version pinned deliberately.

Also called: connect QuickBooks · QBO · accounting integration · realm id

See it
QuickBooks Online connection
🔒ridgelinepostframe.com/project/quickb471
Hobby Shop
Framing · on schedule
The QuickBooks section in Settings showing 'Connected as <Company>' with environment and connected date. Rendered by src/components/settings/QuickBooksIntegrationSection.tsx. Sample data — no customer information appears here.
How it works
  1. 1Start creates a state row recording the account, user, return path and environment.
  2. 2The user approves at Intuit and is redirected back with a code, the state and a company id.
  3. 3The state is validated for existence and expiry, then consumed.
  4. 4Tokens are exchanged and stored encrypted on the account row with the company id.
  5. 5Company info is fetched to capture the company name (best-effort), and the user is returned to where they started with a success or error flag.
Why we built it

The module documents the environment design: 'Sandbox/production switched by QB_ENVIRONMENT env var. Same code, different hosts + different Client ID/Secret per env. No code change between sandbox and production deploys.' The version pin has its own reason: 'Without this, Intuit silently picks the oldest published version and may break behavior on the next release.' The optional Projects scope is gated because 'Intuit returns invalid_scope and blocks the ENTIRE connect if the Intuit app doesn't have the Projects API enabled.'

The problem
  • Invoices had to be re-entered into accounting by hand.
  • An unpinned API version could break silently on a vendor release.
  • Requesting an unavailable scope blocked the entire connection.
Sound familiar?
What you get
One consent flow connects the builder's own books.
Sandbox and production behave identically.
The connection cannot be started by a forged redirect.
What's inside
Accounting tokens refresh themselves
Every accounting API call loads credentials and proactively refreshes the access token before it expires, so the connection does not quietly go stale.
Connection details fill themselves in
If the connect step never captured the company name or plan tier, the status endpoint fetches and backfills them once, then serves from cache.
Customers created in the books, once
A customer is resolved from cheapest to most expensive — cached id, then email, then display name — and a duplicate-name rejection resolves to the existing record instead of failing.
Invoices mirrored into the books on send
Sending an invoice mirrors it into the builder's accounting package so they have a copy and can collect through it, and a failure never blocks the platform send.
Paid in the books, paid on the project
When an invoice or payment changes in the connected accounting company, the balance and status are mirrored back onto the platform invoice so the project shows paid.
Projects created in the books when the contract is signed
Counter-signing a contract creates the customer and project in the books, and starting work flips it to in progress — degrading gracefully through three drivers depending on what the connected company supports.
A log of every accounting exchange
Every push and pull writes a row naming the entity, the direction, the outcome, the HTTP status and a readable message, so a failed sync can be diagnosed after the fact.
Accounting behind a named boundary
Every route that talks accounting imports from one boundary module with provider-neutral names, so a second accounting package becomes an adapter rather than a rewrite.

See it on your own jobs

Twenty minutes, your numbers, no slide deck. We’ll build one of your real buildings in front of you and send you the estimate link at the end — yours to keep either way.

or keep browsing features →