Contents

Events

Batches of storefront events. Each batch is validated event by event: an event with an unknown kind, a malformed uid or a timestamp out of range is rejected on its own and the rest of the batch is stored. Only batch-level problems (no visitor id, invalid consent state, more than 50 events, bad test code, unsupported version) fail the whole batch with a 422.

Base URL, authentication and conventions of this API are on the Collector API page.

post/events

Send a batch of events

Sends up to 50 events for one visitor in a single batch (body ≤ 64 KB).

Transport. Browsers should send the body as text/plain (e.g. navigator.sendBeacon or fetch(..., {mode: "no-cors", credentials: "omit"})) with the key in the key body field: a text/plain POST without custom headers is a CORS simple request and needs no preflight. The Collector does not answer CORS preflights, so application/json or the X-Nucleo-Key header only work from servers and tools such as curl.

Origin. On POST only the Origin header counts (a Referer alone is refused). Origin: null (the sandboxed Shopify Web Pixel) is accepted only when the installation has the Web Pixel enabled, and then only checkout_start and purchase events pass (other kinds are rejected with reason origin) and the channel is recorded as web-pixel.

Consent. If visitor.consent is not granted (compared after trimming and lower-casing) nothing is written — not even the visitor — and the response is 202 with stored: 0 and reason: "consent". If it is denied or revoked and the visitor is already known, the erasure described in POST /consent is applied.

Validation per event.

  • kind must be one of the seven supported kinds (kind).
  • uid is optional; when present it must match ^[A-Za-z0-9_-]{8,40}$ (uid).
  • at is optional (defaults to now) and must be RFC 3339; it must fall between 7 days ago and 5 minutes from now (time). If the batch carries sentAt and the browser clock differs from ours by more than 30 seconds and at most 24 hours, every at is shifted by the difference before the window check (the original value is kept as payload._client_at, the shift is returned in skew).
  • An event that is not a JSON object is rejected (shape).
  • payload must be a flat object of scalars with at most 20 keys and strings ≤ 500 characters; otherwise the payload is dropped (the event is kept) and its index is listed in payloadDropped. Only the keys allowed for the kind are kept (see each event schema); others are silently dropped and the event index is also listed in payloadDropped. Keys starting with _ are reserved.
  • page_view.path and product_view.from are stored without fragment and query string (except q); under /account, /checkout, /checkouts, /orders and /customer only the first segment is kept.
  • query (search only) is truncated to 200 characters with whitespace collapsed. A query that looks like personal data — an email address, 8+ consecutive digits, or a phone-like digit sequence (9+ digits with separators, or 6+ digits starting with +) — is withheld: stored as null, flagged, and its index listed in withheld.
  • Control characters (C0 and DEL) are stripped from every string, except tab, line feed and carriage return: these are not stripped. In query they collapse to a single space like any whitespace; other strings keep them (most fields only trim them at the start and end, payload strings keep them as sent).

Idempotency. An event with a uid is written at most once: re-sending it (or repeating it in the same batch) counts it in duplicates. Always set a random uid so retries are safe.

Identity. customer is a reference signed by your store (see CustomerReference). It is verified, and the visitor is linked to the customer only if visitor.purposes contains personalisation; otherwise the events stay anonymous and identityWithheld is true. A plain customer id is never accepted.

Product references. ref is matched to your Catalog by variant id, then SKU, then product id, then handle. Events whose reference does not match any product are stored anyway and counted in unmapped.

Carts. When payload.cart is present, add_to_cart / remove_from_cart / checkout_start / purchase update the visitor's cart. A cart token that belongs to another visitor is not touched (the event is still stored) and counted in cartRefused.

Test mode. With a test code (6 characters [A-Z0-9], generated in Catalog › Shoppers › Test events) the batch runs the full validation and product matching but writes nothing; the result appears live in Test events and the response carries test: true. An expired or unknown code still keeps the batch in test mode (testKnown: false).

A database error never produces a 500: the batch comes back 202 with every event rejected with reason storage.

Rate limits. 120 requests per minute per client IP, plus an emergency cap of 20,000 requests per minute per public key. The limits are shared by all Collector operations and are checked before the key is looked up.

POST https://api-catalog.nucleoplatform.com/api/collect/v1/events
Authentication: X-Nucleo-Key header or key query parameter
Rate limit: 120 requests per 1m, per IP

Headers

  • Originstringrequired

    Required. Browsers set it; servers and tools must send it (on GET, Referer is accepted instead). Its host must equal one of the installation's domains, or be a subdomain of a wildcard domain (*.acme.example matches shop.acme.example and eu.shop.acme.example, not acme.example). A wildcard must sit under your own registrable domain: it is refused on a public suffix (*.com, *.co.uk) and on shared platforms where every subdomain belongs to someone else (for example *.myshopify.com, *.vercel.app, *.netlify.app, *.github.io); such a wildcard saved earlier matches nothing. Register those hosts by their exact name. null is accepted only when the installation has the Web Pixel enabled. An origin that does not match answers 403.

    Example: https://shop.acme.example

Request bodytext/plain, application/json

A batch of events. Send it as text/plain from browsers (the body is still JSON) or as application/json from servers. Maximum 64 KB.

  • keystring

    The installation's public key.

    pattern ^pk_[0-9a-f]{32}$
    Example: pk_3f9a1c7e5b2d4f60a8c1e9b7d3f5a2c4
  • v1 | "1"

    Contract version. Optional; when present must be 1 (number or string).

    Example: 1
  • sentAtstring (date-time)

    Browser time when the batch was sent. Used to correct clock skew between 30 seconds and 24 hours. An unreadable value is ignored.

  • visitorVisitorrequired
    Child attributes
    • idstringrequired

      Opaque browser identifier generated by the storefront: Shopify's _shopify_y cookie value (so the theme and the checkout Web Pixel share the same visitor) or 32 random hex characters. Ids starting with demo (any case) are reserved and rejected.

      pattern ^[A-Za-z0-9_-]{16,64}$
      Example: 8b1f0c2e9d7a4b3c6e5f1a2b3c4d5e6f
    • consentstringrequired

      Case-insensitive; surrounding spaces are ignored.

      One ofgranteddeniedpendingrevoked
    • purposesarray of string

      Consent purposes. Only personalisation changes behaviour: without it events stay anonymous and the size widget stays impersonal. Items beyond the 10th are ignored; each item is trimmed to 32 characters. The storefront script maps Shopify's analyticsProcessingAllowed() to analytics and marketingAllowed() to personalisation.

      max items 10
    • consentSourcestring

      Where the consent decision came from. Any other value is ignored (stored as null).

      One ofshopifymanualgpc
    • localestring

      Default locale for the batch's events (truncated to 16 characters).

      max length 16
      Example: it
    • channelstring

      Default channel for the batch's events (truncated to 64 characters).

      max length 64
      Example: web
  • sessionstring

    Optional session id (same format as the visitor id). A malformed value is ignored.

    pattern ^[A-Za-z0-9_-]{16,64}$
    Example: s_4c2a9e1b7f3d5a60
  • customerstring

    A customer reference signed by your store, in the form v1.<storeId>.<shopifyCustomerId>.<expiresAt>.<signature> where expiresAt is a Unix timestamp (at most 24 hours ahead; 15 minutes is recommended) and signature is the lowercase hex HMAC-SHA256 of v1.<storeId>.<shopifyCustomerId>.<expiresAt> keyed with your store's signing secret.

    • Signing secret: in Nucleo, Settings › Catalog › Search › Installations, panel Customer reference secret, Generate secret (owner or admin role). It is shown once; Regenerate invalidates every reference signed with the previous one.
    • storeId: the numeric id of your Nucleo store. Nucleo does not show it: ask Nucleo support.

    Sign on your server, never in the theme or in browser code: whoever holds the secret can sign a reference for any customer. Your storefront's backend computes the reference for the signed-in customer and prints it into the page as <meta name="nucleo-customer" content="…"> (or the script's data-customer attribute), or passes it to nucleo.identify(reference); the script picks it up. Wrong store, expired, malformed or badly signed references are treated as anonymous, never as errors.

    max length 200
    Example: v1.12.7012345678.1791104400.9c1e4b7a2f0d3c6e8b5a1f4d7c0e3b6a9d2f5c8e1b4a7d0c3f6e9b2a5d8c1f4e
  • teststring

    Test code from Catalog › Shoppers › Test events (valid for 60 minutes, one per store). Trimmed and upper-cased before validation. A present but malformed value fails the request with 422. The storefront script also picks it up from ?nucleo_test=<code> in any page URL.

    pattern ^[A-Z0-9]{6}$
    Example: K7Q2ZP
  • eventsarray of EventBase & object | EventBase & object | EventBase & object | EventBase & object | EventBase & object | EventBase & object | EventBase & object

    The events, in any order. An empty list is valid.

    max items 50default []

Responses

  • 202The batch was received. Inspect the counters to see what was stored: the storefront usually does not read the response (no-cors, sendBeacon), it is there for testing.
    Headers
    • Access-Control-Allow-Origin Echoes the request Origin when it was present and allowed.
    • Vary Always Origin.
    • X-RateLimit-Limit Requests allowed per minute for the client IP.
    • X-RateLimit-Remaining Requests left in the current minute.

    One of the following shapes:

    EventsResult
    • acceptedintegerrequired

      Events that passed validation.

    • storedintegerrequired

      Events actually written (always 0 in test mode).

    • duplicatesintegerrequired

      Events skipped because their uid was already stored or repeated in the batch.

    • rejectedarray of RejectedEventrequired
      Attributes of each item
      • indexintegerrequired

        Zero-based position of the event in the batch.

        Example: 4
      • reasonstringrequired

        shape not an object · kind unsupported kind · origin kind not allowed with Origin: null · uid malformed uid · time unreadable or out-of-window timestamp · storage the database refused the batch.

        One ofshapekindoriginuidtimestorage
    • payloadDroppedarray of integerrequired

      Indexes of events whose payload (or some of its keys) was dropped.

    • withheldarray of integerrequired

      Indexes of search events whose query was withheld as possible personal data.

    • skewintegerrequired

      Seconds added to every at to correct the browser clock (0 when not corrected).

    • unmappedintegerrequired

      Stored events whose ref matched no product in the Catalog.

    • cartRefusedintegerrequired

      Events whose cart token belongs to another visitor (event stored, cart untouched). Always 0 in test mode.

    • identifiedbooleanrequired

      The customer reference verified and the visitor was linked to the customer.

    • identityWithheldbooleanrequired

      A customer reference was sent but personalisation was not granted, so events stay anonymous.

    • visitorstringrequired

      The visitor id of the batch.

    • testtrue

      Present only in test mode.

    • testKnownboolean

      Present only in test mode — whether the code is the store's active test code.

    EventsNoConsentResult
    • acceptedintegerrequired
    • stored0required
    • reason"consent"required
    • rejectedarray of RejectedEventrequired
      Attributes of each item
      • indexintegerrequired

        Zero-based position of the event in the batch.

        Example: 4
      • reasonstringrequired

        shape not an object · kind unsupported kind · origin kind not allowed with Origin: null · uid malformed uid · time unreadable or out-of-window timestamp · storage the database refused the batch.

        One ofshapekindoriginuidtimestorage
    • visitorstringrequired
    • testtrue
    • testKnownboolean
    EventsStorageResult
    • acceptedintegerrequired
    • stored0required
    • rejectedarray of RejectedEventrequired
      Attributes of each item
      • indexintegerrequired

        Zero-based position of the event in the batch.

        Example: 4
      • reasonstringrequired

        shape not an object · kind unsupported kind · origin kind not allowed with Origin: null · uid malformed uid · time unreadable or out-of-window timestamp · storage the database refused the batch.

        One ofshapekindoriginuidtimestorage
    • visitorstringrequired
    • testtrue
    • testKnownboolean
  • 403The installation is paused, or the origin is not one of its domains. Both answer the same body; the exact reason is visible to the merchant in Catalog › Shoppers › Test events.
    • messagestringrequired
  • 404The key is missing, malformed or unknown (no detail is given on purpose).
    • messagestringrequired
  • 409The store behind the key is not provisioned yet.
    • messagestringrequired
  • 413The body exceeds 64 KB.
    • messagestringrequired
  • 422The batch as a whole is not valid. Nothing is written. Note that errors maps each field to a single message string (not an array).
    Headers
    • Access-Control-Allow-Origin Echoes the request Origin when it was present and allowed.
    • messagestringrequired
    • errorsobjectrequired

      Field path → one message string.

  • 429Rate limit exceeded (120 requests per minute per IP, or 20,000 per minute per key). Nothing is written.
    Headers
    • Retry-After Seconds to wait before retrying.
    • X-RateLimit-Limit Requests allowed per minute for the client IP.
    • X-RateLimit-Remaining Always 0 on a 429.
    • X-RateLimit-Reset Unix timestamp at which the window resets.
    • messagestringrequired
Request
curl -X POST https://api-catalog.nucleoplatform.com/api/collect/v1/events \
  -H "X-Nucleo-Key: $NUCLEO_COLLECTOR_KEY" \
  -H 'Origin: https://shop.acme.example' \
  -H 'Content-Type: text/plain' \
  -d '{
  "v": 1,
  "visitor": {
    "id": "8b1f0c2e9d7a4b3c6e5f1a2b3c4d5e6f",
    "consent": "granted",
    "purposes": [
      "analytics",
      "personalisation"
    ],
    "consentSource": "shopify",
    "locale": "it",
    "channel": "web"
  },
  "session": "s_4c2a9e1b7f3d5a60",
  "customer": "v1.12.7012345678.1791104400.9c1e4b7a2f0d3c6e8b5a1f4d7c0e3b6a9d2f5c8e1b4a7d0c3f6e9b2a5d8c1f4e",
  "events": [
    {
      "uid": "e-7f3a9c1b20",
      "kind": "page_view",
      "market": "IT",
      "payload": {
        "type": "collection",
        "path": "/collections/men",
        "collection": "men",
        "filters": "filter.v.option.color=Black;filter.v.price.lte=80",
        "sort": "price-ascending"
      }
    },
    {
      "uid": "e-7f3a9c1b21",
      "kind": "product_view",
      "ref": {
        "product": "8123456789",
        "variant": "44001234567",
        "handle": "classic-tee-black",
        "sku": "TEE-BLK-M"
      },
      "payload": {
        "price": "29.00",
        "currency": "EUR",
        "pos": 3,
        "list": "collection:men",
        "from": "/collections/men"
      }
    },
    {
      "uid": "e-7f3a9c1b22",
      "kind": "search",
      "query": "black hoodie",
      "payload": {
        "results": 12
      }
    },
    {
      "uid": "e-7f3a9c1b23",
      "kind": "add_to_cart",
      "ref": {
        "variant": "44001234567",
        "sku": "TEE-BLK-M"
      },
      "payload": {
        "cart": "c1-9f2b7e4a1d",
        "quantity": 1,
        "price": "29.00",
        "currency": "EUR"
      }
    }
  ]
}'
Response
{
  "accepted": 4,
  "stored": 3,
  "duplicates": 1,
  "rejected": [
    {
      "index": 4,
      "reason": "kind"
    }
  ],
  "payloadDropped": [
    1
  ],
  "withheld": [],
  "skew": 0,
  "unmapped": 0,
  "cartRefused": 0,
  "identified": true,
  "identityWithheld": false,
  "visitor": "8b1f0c2e9d7a4b3c6e5f1a2b3c4d5e6f"
}
Was this page helpful?