QuickBooks Web Connector and qbXML, Explained: How QuickBooks Desktop Talks to Other Software

QuickBooks Desktop has no web API. Other software reaches it through a small Windows program that polls a SOAP endpoint and passes XML messages to the company file. Here is how that loop works, what each callback must return, and what breaks, from a bridge we built and tested against Intuit's own guide.

Oct 21, 2026

A desktop tower and a ledger connected through a small polling box to a cloud server, with XML envelopes travelling both ways

The QuickBooks Web Connector is a small Windows program that sits next to QuickBooks Desktop and, on a schedule, calls a SOAP web service you host. Your service hands it qbXML requests ("list the active customers", "add this invoice"), the Web Connector passes them to the open company file, and sends QuickBooks' qbXML responses back to you. QuickBooks Desktop has no web API of its own, so this loop is how a web application, an ERP, a CRM or an AI assistant reaches it. Below: the callbacks, the .qwc file, the qbXML itself, and what breaks, from a bridge we built and tested against the protocol in Intuit's own programmer's guide.


How the Web Connector works

The direction surprises most developers: your service never calls QuickBooks. The Web Connector calls you. It polls on a schedule, so it works from behind a firewall without opening any port on the accounting machine.

One run, start to finish:

  1. The Web Connector calls authenticate with the user name and password configured for your service.
  2. If you accept, it opens the company file and calls sendRequestXML. You return a qbXML request.
  3. It passes your request to QuickBooks and returns the answer through receiveResponseXML. You return how far along you are, as a percentage.
  4. Below 100, it calls sendRequestXML again. At 100, it stops and calls closeConnection.
  5. If you return a negative number at any point, it calls getLastError and shows your message to the user.

The six callbacks your service must answer

Intuit's QBWC Programmer's Guide (version 2.0, still the reference) lists six required methods, all in the http://developer.intuit.com/ namespace:

CallbackWhat the Web Connector sendsWhat you return
authenticateuser name, passwordan array: a session ticket, then "" (use the open company file), none (nothing to do), nvu (bad credentials) or busy
sendRequestXMLticket, company file, country, qbXML major and minor versiona qbXML request, "" if there is nothing to send, or NoOp to pause for 5 seconds
receiveResponseXMLticket, QuickBooks' qbXML response, an HRESULT and message if the request processor failedpercent done (100 means finished), or a negative number on error
connectionErrorticket, HRESULT, messagedone to stop, "" to retry with the open company file, or a company file path to try instead
getLastErrorticketthe message to show the user
closeConnectionticketa status message, shown in the Web Connector's status column

Optional ones exist too: clientVersion to reject an old Web Connector, a server version callback, and three for interactive sessions.

Two details from building it:

  • The guide contradicts itself on authenticate. Its prose describes the returned array as if the first element were the company file. Its own sample code, and every other callback, treat element 0 as the session ticket and element 1 as the company or status. Follow the sample.
  • The server version callback has two names. The guide's reference calls it getServerVersion; community implementations answer serverVersion. Our endpoint answers both.

The .qwc file

The QuickBooks user installs your service by adding a small XML file to the Web Connector. It tells it where to call and how often:

<?xml version="1.0"?>
<QBWCXML>
  <AppName>quickbooks-bridge</AppName>
  <AppID></AppID>
  <AppURL>https://bridge.example.com/qbwc</AppURL>
  <AppDescription>Syncs customers and writes approved invoices</AppDescription>
  <AppSupport>https://bridge.example.com/qbwc</AppSupport>
  <UserName>bridge</UserName>
  <OwnerID>{57F3B9B1-86F1-4FCC-B1EE-566DE1813D20}</OwnerID>
  <FileID>{90A44FB5-33D9-4815-AC85-BC87A7E7D1EB}</FileID>
  <QBType>QBFS</QBType>
  <Scheduler>
    <RunEveryNMinutes>15</RunEveryNMinutes>
  </Scheduler>
</QBWCXML>

AppURL must be HTTPS. The Web Connector accepts plain http only for localhost, for development. The password is not in the file: the user types it into the Web Connector, which sends it to authenticate on every run.

What qbXML looks like

A request asking for up to 25 active customers:

<?xml version="1.0" encoding="utf-8"?>
<?qbxml version="13.0"?>
<QBXML>
  <QBXMLMsgsRq onError="stopOnError">
    <CustomerQueryRq requestID="customers">
      <MaxReturned>25</MaxReturned>
      <ActiveStatus>ActiveOnly</ActiveStatus>
    </CustomerQueryRq>
  </QBXMLMsgsRq>
</QBXML>

And one adding an invoice:

<InvoiceAddRq requestID="invoice-1">
  <InvoiceAdd>
    <CustomerRef><FullName>Abercrombie, Kristy</FullName></CustomerRef>
    <TxnDate>2026-10-21</TxnDate>
    <RefNumber>1042</RefNumber>
    <InvoiceLineAdd>
      <ItemRef><FullName>Installation</FullName></ItemRef>
      <Desc>On-site install</Desc>
      <Quantity>2</Quantity>
      <Rate>125.00</Rate>
    </InvoiceLineAdd>
  </InvoiceAdd>
</InvoiceAddRq>

Every response carries a status as attributes: statusCode="0" is success, 1 on a query means nothing matched, anything else is an error with a readable statusMessage.

What breaks

  • Element order. qbXML is validated against a schema that fixes the order of elements. Swap TxnDate and RefNumber and QuickBooks rejects the request. Build requests from typed structures, never by concatenating strings.
  • Version. sendRequestXML tells you which qbXML version the installed QuickBooks supports. Write at that version or below. We cap at 13.0 and drop lower when the installed version reports less.
  • References by name. FullName must match a customer or item in the company file exactly. If it doesn't, QuickBooks answers with statusCode="3140", an invalid reference, and names it. Match names before you send, and report the error rather than retrying.
  • Errors that go nowhere. If receiveResponseXML returns a positive number after a failed request, the Web Connector carries on and the user never hears about it. Return a negative value and let getLastError say what happened.
  • The machine. The Web Connector runs on Windows, next to QuickBooks, and only when QuickBooks can open the company file. If the PC is off, nothing syncs. Design for delays of hours, not seconds.

What we built

Our bridge, open source on GitHub, implements the Web Connector side in Go: the six callbacks plus the optional ones, a queue of qbXML requests per session, typed requests in schema order, and a .qwc generator. It reads customers and writes the invoices waiting in an outbox. We tested it the way the Web Connector drives it: a full session from authenticate to closeConnection, a rejected password, and QuickBooks' error 3140 on an unknown item, which surfaces through getLastError.

What we haven't done yet is run it against a live QuickBooks Desktop company file, which needs a Windows machine with QuickBooks installed. The QuickBooks Online half of the same bridge runs against a live sandbox; that part is in QuickBooks API integration.

If your business still runs QuickBooks Desktop, read what Intuit is ending and what to do about it before you invest in anything new around it.

Related: QuickBooks API integration, QuickBooks MCP, and QuickBooks automation with AI.