smallbox

← All articles

What one capability unlocks

What does your system own when you push records to the accounting ledger?

Accounting sync: the hard part is the reconciliation boundary

You add a "send to accounting" button. A QuickBooks or Xero token, a couple of calls, and an invoice that lived in your product now also lives in the client's ledger. The first sync works, the bookkeeper sees the entry, and it feels like the integration is finished. The push really is the simple part — and the moment it succeeds is the moment the actual question opens, because now the same invoice exists in two systems, and something has to keep them honest.

The boundary, before the button

A sync isn't one system writing to another. It's two systems that both hold a version of the same fact, joined at a seam — and the only thing that matters is what each side is allowed to be authoritative about, and what happens when their versions drift.

The accounting system is, and should stay, the source of financial truth. Double-entry, the trial balance, the audit trail a regulator or an accountant will actually open: that is the ledger's job, it is built for it, and reimplementing any of it on your side is the wrong build. Your product is not a competing book of record. It's an upstream system that produces facts the ledger needs to know about — an invoice was raised, a payment landed, a credit was issued — and hands them across the seam in the shape the ledger expects.

So the ownership question is narrow and answerable. Three things sit on your side of that boundary, and getting them right is the whole job.

What stays yours: the mapping, the key, the log

The mapping. Your product thinks in its own concepts — a plan, an order, a refund, a customer tier. The ledger thinks in its chart of accounts — this revenue account, that tax code, this customer record, that item. Nobody but you can decide that your "Pro annual" becomes their "Subscription Revenue, tax-exempt, deferred." That translation is a product decision, it lives in your code, and it is the part that makes the sync mean the right thing rather than merely succeed. Get the mapping wrong and the numbers reconcile perfectly to the wrong accounts — which is worse than failing, because it looks fine until someone reads the books.

The idempotency key. This is the one that costs money when it's missing, so it gets its own paragraph below. The short version: every fact you push carries a stable identifier of your devising, so that pushing it twice is recognised as the same fact and posted once.

The reconciliation log. A record, on your side, of what you sent across the seam and what came back — this invoice, synced at this time, accepted as that ledger entry with that id. Not the financial truth; the ledger keeps that. This is the crossing record: the thing that lets a human, months later at audit time, point at one of your invoices and say with certainty which ledger entry it became, or that it never made it. Without that log, "did this sync?" becomes an investigation. With it, it's a lookup.

Everything outside those three you leave alone. You don't compute balances, you don't enforce accounting rules, you don't become a second ledger. You produce facts, map them, push them exactly once, and keep a record of the crossing.

The bug that posts twice

Here is the hard part, named plainly so it doesn't hide: idempotency across the seam.

Syncs fail halfway. The network drops after the ledger accepted the entry but before your side recorded the acknowledgement. A background job times out, gets retried, and runs again. A user, seeing no confirmation, clicks the button a second time. Each of these is ordinary, and each ends the same way unless you've designed for it: the same invoice posted to the ledger twice, revenue counted twice, a book that no longer matches reality and a bookkeeper who now has to find and reverse a duplicate they didn't create.

The fix is not "be careful." It's structural. Every fact crosses the seam with a stable, deterministic key derived from your record — not a fresh id per attempt, but the same id every time this particular invoice is sent. Both accounting APIs accept exactly this: a caller-supplied reference that lets a retry be recognised as a repeat and collapsed to a single posting. Combined with the reconciliation log — check it before you push, write to it after the ledger confirms — a retried sync becomes a no-op instead of a duplicate. The retry safety and the crossing record are the same mechanism viewed from two angles, which is why neither is optional and why both belong to you, not the provider.

The deeper reason this lands on your side is that the ledger can't make the call for you. It can dedupe an identical reference, yes — but only if you supply one, and only you know that this attempt and that earlier attempt are the same business fact rather than two genuine invoices that happen to look alike. Where the reconciliation boundary sits is exactly here: at the key you mint, on your side, before anything crosses.

What it rides on, and what it isn't

The plumbing around all of this is the part a foundation already carries. Syncing to an accounting system is a scheduled, retrying activity — pull the day's facts, push the ones not yet crossed, handle the ones the ledger rejected — which is a background job, not something to bolt onto a web request and hope the user doesn't navigate away. Every crossing and every failure wants to be logged, so that when the books and your records disagree, there's a trail that says what happened and when. And the facts most worth syncing are often billing facts — what was charged, what was paid, what was refunded — which means the record you're pushing from has to be one you own in the first place.

Those three aren't hypothetical for the foundation. CompanyGraph runs a background batch runner, a central logger that its services emit to, and Stripe-backed subscriptions with a local mirror of subscriptions and transactions, in production today. That mirror is the relevant precedent: it's already a record CompanyGraph owns and reconciles a provider against, which is precisely the kind of record an accounting sync pushes from. The accounting integration itself isn't built — but the boring half it would ride on is running and checkable, which is the only honest reason to mention it.

What an accounting sync is not is a place to recreate finance. The temptation, once you're holding invoices and payments and credits, is to start computing balances and enforcing rules on your side. Resist it. The instant you have your own numbers and the ledger's numbers and no agreement about which is authoritative, you've manufactured the exact ambiguity reconciliation exists to resolve — and at audit time, two confident records and no verdict is the expensive outcome.

The verdict

This is a feature, not a product — a clean, valuable one when the boundary is drawn correctly, and a quiet liability when it isn't. Own three things and leave the rest: the mapping from your concepts to their chart of accounts, the idempotency key that guarantees a fact posts exactly once, and the reconciliation log that lets a human prove what crossed. The accounting system stays the source of financial truth; your job is to feed it accurately and exactly once, and to keep a record of having done so.

It's the same shape as a single payment provider, one ring out. Where keeping your own record of what a payment means is about not letting a provider become your only copy, this is about not letting a retry become a duplicate when you write into a system that already owns the truth. And it's the capability underneath the larger build — the dashboard whose real job is reconciling money that moves through more than one door — where an accounting sync is one feed among several and the idempotency key is what keeps that feed trustworthy.


An accounting sync rides on the background jobs, logging, and billing record a foundation already carries — all three run in CompanyGraph today. If you're weighing where the reconciliation boundary should sit for your own product, that's the conversation a single workflow is meant to settle.

Articles describe the Foundation. The Foundation Map is the thing itself — accounts, admin, email, logging, and deployment, with one real workflow running through them.

← All articles