Analysis7 min read

Price monitoring: a matching number hid a currency mismatch

A live storefront check found USD and EUR prices labelled GBP in product JSON-LD. Inspect the recordings and validate currency before accepting a price signal.

On this page

A price monitor can extract the right digits and still record the wrong money. In our live checks of one Finisterre product, the HTML served for a US storefront view showed $50.00, while the matching product offer in JSON-LD described 50.0 GBP. Our EUR storefront views showed €55,00 beside an embedded offer of 55.0 GBP. The UK control showed £40 and 40.0 GBP, which agreed.

The practical rule is to hold a price record when its currency conflicts with the selected product's page context. A successful response, parseable JSON and matching amount do not resolve that conflict. We built a small checker and preserved the observations so readers can reproduce the classification before adapting the check to their own monitoring jobs.

What we actually checked

The subject was Finisterre's Barents Beanie in Olive, One Size: SKU 212955, variant 58572724830592. We matched the selected variant input, the product-specific structured offer and the corresponding Ajax variant record. We did not take a category's minimum price, another size, a recommendation or a crossed-out price. The selected Offer parsed successfully; the collector separately recorded an empty JSON-LD block in each page.

Our main experiment compared four storefront contexts, including US, UK and Netherlands views. For each context, we requested the unqualified product URL and the /en-us/ entry twice, with balanced ordering. All observations were collected on 11 September 2026.

The main collection completed 16 product-page observations across 48 GET requests: 40 responses were HTTP 200 and eight were redirects. Every final HTML and product-JSON response was readable. Twelve page observations had a currency disagreement; the four UK observations agreed. The method records 1,344,100 received response-body bytes, including the redirects, with no transport failures or missing byte measurements.

The initial country and browser probes are separate from that total. These observations describe the collection window, not country-wide reliability.

The same variant carried conflicting currency labels

We compared the product's own price block with two explicit currency signals in the same HTML document: the Open Graph price currency and the active storefront currency. Those signals agreed with each other. We then inspected the exact variant's JSON-LD Offer.

Observed storefront contextProduct price blockMatching JSON-LD offerObservations
EUR storefront views€55,0055.0 GBP8
US / USD$50.0050.0 GBP4
GB / GBP£4040.0 GBP4

The number in each embedded offer matched the displayed number; its currency label did not always match. That distinction matters to a price database: 50.0 GBP and 50.00 USD are different records even before any exchange-rate calculation. We did not convert currencies or treat these market views as a price change over time.

Four fresh, restricted browser contexts displayed the same prices and currency disagreements. That first run blocked localization resources. We then repeated the US and Netherlands views while allowing the observed commerce scripts, including the cross-border application, and waited ten seconds after the document loaded. Both disagreements remained. A product-country metadata POST and other resources were still blocked; the four-context run recorded 12 page errors and the expanded run recorded eight. These checks do not prove what a fully loaded shopping session displays. The central finding is the currency conflict within the served HTML. The method retains the restrictions, errors and earlier incomplete calibration.

We have not identified which template, app or configuration produced the currency label. The finding concerns the captured product responses; it is not evidence of a platform-wide Shopify defect.

A market-looking URL did not settle the context

The second pitfall appeared when joining HTML and Ajax data. Shopify documents product monetary fields in the customer's presentment currency and advises using locale-aware URLs. Its example derives the request from window.Shopify.routes.root. A bare product JSON amount does not independently establish its currency. Shopify Product API, Ajax routing.

In eight of the 16 observations, the HTML redirected to a different storefront prefix while the originally requested product JSON kept its own path. All eight original-path JSON amounts differed from the JSON fetched under the effective page prefix. For example, on the US route the unqualified HTML redirected to /en-us/ and showed $50.00. The unqualified product JSON returned 4000, while the effective /en-us/ JSON returned 5000. Neither JSON response included a currency field. Labelling that first value as $40 by borrowing the redirected page's currency would join different contexts.

This is why the checker records the requested URL, every redirect, the effective product-page URL and the emitted storefront route root. It treats a JSON request under another prefix as a separate diagnostic. It does not borrow the redirected page's currency to label that other amount.

The HTTP arm sent no cookies and executed no JavaScript. The browser arm started with fresh state but could receive cookies. We report them separately rather than assuming they represent identical sessions. Neither arm selected a shipping address, submitted a localization form or added anything to a cart.

Hold contradictory records before making a pricing decision

The useful output is a reason to accept or investigate a record, not a claim that one source is always authoritative.

CheckWhat to do when it fails
Exact product, selected variant and SKU agreeHold the record; do not substitute a similar title or the first offer.
The page's explicit currency signals agreeMark the context unverified until the conflict is resolved.
The selected offer's amount and currency agree with that contextFlag the conflicting fields instead of silently changing the currency label.
The API amount belongs to the effective page contextKeep a different-prefix response separate; obtain context evidence for it.
Market, variant, currency and price basis match the comparison recordTreat a different context as a separate series, not a price movement.

Replaying this dataset holds 12 of 16 page records for a currency conflict. The other four pass the implemented within-record checks; that means internal agreement for this observed variant and context, not a verified checkout price or permission to compare it with another market. The eight original-path API responses are retained as separate diagnostics rather than silently joined to redirected HTML.

Here is a condensed view of actual record r1-us-en-us; the original fields remain in the download:

json
{
  "variant_id": "58572724830592",
  "source_price_text": "$50.00",
  "storefront_currency": "USD",
  "naive_jsonld_record": {"price": "50.0", "currency": "GBP"},
  "accepted_for_price_comparison": false,
  "quarantine_reasons": ["jsonld_storefront_currency_disagreement"]
}

That is a measured data-quality result. We did not observe a production repricing system act on these records, measure money saved or establish payable checkout totals. The economic purpose of the check is to keep contradictory price data out of a decision that could affect purchasing or margins.

For storefront owners, the same comparison is useful when testing product markup across markets. Google's merchant-listing documentation defines the currency accompanying a price, and its general structured-data guidelines require markup to represent the visible content. Those requirements explain why consistency matters; we did not inspect this retailer's Merchant Center feed, Googlebot view or Search Console results. Price and currency, visible-content guidelines.

Reproduce the classification

Download replay.py and selected-records.zip into the same directory. With Python 3.10 or newer, run:

sh
python3 replay.py selected-records.zip

The command makes no network requests. It recomputes decisions from the recorded fields and checks the cell plan, request references and completion totals. Compare the output with results.json. A completed replay can contain legitimate quarantines; malformed or inconsistent recordings are errors. The download instructions include the actual collector, configuration, tests and exact command requirements.

The published records retain selected identifiers, original price strings, explicit currency fields, requested/effective URLs, timestamps, status codes and body hashes. Raw page captures, cookies, exit addresses and proxy credentials remain private. Offline replay checks the published classifications and arithmetic; it cannot independently authenticate historical network responses.

The adapter is intentionally specific to this product's markup and GBP/USD/EUR formats. For a property you operate, change the allowlist, variant identity and selectors, then verify the amount scale and currency evidence against a real page before collecting more samples. Missing or ambiguous fields should remain unverified. The ETag monitoring case covers a separate decision: reducing body transfer once the content being monitored is useful.

Method: actual public GET observations with agent-assisted collection and analysis, followed by separate evidence, editorial and search reviews. Local fixture tests exercise the checker and are not additional retailer observations.

Notify me when access opens. One email when access opens. Nothing else.

Sources

  1. Shopify Ajax Product API: presentment currency and locale-aware URLs
  2. Shopify Ajax API: use the storefront route root
  3. Google merchant listing structured data: price and currency
  4. Google structured-data guidelines: represent the visible content

Tagged:ProxiesTroubleshooting