Skip to content
Desktop Accounting API

Change feed

The change feed watches QuickBooks resources for you. Subscribe a project to resources such as customers and invoices, and every connected end user’s company file is read on a schedule. Whenever records changed or were deleted since the last read, you receive a data.changed webhook with their IDs. Fetch the records you care about through the API.

It is off by default. Every poll opens a QuickBooks session on your customer’s computer, so turn it on only for the resources you need, at the interval you need.

  1. You create a change feed subscription with the resources to watch and an interval of at least 15 minutes.
  2. Every 15 minutes, each connected end user whose interval has passed is polled: for each resource, the records modified since the previous poll, and the records deleted in the same window.
  3. New changes become data.changed events, delivered to every webhook endpoint subscribed to data.changed.
  4. Your handler fetches the records by ID, or deletes its copy, and answers 2xx.

Polls are the lowest priority in the end user’s queue. They run only when none of your own requests is waiting, so they never delay your traffic.

import { DesktopAccountingApi } from "@desktopaccountingapi/quickbooks-desktop";
const client = new DesktopAccountingApi();
const subscription = await client.changeFeedSubscriptions.create({
resources: ["customers", "invoices"],
intervalMinutes: 15,
});
console.log(subscription.id, subscription.resources);

Then add a webhook endpoint with data.changed among its event types, or add it to an existing endpoint.

Field Meaning
resources Resources to watch, each listed once, named as in the SDK and operation IDs: customers, invoices, billCheckPayments, salesRepresentatives. Every resource whose list filters by modification time and that has a typed delete operation can be watched. Cross-type lists (items, entities, transactions) are not resources of their own; subscribe to the typed resources, such as serviceItems and inventoryItems.
intervalMinutes Minutes between polls of each end user, 15 to 1440. Default 15. When two subscriptions watch the same resource, the shorter interval applies.
enabled false pauses polling and keeps the settings.

A project can have up to 10 subscriptions (422 CHANGE_FEED_SUBSCRIPTION_LIMIT_REACHED). Test and production projects have separate subscriptions. Every create, update and delete is recorded with the API key and request ID that made it. Read-only keys can list and retrieve subscriptions, not change them.

{
"id": "evt_01j9x7c2m4p6r8t0v2x4z6b8dc",
"type": "data.changed",
"timestamp": "2026-10-12T10:17:04.211Z",
"projectId": "proj_01j9...",
"data": {
"objectType": "data_change",
"connectionId": "conn_01j9...",
"endUserId": "eu_01j9...",
"resource": "customers",
"changed": ["80000001-1700000000", "80000007-1700000123"],
"deleted": ["8000000A-1700000456"],
"windowStart": "2026-10-12T10:00:00.000Z",
"windowEnd": "2026-10-12T10:17:00.000Z",
"page": 1,
"pageCount": 1
}
}
Field Meaning
resource The watched resource.
changed IDs of records created or modified in the window (including deactivated list records). Fetch them with the resource’s retrieve or list operation.
deleted IDs of records deleted in the window. Delete your copies.
windowStart, windowEnd The time window the poll covered (server time, UTC).
page, pageCount One event carries at most 100 IDs. A poll with more changes sends several events for the same window, numbered from 1.

Events carry IDs only, never record bodies or other accounting data. A record that changed several times between polls appears once.

import { DesktopAccountingApi } from "@desktopaccountingapi/quickbooks-desktop";
const client = new DesktopAccountingApi();
interface DataChange {
endUserId: string;
resource: string;
changed: string[];
deleted: string[];
}
export async function handleDataChanged(body: string, headers: Record<string, string>, secret: string) {
const event = await client.webhooks.verify<DataChange>(body, headers, secret);
if (event.type !== "data.changed" || event.data.resource !== "customers") return;
const qb = client.forEndUser(event.data.endUserId);
for (const id of event.data.changed) {
const customer = await qb.qbd.customers.retrieve(id);
console.log("upsert", customer.id, customer.revisionNumber);
}
for (const id of event.data.deleted) console.log("delete", id);
}
  • Deduplicate on webhook-id. Delivery is at least once. A poll that is repeated sends the same event IDs, and a resent delivery keeps its webhook-id.
  • Order does not matter. Events are not ordered. Store each record with its revisionNumber; when you fetch a record, keep the copy with the newer revisionNumber and ignore an older one.
  • Answer fast. Queue the fetches and answer 2xx. Your fetches go through the end user’s queue like any other request.

Each poll covers the time since the previous one, plus a 2-minute overlap. QuickBooks stamps changes with the clock of the customer’s computer, which can be a few minutes off. The overlap means a computer clock up to 2 minutes behind our servers never loses a change, and a clock that is ahead only moves a change to the next poll. Records seen in the overlap with the same ID and revisionNumber are not reported twice.

Window bounds go to QuickBooks in the computer’s local time, with its UTC offset, which the feed learns from the timestamps QuickBooks returns. Until it has seen one, a poll reaches 14 hours back and 14 hours ahead, so no change is lost whatever the computer’s time zone. Its first events can include records changed shortly before the subscription, and a record can be reported one poll early; it is not reported again.

When a poll returns more changes than one QuickBooks page holds, the feed splits the window and reads each part, so no change is skipped however busy the company file is.

  • The end user’s computer is offline, or the Web Connector is not running: no poll. The next poll after it comes back covers the whole time since the last successful poll.
  • The connection is disabled: no poll until it is enabled again; then the gap is covered.
  • A write’s outcome is unknown (uncertain writes): polls wait until the connection stops holding later writes back. Your own reads still run.
  • A poll fails (QuickBooks busy, the session ended, an unreadable answer): nothing is reported for that window, and the next poll reads it again.
  • Missed schedules: a late poll covers everything since the previous successful one.

Polls run when the Web Connector checks in, so their timing follows the end user’s Web Connector schedule.

Changes made before a subscription (or a resource) was added are not reported: the feed starts at that time, or when the end user finishes setup if that is later. There is no historical backfill; use the incremental sync pattern for the first copy, then let the feed keep it current.

Removing a resource, deleting a subscription or disabling it stops the polls: a poll already queued for it is canceled before it reaches QuickBooks, so no data.changed event follows and the poll is not billed. Adding the resource back, or enabling the subscription again, starts over from that moment. Resetting an end user’s company file identity, or moving the end user to another project, also starts over.

QuickBooks keeps deletions for 90 days, so an end user offline for longer needs a full comparison by ID.

A successful poll of a production company file counts as a data request, exactly like a read you send yourself: it makes the file active for that day (Billing). Polls of Intuit sample company files, exempt files and anything in a test project are free. While your organization’s production data requests are blocked for billing, polls of billable company files pause and resume where they stopped once billing is fixed.