Skip to main content

Tax reconciliation

What is being compared

For every order, the engine holds two numbers: what Shopify collected at checkout, and what the engine says was owed based on your registrations, rates and tax classes. Reconciliation is the verdict on the difference.

A per-line tolerance of 0.02 in the transaction currency absorbs cross-rounding between two independent engines — that is a rounding difference, not a discrepancy.

The six verdicts

VerdictMeaningTypical cause
MatchWithin toleranceHealthy
OverShopify collected more than owedA stale rate, or a rate override that has not propagated
UnderShopify collected less than owedA rate change you applied here but not in Shopify
UnregisteredTax was charged where you have no registrationMissing registration, or a registration whose active dates have lapsed
Not chargedThe engine says taxable, Shopify collected nothingAlmost always an unmapped product tax class
UnresolvedThe engine could not resolve the jurisdictionIncomplete address data

Unresolved is the one to take seriously. It is not "small" — it is unknown. The engine emits it when it could not determine what was owed at all, which means the delta is zero by construction. That is why unresolved can never be automatically demoted to immaterial: doing so would let a period finalise with an unknown liability silently treated as nil.

Dispositions

A disposition is what a human — or a governed rule — decided about a verdict.

DispositionMeaning
ImmaterialBelow your materiality policy. Never blocks a close
ResolvedDispositioned with a typed resolution
WaivedAccepted as-is. May require a second approver
ReopenedA recompute changed the money after a resolution
SupersededReplaced by a newer revision of the order

The verdict is preserved on the row regardless of the disposition. A disposition that overwrites its verdict makes historical questions unanswerable — which is precisely the sort of question an auditor asks.

Cause clustering

At real order volume, a flat list of five thousand discrepancies is not a worklist. Rows are grouped by cause into readable cluster keys:

rate_low | india_gst | 29@18%
unmapped_product | us_sales_tax | CA

Keys are readable rather than hashed, because they appear in logs, in suppression scopes and in support conversations. Working a cluster fixes hundreds of rows at once — a cluster of unmapped_product is one tax-class mapping, not five hundred individual decisions.

Root-cause detection is helped by two fields carried on every computed line: the product ID, and whether the tax class came from a real mapping or from the fallback. Both are descriptive and deliberately excluded from the idempotency hash, so adding them did not invalidate existing computations.

Materiality policy

Your materiality policy decides what is too small to be worth anyone's time.

SettingWhat it does
Absolute minimumAmounts below this are immaterial
Relative thresholdA percentage of the order
Block statusesWhich verdicts block a period close — default under, not charged, unresolved
Waive approval thresholdAbove this amount, a waiver needs a second approver
Exposure alert thresholdWhen total exposure raises an alert
Over-collection does not block a close by default

Over-collection is a customer-refund problem, not a filing problem — you are not under-declaring anything. Blocking a close on it is what drove merchants to bulk-waive, and bulk-waiving is worse than having no gate at all. It remains visible and workable; it just does not stop you filing.

Policy amounts are authored in your chosen currency but compared on a normalised anchor. A ₹1 floor enforced as if it were $1 would be roughly eighty times too permissive. Where a policy amount cannot be converted, the built-in floor applies — which is conservative, meaning more rows stay blocking, never fewer.

Suppression rules

A suppression rule silences a known, understood cause. The argument for rules over waiving is simple: revoking a rule reopens the rows it suppressed; a waiver cannot be undone that way.

Rules are scoped by cause, regime, jurisdiction and amount, and are FX-anchored like policy amounts. A rule whose threshold cannot be anchored is skipped rather than applied at the wrong scale — silently applying a ₹-scale threshold to a USD anchor would suppress far more than intended.

Rules can carry an expiry, and expired rules are swept nightly.

Separation of duties on waivers

Above your waive-approval threshold, a waiver needs a second person.

The control is on identity, not on permission. Holding both the requesting and the approving permission does not let one person do both — the approver must be a different user. And where user identity is not available (RBAC not yet configured, so every actor collapses to one account), the request is refused before staging rather than being frozen pending forever.

Order edits and supersession

When an order is edited in a way that changes what tax was owed, the engine recomputes and marks the prior revision superseded.

Supersession is applied to both the reconciliation row and the underlying computation. That second half matters: statutory returns, evidence packs, amendments, e-invoice reconciliation and nexus turnover all sum computations over a date window, so marking only the reconciliation would fix the worklist while double-counting the edited order on the return itself.

Not every edit triggers a recompute. A tax signature and an order signature are compared first, so fulfilment and tag edits do not recompute — but an address change does, because it moves what you owe even when what Shopify collected has not changed.

Sticky resolution

A resolution stays resolved. Shopify redelivers order webhooks on any non-2xx response, and persistence returns successfully on a deduplicated write — so naive logic flips a resolved row back to under on every redelivery while leaving the resolver's name attached.

Only a real change in the money (more than half a cent) reopens a resolved row, and when it does, the disposition fields are cleared properly so later verdict changes can still land.

Working the tab

FeatureWhat it does
FiltersVerdict, disposition, regime, jurisdiction, period, owner, materiality
AgingHow long discrepancies have been open
Drill-downThe full computation behind a row
AssignmentGive a row or a cluster an owner
Bulk resolveDisposition a selection at once
Saved viewsYour filter combinations, kept
ExportCSV or JSON of the current filtered set
TrendsExposure over time, and open-versus-closed

Exposure snapshots are written nightly so the trend is real history rather than a recomputation of the current state.

Filing gate

Open reconciliations in your policy's blocking set prevent a period from being finalised. That is the point of the tab: the return should not be prepared over discrepancies nobody has looked at.

The gate reports blockers and warnings separately, and tells you which cluster is responsible — so unblocking a close is usually one fix, not a hundred.

Plan tiers

CapabilityFreeStarterProAgencyEnterprise
Verdicts, worklist, drill-down
Cause clustering
Materiality policy
Suppression rules
Assignment, bulk actions, saved views
Separation of duties on waivers
Exposure trend and snapshots

Best practices

  • Work clusters, not rows. One unmapped product tax class can account for a four-figure row count.
  • Write a suppression rule instead of waiving. It is reversible, it is scoped, and it documents the decision.
  • Never bulk-waive to unblock a close. You will be waiving the one real under-collection along with the noise.
  • Set materiality deliberately. Too low and the tab is unusable; too high and the gate stops protecting you.
  • Investigate unresolved first. It is the only verdict that means "we do not know", and it cannot be demoted away.

See also