Zum Hauptinhalt springen

Xero

With Xero, Vodia Billing computes the tax and Xero records the figures supplied to it.

This is not a preference. Xero's automated sales tax cannot run on invoices created through its API, so an API-created invoice carries whatever tax figures the caller provides. Vodia Billing therefore has to be the tax authority for a Xero tenant, and the tenant needs a tax profile applied here.

Vodia Billing 18

Xero customers do not pay by card here

An invoice pushed to Xero is paid in Xero. A customer bound to an organisation is never offered the Stripe Pay button, because a second collection channel against one document is one payment recorded twice.

Setup​

1. Register the app​

In the Xero developer portal, create an app of type Web app. Register it on your own Xero developer account, one app per install — that matters because of the connection cap described below.

On App details, set the app name and the company or application URL, which is the Vodia Billing base URL:

https://your-domain

Xero app details

On Configuration, set the redirect URI:

https://your-domain/api/xero/callback

The client ID and the client secret are on the same screen.

The client secret is shown once

Copy the secret when it is generated. Afterwards the field shows only its creation timestamp and the value cannot be read back. If it is lost, use Generate another secret and re-enter it under Accounting → App.

Xero configuration

2. Enter the credentials​

Accounting → App, choose Xero, paste the client ID and secret. Both are stored encrypted under ENCRYPTION_KEY.

3. Connect the organisation​

Accounting → Connect. Authorise the specific Xero organisation you bill from. The organisation name appears on the connection once it is established.

4. Discover​

Discover pulls the contact list, chart of accounts and tax rates.

5. Map accounts​

Map usage, recurring, goods and discount lines to revenue account codes from your Xero chart of accounts.

6. Map customers​

Xero contacts carry a real Account Number field:

Xero contact

Vodia Billing's own Account reference — read from each site's PBX Billing parameter field, parm1 by default, and inherited from there onto the customer it bills under — is compared against it:

PBX tenant billing parameter

sis-sds-ss-dsqwqx-1 on both sides is an exact reference match. This is the strongest of the three tiers auto-bind understands — an operator typed the same string into both systems, so it is a fact rather than a guess — and it is one of the two tiers the auto-bind pass is allowed to act on without a human: Accounting → Mapping binds it the moment you click Auto-bind, and the scheduled pass binds it unattended on its own interval (default four hours, configurable under Settings).

If no reference matches, auto-bind falls back to the contact's Name against the customer's name. That is an inference rather than a fact, and it is auto-bindable on the same terms — which is why a name already claimed by another customer, or two customers proposing the same contact in the same pass, are both refused by name rather than resolved by whichever was processed first.

The binding is written to the customer, never to a site. A customer can span two PBXs, so there is no per-system answer to fall back on.

A match on domain name alone is the weakest signal and is never bound automatically, by either the button or the timer. It is only ever proposed — confirm it by hand from the mapping screen.

7. Apply a tax profile​

Every Xero tenant needs a tax profile applied in Tax, because Vodia Billing is computing the tax that Xero will record.

Tax types​

Xero invoice lines carry a TaxType. Vodia Billing supplies the tax type alongside the amount it computed.

Tax types that silently produce zero

Two failure modes here produce a 0.00 tax figure with no error at all:

  • TaxType: "AVALARA" returns a successful response with zero tax.
  • GUID-shaped tax type values are Avalara-managed and cannot be used on a manual tax document.

Both look like success. Always check the tax figure on the first invoice pushed to a new Xero organisation, in Xero itself, before you send anything to a customer.

Use the tax rates that Discover pulled from your own organisation. Those are the ones that work.

Totals​

For a Xero tenant, Vodia Billing's total is the invoice total. There is no separate face value to reconcile, because Xero is recording what it was given rather than computing its own figure.

The divergence detector treats Xero this way, so a mismatch on a Xero tenant is a real problem and worth investigating, unlike on QuickBooks.

One push, and no re-push button​

Vodia Billing creates Xero invoices with PUT, not POST, and this is deliberate.

POST /Invoices silently overwrites an authorised invoice

Xero's POST /Invoices behaves as an upsert. Sent against an invoice number that already exists — including one already authorised — it overwrites it without error.

PUT is create-only. A second attempt against the same number returns a 400 rather than destroying the original.

On top of that, Vodia Billing refuses a second push locally, before any API call, once an invoice is recorded as pushed. Between the local blocker and PUT, a duplicate push cannot happen.

The consequence is that there is no re-push button. If a push genuinely needs redoing, the invoice must be voided in both systems and reissued.

Seeing what actually happened​

The Xero developer portal has a Logs page on the app, listing every call made with its credentials: UTC timestamp, organisation, status, type, URL, method, request and response size, and duration. It is the fastest way to confirm what a push actually did.

Xero logs

A single push, with Xero sending the email, looks like this:

GET  /api.xro/2.0/Organisation      200
GET /api.xro/2.0/Contacts/{id} 200
PUT /api.xro/2.0/Invoices 200
POST /api.xro/2.0/Invoices/{id} 204 <- email
GET /api.xro/2.0/Invoices/{id} 200 <- read-back

The PUT is the create. A second attempt against the same invoice number appears here as a 400, which is what you want to see rather than a silent 200 that overwrote something.

Payment sync appears separately as a single batched GET /Invoices?IDs=…, and Discover as GET /Contacts?page=….

Connection cap​

A Xero app supports a limited number of connected organisations. The limit and the current count are shown on the app's Configuration screen, as a connection count — read the number on your own app rather than assuming one, because only Xero can raise it.

Connection management lists each connected organisation with its Xero tenant ID, which is how you tell them apart when a partner bills from more than one.

Xero connection management

This cap is the reason each install registers its own app rather than sharing one, and it bounds the "split across multiple organisations" remedy below.

Daily API ceiling​

Xero's daily limit is 1,000 calls per organisation, not 5,000. The per-minute limit is 60, with a concurrency limit of 3.

A pushed invoice costs three calls when Vodia Billing sends it — contact read, PUT /Invoices, read-back — and four when Xero sends it, with an additional email call. The organisation read is cached for six hours, so it costs one call per run rather than one per invoice.

A push run therefore covers roughly 230 to 310 invoices per organisation per day, depending on which side sends. The unit is the invoice, so it is the number of customers that counts, not the number of sites: an enterprise of forty sites on one document costs the same three or four calls as a single site does.

Bill runs do not push against that estimate. Xero returns x-daylimit-remaining on every response and the run reads it, pausing when the count reaches a reserve of 50 calls left. A paused run records the ceiling, the reserve, the remaining count, how many tenants are still to go, and the resume time — Xero's counter resets at 00:00 UTC.

Nothing is marked failed. The remaining rows keep their place in the order and the next pass starts where this one stopped, because the local push blocker refuses an invoice that already carries an external ID.

If you bill more than a few hundred tenants from one Xero organisation, split the fleet across multiple runs on consecutive days, or across multiple organisations up to the connection cap on your app.

Sending the invoice​

For a Xero organisation, either side can send. Configure it per customer:

  • Vodia Billing sends — the customer receives the Vodia Billing invoice email, with call detail attached and a share link. Xero holds the accounting record.
  • Xero sends — Xero emails its own invoice. Vodia Billing then treats the invoice as externally delivered and does not send its own copy.

Reminders follow the same setting. This is why tax authority and delivery are tracked separately: a Xero tenant has Vodia Billing computing tax while either side may be sending.

The choice also changes the API cost of a push run, as above.

Credits​

Because Vodia Billing owns the tax on a Xero tenant, credits can be issued from within Vodia Billing. They apply after tax, and voiding the invoice restores them.

The corresponding credit note still has to be raised in Xero. Vodia Billing records the credit but does not create Xero credit notes.

Australian GST​

Check the GST on your first discounted AU invoice

The discount travels to Xero as its own negative line, so GST should be computed on the discounted base.

Verify that figure by hand in Xero on the first Australian invoice that carries a discount, before it reaches the customer. Tax is the one area where a wrong result produces an invoice that looks entirely normal.

Worked example​

What arrives in Xero​

The invoice from Worked Example, pushed to Xero:

Invoice INV-00151        Contact: Acme Corp
Date 2026-09-01 Due 2026-09-15 Status: AUTHORISED

Line Qty Unit Account TaxType Amount
US National 1240.00 0.012 200 OUTPUT2 14.88
United Kingdom 86.00 0.035 200 OUTPUT2 3.01
UK Mobile 12.00 0.14 200 OUTPUT2 1.68
US Toll Free 318.00 0.00 200 OUTPUT2 0.00
Extensions 42 12.00 200 OUTPUT2 504.00
Hunt groups 3 5.00 200 OUTPUT2 15.00
DID numbers 18 1.50 200 OUTPUT2 27.00
Managed support plan 1 150.00 200 OUTPUT2 150.00
Desk handset 1 89.00 260 OUTPUT2 89.00

Subtotal 804.57
Total Tax 82.72
TOTAL 887.29

Xero's total is 887.29, the same as Vodia Billing's, because Xero is recording the tax figures it was given rather than computing its own.

That is why a divergence alert on a Xero tenant is a real problem worth investigating, unlike on QuickBooks.

The failure that looks like success​

The same push with the wrong tax type:

  Line                        Qty      Unit    Account   TaxType     Amount
US National 1240.00 0.012 200 AVALARA 14.88
...
Subtotal 804.57
Total Tax 0.00 <-
TOTAL 804.57

HTTP 200. No error, no warning, no alert. An invoice that is $82.72 short, sitting in Xero as authorised.

AVALARA and GUID-shaped tax type values are Avalara-managed and cannot carry manual tax. Use the tax rates Discover pulled from your own organisation.

Check the tax figure on the first invoice pushed to any new Xero organisation, in Xero, before it goes to a customer.

A second push​

POST /api/invoices/<id>/push
409 Conflict — invoice already pushed to Xero on 2026-09-01 03:14

Refused locally, before any API call. And if it somehow reached Xero, the adapter uses PUT:

PUT /Invoices  (InvoiceNumber INV-00151)
400 Bad Request — invoice number already exists

POST /Invoices would have behaved as an upsert and overwritten the authorised invoice without error. PUT is create-only. Between the local blocker and PUT, a duplicate push cannot happen — and that is why there is no re-push button.

Both the PUT and a refused second attempt are visible on the app's Logs page in the developer portal.

Pacing against the daily ceiling​

Push run, Xero org "Northwind Telecom"   (Xero sends the invoice)
Ceiling: 1,000 calls/day
Reserve: 50
Consumed: 948
Pushed: 236 invoices (~4 calls each)
Paused: 64 customers remaining — resumes after 00:00 UTC

At four calls per invoice a single Xero organisation supports roughly 235 pushed invoices per day, nearer 310 if Vodia Billing does the sending. A grouped estate gets further on the same budget, because forty sites on one document are one invoice. The run paused on the reported remaining count, not on that estimate.