Sync to QuickBooks, Xero and HubSpot
Send customers, invoices, payments and credit notes to QuickBooks Online or Xero, and plans, MRR and subscription status to HubSpot — one way, per environment, without code.
Base316 is the system of record for billing. Settings → Integrations copies what it produces into the tools the rest of the business already uses: the books (QuickBooks Online or Xero) and the CRM (HubSpot). Nothing is read back except the vendor's own ids, so a re-sync updates what is there instead of posting it twice.
What goes where
| Base316 | QuickBooks Online | Xero | HubSpot |
|---|---|---|---|
| Customer | Customer (DisplayName, email, address) | Contact (Name, ContactNumber = Base316 id) | Company (base316_customer_id) |
| Finalized invoice | Invoice — lines, DocNumber, due date, tax | Invoice ACCREC, AUTHORISED, lines, tax per line | — |
| Voided invoice | Invoice voided | Invoice VOIDED | — |
| Payment (card or recorded) | Payment linked to the invoice | Payment against the invoice | — |
| Credit note | CreditMemo, applied to the invoice | Credit note ACCRECCREDIT, allocated to the invoice | — |
| Subscription | — | — | base316_plan, base316_mrr, base316_subscription_status on the company, and one deal |
- Money goes over exactly. Amounts are rounded once, to the currency's own minor unit — none for JPY and KRW, three places for KWD and BHD — and written into the request as the decimal Base316 rated, never through a floating-point number. Line amounts are split so they add up to the invoice total to the cent.
- Tax is sent as the amount Base316 charged, against the vendor tax code you map each Base316 tax rate to. A document carrying two rates at once (state plus city) is refused with a message rather than guessed at — map a combined rate at the vendor and retry.
- Discounts become a discount line in QuickBooks and a negative line in Xero.
- A credit note on an invoice that is already paid is created but left unapplied: the customer is owed a refund, which is a decision for a person.
- Prepaid wallet balance applied to an invoice is not sent as a payment. In the books, those invoices show the wallet amount as still open; record the wallet as you account for prepayments today.
- MRR is the same figure as the revenue report: recurring plan fees and addons, per month, excluding usage; a cancelled subscription is 0.
When it syncs
Every customer, finalized invoice, payment, credit note and subscription change already raises a webhook event. The same events queue a sync job; the scheduler sends queued jobs every fifteen seconds. Nothing waits on the vendor while somebody is checking out.
- A job sends whatever it depends on first — an invoice sends its customer if the customer has not been sent — so jobs never need to run in order.
- Rate limits: a 429 pauses that connection until the vendor's
Retry-After(a minute when it gives none), without using up an attempt. - Outages (timeouts, 5xx) are retried at 30 s, 2 min, 8 min, 32 min and 2 h, then marked failed.
- A refused document — a missing mapping, a duplicate number — fails at once with the vendor's own message. Fix the cause and press Retry in the sync log.
- Sync now sends everything that already exists: customers, issued invoices, settled payments and issued credit notes (live subscriptions for HubSpot), skipping anything already sent, with a progress bar.
Connecting
- Open Settings → Integrations in the environment you want to connect. Sandbox environments connect to the vendor's sandbox (a QuickBooks sandbox company, the Xero Demo Company, a HubSpot developer test account), and Production environments to live books — the same split as Stripe's test and live modes. Connect each environment separately.
- Press Connect, sign in at the vendor and choose the company. You come back to the Integrations screen.
- Press Mapping and choose where things post: the QuickBooks product or service (or Xero revenue account) for invoice lines, the account payments land in, and a vendor tax code for each Base316 tax rate. For HubSpot, choose the deal pipeline and stages.
- Press Sync now to send what already exists.
Disconnect stops syncing at once, cancels anything queued and revokes the token at the vendor. What is already in the books stays there. Reconnecting the same company resumes where it left off; connecting a different company starts clean, because the old company's ids mean nothing in the new one.
If the vendor stops renewing access (the person who connected it left, or revoked the app), the integration shows Needs reconnecting. Work keeps queuing and is sent once somebody presses Reconnect.
Checking from code
GET /v1/integrations (secret key) lists each vendor's status, mode, the
connected account, when it last synced, the last error and how many jobs are
pending or failed — base316.integrations.list() in the TypeScript SDK.
Connecting is an OAuth consent a person gives in the dashboard, so there is no
API to create a connection.
Operating Base316: going live
Base316 itself is registered once with each vendor as the developer app every customer authorizes. Until a deployment has the app's credentials, the integration says Not configured on this deployment and names the variables.
The redirect URI is the same for sandbox and live; register exactly one per
deployment. They are {API URL}/integrations/oauth/{vendor}/callback:
| Deployment | QuickBooks | Xero | HubSpot |
|---|---|---|---|
| QA | https://qa-api.base316.com/integrations/oauth/quickbooks/callback | https://qa-api.base316.com/integrations/oauth/xero/callback | https://qa-api.base316.com/integrations/oauth/hubspot/callback |
| Production | https://api.base316.com/integrations/oauth/quickbooks/callback | https://api.base316.com/integrations/oauth/xero/callback | https://api.base316.com/integrations/oauth/hubspot/callback |
| Local | http://localhost:3110/integrations/oauth/quickbooks/callback | http://localhost:3110/integrations/oauth/xero/callback | http://localhost:3110/integrations/oauth/hubspot/callback |
(The billing deployment, if it should sync too, adds the same paths on
https://billing-api.base316.com.) A deployment that is not
BASE316_ENV=production — QA included — only ever uses sandbox credentials.
The variables go into the API and scheduler workloads' secrets
(base316-secrets); the scheduler is what sends.
QuickBooks Online (Intuit Developer)
- At developer.intuit.com, create an app with the Accounting scope
(
com.intuit.quickbooks.accounting). - Under Development → Keys & credentials, add the redirect URIs above and
copy the development keys into
BASE316_QUICKBOOKS_SANDBOX_CLIENT_IDandBASE316_QUICKBOOKS_SANDBOX_CLIENT_SECRET. These work only against sandbox companies (sandbox-quickbooks.api.intuit.com). - For live books, complete Intuit's production questionnaire, add the
production redirect URI under Production → Keys & credentials, and set
BASE316_QUICKBOOKS_CLIENT_IDandBASE316_QUICKBOOKS_CLIENT_SECRETon the production deployment. - Base316 pins
minorversion=75and sendsrequestidon every create.
Xero (Xero Developer)
- At developer.xero.com, create a Web app. New apps get granular scopes;
Base316 requests
openid profile email offline_access accounting.contacts accounting.invoices accounting.payments accounting.settings.read. - Add the redirect URIs above, and set
BASE316_XERO_CLIENT_IDandBASE316_XERO_CLIENT_SECRET. Xero has no separate sandbox keys — a sandbox environment authorizes the same app against the Demo Company — butBASE316_XERO_SANDBOX_CLIENT_ID/_SECRETare honoured if you register a second app for QA. - Xero limits each organisation to 60 calls a minute and 5,000 a day; the sync waits out a 429 rather than failing.
HubSpot (public app)
- In a HubSpot developer account, create a public app using OAuth, with
the scopes
oauth crm.objects.companies.read crm.objects.companies.write crm.objects.deals.read crm.objects.deals.write crm.schemas.companies.read crm.schemas.companies.write crm.schemas.deals.read crm.schemas.deals.write. The schema scopes let the first sync create thebase316property group. - Add the redirect URIs above, and set
BASE316_HUBSPOT_CLIENT_IDandBASE316_HUBSPOT_CLIENT_SECRET. Sandbox environments connect to a developer test account through the same app. - Tokens are exchanged and refreshed at HubSpot's v3 endpoint
(
/oauth/v3/token); v1 stops working on 16 February 2027.
Tokens are encrypted at rest under BASE316_ENCRYPTION_KEY and are moved by a
key rotation like every other stored credential.

