Skip to content

QuickBooks Online

The QuickBooks Online (QBO) integration is OpsMerge's primary accounting bridge for UK MSPs. Invoices generated in OpsMerge push to QBO; payments recorded in QBO flow back as paid status in OpsMerge.

A Xero integration — same architecture, different vendor — is coming soon for MSPs who don't use QuickBooks.

What syncs

  • Invoice push: OpsMerge → QBO. When an invoice moves from Draft to Open in OpsMerge, it pushes to QBO as a QuickBooks Invoice.
  • Payment pull: QBO → OpsMerge. Payments recorded in QBO (manual or via QBO's payments integration) flow back to OpsMerge as "invoice paid".
  • Daily reconciliation: a once-a-day worker compares totals between OpsMerge and QBO and surfaces any drift.

We do not sync:

  • Customers are created in OpsMerge first, then on the first invoice push the matching QBO customer is found (by name) or created. Same direction only.
  • Service items / products — the OpsMerge Service field maps to a QBO Item, you set the mapping in OpsMerge.
  • Tax codes — UK QBO has its own tax setup. OpsMerge maps tax codes by reference (set during integration setup).

One-time setup

You need an Intuit Developer app — see QBO Intuit Developer setup for the registration steps, or skip if you're using OpsMerge's shared dev app (default for most MSPs).

  1. Settings → Integrations → QuickBooks → Connect.
  2. Click Connect to QuickBooks.
  3. OAuth into the Intuit screen, choose your company (realm).
  4. Grant the requested scopes — at minimum: company info, accounting, payments read.
  5. You're redirected back. OpsMerge shows "Connected to: <Your QBO company name>".

Now configure mappings:

  • Service mapping: every OpsMerge service maps to a QBO Item. Settings → PSA → Services → Map.
  • Tax code mapping: each OpsMerge tax code (e.g. "VAT Standard 20%") maps to a QBO tax code. Settings → PSA → Tax codes → Map.
  • Default income account: when a new QBO item is created automatically, this account holds the revenue.

A pre-flight check at the top of the QBO settings page tells you what's mapped and what isn't.

Invoice push

Once mappings are in place, invoice push is automatic:

  1. OpsMerge invoice moves from Draft to Open.
  2. Background worker fires the push (within a minute, usually within seconds).
  3. Push includes: invoice number, customer, lines (with item refs and tax codes), totals, due date.
  4. QBO confirms. The OpsMerge invoice records the QBO invoice ID.

For UK QBO, OpsMerge computes tax via per-line TaxCodeRef — QBO does the actual tax calculation server-side. We deliberately don't send TxnTaxDetail.TaxLine (UK QBO would reject it).

If a push fails, the OpsMerge invoice records the error. The QBO sync log surfaces all errors:

Settings → Integrations → QuickBooks → Sync log.

The most common failure modes are documented under Common issues.

Payment pull

When a payment is recorded in QBO (manually, or via QBO's own payment processor):

  1. QBO fires a webhook to OpsMerge.
  2. OpsMerge verifies the HMAC signature.
  3. OpsMerge looks up the QBO invoice ID, finds the OpsMerge invoice, marks it Paid.
  4. A Payment record is created in OpsMerge with the QBO payment reference.

The webhook is HMAC-verified — we don't trust the body without it. Set up at: Intuit Developer dashboard → app → Webhooks → set URL to OpsMerge's QBO webhook endpoint.

If you don't want to set up webhooks, the daily reconciliation worker eventually catches payments — but with up to a day's lag.

Retry and throttling

QBO has a per-realm rate limit (around 500 requests/minute by default; lower in some cases).

OpsMerge applies a per-realm governor:

  • Token bucket per realm — we won't exceed the limit even with multi-invoice runs.
  • A semaphore per realm — only one push in flight at a time per realm, to avoid race conditions.
  • Retry-After honoured strictly when QBO sends one.
  • 5xx errors are retried with exponential backoff + jitter.
  • 4xx errors surface immediately (they're not retriable — the request itself is wrong).

You can monitor the throttle state at Settings → Integrations → QuickBooks → Sync state.

OAuth token expiry

QBO's OAuth tokens expire if the connection is unused for 100 days.

For an active integration this is irrelevant — token refresh happens every push. But if you go quiet for over 100 days, the integration disconnects and the next push fails with "token expired".

To reconnect: Settings → Integrations → QuickBooks → Reconnect.

We email you when the integration disconnects so you don't only discover it at month-end.

Disconnect behaviour

When you intentionally disconnect:

  • All OpsMerge → QBO breadcrumbs are cleared (customer external IDs, invoice external IDs).
  • Existing OpsMerge data stays (the invoices are still there; they just no longer have QBO links).
  • Reconnecting later starts fresh — OpsMerge looks up matches by name and adopts orphaned records.

Migration 000177 (shipped 2026-05-06) dedupes legacy duplicates from prior reconnect cycles where this logic was imperfect.

Webhook scope

QBO sends webhooks for: Payment, Invoice, Customer change events. OpsMerge subscribes to Payment for the pull-back flow.

You set the verifier token in OpsMerge (Settings → Integrations → QuickBooks → Webhook verifier). The Intuit dashboard generates the token; we use it to verify signatures.

Common patterns

"I want to push invoices automatically when the recurring run fires"

That's the default behaviour. Recurring invoices generated in OpsMerge move to Open immediately and push to QBO.

If you'd rather review-then-push: Settings → PSA → Recurring → Auto-send / push → Off. Then your recurring invoices land as Draft; you review each, mark Open, and they push.

"Multiple QBO realms (one per region)"

OpsMerge supports one QBO connection per tenant. For multi-region MSPs with multiple QBO accounts, you currently need to either consolidate into one QBO realm (the cleanest path) or run separate OpsMerge tenants per region. We're aware this is a constraint and it's on the roadmap.

"I don't want to push every invoice to QBO"

Per-invoice opt-out: Invoice → Actions → Don't push to QBO. Useful for, e.g., a personal-favour discount you're invoicing through OpsMerge for tracking but not booking through accounting.

"Sandbox testing"

OpsMerge supports a global sandbox / production switch in the QBO integration. Set it to sandbox, OAuth into your QBO sandbox realm, push test invoices — they go to sandbox, not your live QBO. Flip back to production when you're ready.

Common issues

Invoice push fails with "missing service" or "missing TaxCodeRef". Every invoice line needs service_id (mapped to a QBO Item) and tax_code_id (mapped to QBO tax code). OpsMerge's invoice editor enforces these on save for UK tenants, but legacy lines from before this guard may lack them. Edit the line, fill in the missing field, save.

"Invalid TxnTaxDetail" on UK push. Don't send TxnTaxDetail — UK QBO computes tax from per-line TaxCodeRef. If you've customised the QBO push code, revert.

Push succeeded but invoice doesn't appear in QBO. Look at the QBO invoice ID stored on the OpsMerge invoice (Audit log → Push events). If it's there, search in QBO by that ID — sometimes the QBO UI's date filter excludes a same-day invoice. The ID is authoritative.

Reconnect created a duplicate customer in QBO. Pre-migration 000177 this could happen. After 000177, we look up QBO customers by name and adopt rather than create. If you see duplicates, manually merge in QBO and tell us — we'll investigate.

Payment came in but invoice still shows Open. Either the webhook isn't configured, or the verifier signature failed. Check Settings → Integrations → QuickBooks → Webhook log. If empty, the webhook isn't reaching us; check the Intuit dashboard webhook URL.

"Rate limited" message in sync log. QBO throttled us; OpsMerge backed off; pushes will resume. No action needed. If it's persistent (several hours), there's something funny with your realm's specific limit.

pgx COUNT scan errors in the worker logs. Internal — pgx v5 refuses to scan int8 into bool, we use EXISTS/BOOL_OR or scan COUNT into int and derive. This shouldn't surface to you; if it does, it's a bug.

Next

OpsMerge is a product of Brindleford Technologies Ltd, company number 16871436, registered in England and Wales.