Service · Shopify · QuickBooks · ShipStation

Stop retyping tracking
numbers. Forever.

If a human in your warehouse is reading a tracking number off ShipStation and pasting it into a QuickBooks invoice — across hundreds of orders a week — that's a build, not a process problem. One Python automation, running hourly, idempotent. Backfilled 361 of 383 invoices in 18 minutes on first run.

Backfill match rate
94%
First-run invoices fixed
361
Daily minutes recovered
~60
Run frequency
Hourly
The pain

The hidden tax in every shipment.

Every shipment incurs the same hidden cost: someone reads the tracking number off the ShipStation screen, finds the matching invoice in QuickBooks, pastes it into the TrackingNum field, saves the invoice. About 30 seconds per order.

At 50 shipments a day across six storefronts, that's an hour of warehouse time burned on data entry — and a non-trivial error rate. Typos. Wrong invoice. Tracking number that never makes it onto the customer's PDF.

The team knows it's wrong. They don't have time to fix it. So it keeps happening.

Why it's not as simple as it looks

Three obstacles a naive script runs into immediately.

"For each ShipStation label, find the matching QuickBooks invoice and write the tracking number on it" is the obvious one-line spec. The implementation runs into three real problems:

Obstacle 1

ShipStation doesn't know your stores

ShipStation V2 attaches a numeric external_order_id to each label, but no store_id. With multiple Shopify storefronts feeding one ShipStation account, every lookup is a multi-tenant problem.

Obstacle 2

No canonical bridge field

QuickBooks invoices are created from Shopify orders by a third-party sync tool. The Shopify order number lands somewhere on the invoice — but where, and in what format?

Obstacle 3

PrivateNote is not queryable

The QuickBooks API supports a SQL-like query language. PrivateNote is projectable (you can SELECT it) but not queryable (you can't WHERE on it). The obvious bridge field is unusable for direct lookups.

How it works

In-memory index. Cascade lookup.

The shipment object — one level above the label in ShipStation's hierarchy — carries a shipment_number field that's exactly the prefixed Shopify order name: RB32708 for Raw Blend, CE5776 for Commercial Equipment, UK2534 for U-Konserve. The two-letter prefix unambiguously identifies the source store. No multi-tenant fan-out needed.

For the bridge field, the third-party sync tool writes the prefixed Shopify order name into the QuickBooks invoice's PrivateNote field exactly. To dodge the not-queryable problem, each run pages through invoices created in the last 60 days (MetaData.CreateTime is queryable, so it bounds cleanly) and builds an in-memory index. For ~900 invoices, it costs ~5 seconds and ~8 paginated queries.

The lookup cascades through three fallback strategies — regular Shopify orders, full INV-XXXX typed warranty entries, and bare numeric warranty entries — all caught in the same function.

shipstation_tracking_sync.py · lookup cascade python
def find_invoice_for_shipment(shipment_number, by_private_note, by_doc_number):
    """Lookup priority:
       1. PrivateNote == shipment_number          (regular Shopify orders)
       2. DocNumber  == shipment_number           (warranty: full INV-XXXX typed)
       3. DocNumber  == "INV-" + shipment_number  (warranty: bare number)
    """
    matches = by_private_note.get(shipment_number) or []
    if matches:
        return "private_note", matches
    matches = by_doc_number.get(shipment_number) or []
    if matches:
        return "doc_number", matches
    if shipment_number.isdigit():
        matches = by_doc_number.get(f"INV-{shipment_number}") or []
        if matches:
            return "doc_number", matches
    return "none", []
Idempotency

Safe to re-run. Always.

An automation scheduled hourly has to survive partial runs, flaky networks, half-finished executions. Three guards make every run safe to repeat with zero side effects:

Guard 1

Watermark file

cache/shipstation/state.json records last_processed_at. Each run pulls labels created since that timestamp, then advances on success.

Guard 2

Already-set check

If invoice.TrackingNum already equals the label's tracking number, skip. No write, no SyncToken churn, no audit-log noise.

Guard 3

Multi-match guard

If a shipment_number matches more than one invoice (duplicate invoices in QB — it happens), log the conflict and skip. Never risk writing to the wrong one.

First real run

Backfilled 30 days. 18 minutes.

The backfill output, end-to-end, on the first real production run against the client's real data:

shipstation_tracking_sync.py · backfill run
$ python -m automations.rawblend.shipstation_tracking_sync --backfill 30
loading ShipStation labels created 2026-04-07 → 2026-05-07 ...
383 labels loaded across 6 storefronts
building QuickBooks index (60 days, 911 invoices) ...
index ready in 4.8s
matching shipments to invoices ...
361 invoices matched → tracking number written
20 unmatched (warranty customer-name strings — expected)
2 multi-match (duplicate invoices in QB — skipped, logged)
watermark advanced to 2026-05-07T04:48:30Z
done in 17m 42s
Screenshot · Flask dashboard · ShipStation sync card showing last-run summary

From that point forward, the warehouse stopped touching tracking numbers. The hourly cron handles every new shipment as it ships. The bookkeeper now trusts that tracking numbers are correct — they always come straight from the source.

What you get

Built to own and forget.

A single Python module living inside your automation platform (or as a standalone script). Drops into Windows Task Scheduler or any cron. No SaaS subscription. No vendor lock-in.

Source code, deployment guide, and a runbook — everything your team needs to operate it without a developer in the room.

Onboarding for one storefront from $4,800 + GST. Multi-store builds scoped on the call. Discovery is free; we'll tell you in 30 minutes whether your stack is workable and what the actual cost is.

Metric Before After
Tracking write source Human reading ShipStation screen ShipStation V2 API
Time per shipment ~30 sec manual 0 sec (automated)
Error rate Typos, wrong invoice 0% — always from source
Run frequency When staff get to it Hourly, autonomous
Visibility Nobody knows until a customer complains Dashboard card · failed runs surfaced
Cost over 12 months ~$15,000 of warehouse time One-off build + zero recurring
Related Builds

Other angles on the
same platform.

Every page on this list is drawn from the same Raw Blend build — different problems, same operating model.

Want this build
in your warehouse?

If your shipping volume is anywhere near Raw Blend's, the build pays for itself in the first month. Tell us about your setup — Shopify + ShipStation + QuickBooks is the standard combo we ship against, but other stacks work too.

Discovery is free · Quote in 48 hours · No retainer required