Troubleshooting: Common Validation Errors
Every rejected request returns its reason as the HTTP 400 response body, and - since the
error logging fix described below - is also recorded in Event feed → Errors of your workspace, so
you don't have to reproduce a failure locally to see why it happened.
As of 2026-08, every validation failure across /track and the /v1/*
endpoints is recorded to the workspace's Errors tab (Event feed) - not just a subset of code paths. If a request
400s, check there first: the logged entry includes the error message, the offending payload, and
the source IP/User-Agent.
Two constraints that used to reject perfectly reasonable data were relaxed in 2026-08. If you're reading this from an older deployment or an older cached copy of this page, note that the examples below describe the current (post-2026-08) behavior:
-
Numeric strings are now accepted anywhere a number is expected -
quantity,price, and Moneyamountfields all accept either a JSON number (1) or a numeric string ("1"), which is what browsers and HTML form inputs naturally send. Non-numeric strings ("abc") are still rejected. -
A bare scalar is now accepted wherever a Money object is expected - a
transaction's
revenue,value,discountAmount, and each product'sfinalUnitPrice/regularPrice/discountPrice/netUnitPricecan be sent as a plain number (99.9) instead of{"amount": 99.9, "currency": "PLN"}. When sent as a bare scalar, the field has no currency of its own - it's backfilled fromrevenue.currencybefore the currency-consistency check runs, so cross-currency mismatches are still caught. Values that are neither an object nor a number/numeric string are still rejected.
One sharp edge: the backfill source is revenue.currency itself. If
revenue is sent as a bare scalar too, there is no currency to backfill from - give
revenue its currency explicitly (as an object, or make sure at least one Money field in
the payload carries a currency) rather than relying on every field defaulting.
Error Reference
| Error message | Cause | Correct payload fragment |
|---|---|---|
label is required |
page.view, product.view, product.addToCart, and
form.submit all require a top-level label, even though it's not
persisted.
|
{"label": "Products Page", ...} |
client must have at least one identifier (customId, id, uuid, or email) |
Every event needs a way to attribute it to a visitor. An empty or missing
client object fails this on every endpoint that requires one.
|
"client": {"customId": "vt_vid-cookie-value"} |
params.sku: expected a non-empty string, got null. Example: {...} |
product.view and product.addToCart both require
params.sku. Missing it, or sending an empty string, triggers this.
|
"params": {"sku": "PROD-001", ...} |
params.quantity: expected a number, got string "abc". Example: {...} |
params.quantity (addToCart) must resolve to a number. A JSON number
(2) or a numeric string ("2") both work as of 2026-08 - this
error means the value is neither, e.g. free-text.
|
"params": {"sku": "PROD-001", "quantity": 2} (or "quantity": "2") |
must be a Money object ({"amount":...,"currency":...}) or a bare number, got: [...] |
A field expecting Money (revenue, value,
discountAmount, finalUnitPrice, etc.) received something that is
neither an object nor a number/numeric string - an array, a boolean, or malformed JSON in
that position.
|
"revenue": {"amount": 99.99, "currency": "USD"} or, since 2026-08, the shorter
"revenue": 99.99 (currency then inherits from the transaction where
applicable) |
value.currency: expected "USD" (must match revenue.currency), got "EUR". Example: {...} |
All monetary fields in one transaction must share a single currency. This is unaffected by the 2026-08 leniency change - mixed currencies are still rejected outright, they're just now reported with the expected vs. actual currency instead of a generic message. | Use the same currency in revenue, value,
discountAmount, and every product's finalUnitPrice |
source: expected one of WEB_DESKTOP, WEB_MOBILE, MOBILE_APP, POS, MOBILE, DESKTOP, got string "web". Example: {...} |
source on a transaction must be one of the six accepted enum values, exactly
(case-sensitive). Anything else - including a plausible-looking value like
"web" or "online" - is rejected.
|
"source": "WEB_DESKTOP" |
paymentInfo.method: expected a non-empty string, got string "". Example: {...} |
paymentInfo.method is required and must be non-empty. There is no fixed enum
for it - CASH, CARD, PAYPAL, STRIPE,
etc. are all accepted, it just can't be missing.
|
"paymentInfo": {"method": "CARD"} |
products array must contain at least one item. Example: {...} |
An empty or missing products array on a transaction. |
"products": [{"sku": "PROD-001", "name": "Widget", "finalUnitPrice": {"amount": 99.99, "currency": "USD"}, "quantity": 1}] |
See the full field-by-field reference on the Transaction event page, or the corrected HTTP API examples for every other endpoint.