Guide

# Keep another system in sync

Webhooks announce order and quote changes the moment they happen; the query feed is the ledger that makes your copy complete. Build the loop on the feed first, then let deliveries trigger it.

## Use the query feed, not the list

Both endpoints return orders. Only one is safe to page through while things are changing underneath you.

GET /v1/orders
Most recently updated first
Good for looking one order up. The ordering shifts as orders change, so a document can move between pages while you are reading.

GET /v1/orders:query
Oldest change first
Stable ordering plus an `updatedSince` window. This is the one to build a poller on.

## The loop

Ask for everything since your checkpoint, page to the end, then move the checkpoint to the last document you actually processed — not to “now”. Overlap the window by a minute or two: `updatedSince` is inclusive, so re-seeing one document is normal and your handler should be idempotent anyway.

```js
let since = checkpoint.read();   // e.g. 2026-07-29T02:00:00Z
let token = "";

do {
  const page = await get("/v1/orders:query", {
    updatedSince: since, pageToken: token, pageSize: 100
  });
  for (const order of page.orders) {
    upsert(order);               // keyed on orderId
    since = order.lastUpdatedAt;
  }
  token = page.nextPageToken;
} while (token);

checkpoint.write(since);
```

## Let webhooks trigger the loop

Subscribe an HTTPS endpoint with `POST /v1/webhooks`, picking the event types you care about — created, updated, status changes and archival on orders; created, updated, status changes and conversion on quotes. Store the `whsec_` secret from the create response: it is shown exactly once, and the full string is the HMAC-SHA256 key for the `X-Factory-Signature` header on every delivery. Webhook operations are administrator-only — subscribing, listing deliveries, all of it — so run this part of the loop with an administrator's key.

Treat a delivery as a trigger, not a source of truth — the body names the document that changed by id (`data.order.id` or `data.quote.id`, plus `data.previous` on a transition) and never carries the document itself: acknowledge it with a 2xx, then run the same checkpointed loop. Failed deliveries retry for about ten hours across six attempts before they exhaust, and a subscription with nothing delivered successfully for seven days is disabled until you re-enable it — so keep a slow timer on the loop as the backstop. `GET /v1/webhook-deliveries` lists every attempt with the status your endpoint returned, which makes a missed event a lookup rather than a guess.

## Two feeds, one document

Quotes and orders are separate feeds and a document lives in exactly one at a time. Poll both, with their own checkpoints.

> **The migration to expect.** When a quote is accepted in the Factory app it becomes an order. It leaves the quote feed with no tombstone — no final deleted record, it simply stops updating — and appears on the order feed sharing the same 26-character suffix. A quote that goes quiet is reconciled by swapping `quote_` for `order_` and checking the order feed for that id.

## Backfilling a bounded window

Pair `updatedSince` with `updatedBefore` to walk a closed window rather than everything up to now — the way to backfill history in chunks without one enormous run, or to re-pull a single day you suspect you mishandled. The lower bound is inclusive, the upper bound exclusive, so consecutive windows tile without overlapping.

```bash
GET /v1/orders:query?updatedSince=2026-07-01T00:00:00Z&updatedBefore=2026-08-01T00:00:00Z
```

## Archiving arrives in the feed

Pass `includeArchived=true` and the feed carries live and archived orders together. Because archiving stamps the order's update time, you see it happen in-band: the order comes round again with `isArchived: true` and a fresh timestamp, and a restore comes round with false. Your existing upsert handles both — there is no second feed to reconcile and no deletion to infer.

Leave the flag off and archiving looks like a document that simply stopped updating, which is indistinguishable from one that is merely quiet. If your mirror needs to know, turn it on.

## What never shows up

- Work-in-progress documents that have not yet become a quote or an order.

- Quotes on the order feed, or orders on the quote feed. Ever.

- Deletions, as anything other than an absence.

## Narrowing the feed

On top of the update window, both feeds accept a creation-time window — `createdAfter` (inclusive) and `createdBefore` (exclusive) — which combines with it: recent changes to orders created in a period. The order feed also filters by customer, reference substring, draft or submitted, workflow status id or name, payment status, received status, fulfilment method, and a required-by window (`requiredAfter`/`requiredBefore`). Discover your account's status ids from `GET /v1/company/order-statuses` — they are per-account, so do not hard-code them.

---

Source: https://developer.factory.app/guides/keep-in-sync · Factory Sales API v1
