← back

The API has trust issues

Getting one number out of Xero requires OAuth, a signed handshake, and manners. Worth it, mostly.

Xero holds the books. To read a single number out of it, I have to prove who I am three separate ways — and I still verify its signature on everything it sends back. Trust, in integration land, is a one-way street with toll booths in both directions.

I’m not complaining. The paranoia is symmetric, and after you’ve seen what an unverified webhook can do to a ledger, symmetric starts to feel like the only sane arrangement.

The visa application

Everything starts with OAuth. You fill out an application stating your intentions — I would like to read the contacts, the invoices, the trial balance, thank you for asking — and Xero issues you tokens with the warmth of a border agent who has heard every story before.

The details are where the personality lives. The redirect URI has to match the registered one to the character, including a trailing slash I forgot twice and will never forget again. The access token lives for thirty minutes, like a parking meter. The refresh token lasts sixty days, like a gym membership I should be using more. And because a token that can read accounting data is effectively a key to the building, they get encrypted before they touch the database. A credential at rest unencrypted is just a leak with a delay on it.

Then it tests me back

Webhooks are where it gets genuinely funny. Before Xero will send me real events, it sends a fake one — an “Intent to Receive” — to check that I’m actually listening. I have to answer 200 to a made-up event, politely, like a butler confirming a doorbell works. Only then does the real feed start.

Every real event arrives with an HMAC signature computed from a key we share. I recompute it on my side. If the math disagrees, the event is treated like a stranger at the door with a clipboard: ignored, logged, no hard feelings. The API demanded three proofs of my identity, and its own events still don’t get in without showing papers.

Sixty questions a minute

Rate limiting has a personality too. The API answers sixty questions a minute and then goes quiet, and no amount of asking changes that. It isn’t slow. It’s dignified.

So the sync batches its business, waits its turn, and retries with jitter — the jitter being the polite fiction that my retries aren’t a pattern. A fixed retry interval is a metronome, and metronomes sound like an attack. Randomized delays sound like a person. The machines can’t tell the difference, but the machines are the audience, so.

And then, the polygraph

Here’s the part I actually respect. After all that ceremony — the scopes, the signatures, the metered questions — the trust flows back the other way too. Every day CostLayer pushes a COGS journal to Xero as a draft, and then the reconciliation compares Xero’s stock account against my local FIFO valuation and reports a single number: drift. Zero is the only passing grade.

The API that made me prove everything gets verified right back, down to the cent. That’s not paranoia. That’s bookkeeping.

Integrations don’t eat weeks because of the happy path — the happy path takes an afternoon. They eat weeks on the handshakes. The handshakes are done now, and the number still comes out exact, which was the entire point of being this suspicious in every direction at once.