Contents
Consent
Consent changes for a visitor. granted creates or refreshes the visitor; denied and revoked
erase everything stored about that visitor and leave only a hashed tombstone for 30 days; without a
visitor id they are counted anonymously so you can measure opt-outs without storing anything.
Base URL, authentication and conventions of this API are on the Collector API page.
/consentReport a consent change
Reports the visitor's consent state. Same transport, key, origin rules and rate limits as
POST /events.
consent | with visitor.id | without visitor.id |
|---|---|---|
granted | creates or refreshes the visitor with its purposes and source; counted once per visitor | 422 (an id is required) |
denied / revoked | if the visitor exists: deletes its events, identities and carts (carts already confirmed by Shopify's server are kept but detached from the person) and keeps a tombstone (sha256 of the id) for 30 days; always counted | counted anonymously, nothing written |
pending | nothing written, nothing counted | nothing written, nothing counted |
A later granted with the same id starts a brand-new visitor. Purposes are read from purposes, or
from visitor.purposes when absent; the source from visitor.consentSource, or consentSource.
With a test code nothing is written or counted and the response carries test: true.
Rate limits. Shared with all Collector operations: 120 requests per minute per IP, 20,000 per minute per key.
X-Nucleo-Key header or key query parameterHeaders
OriginstringrequiredRequired. Browsers set it; servers and tools must send it (on GET,
Refereris accepted instead). Its host must equal one of the installation's domains, or be a subdomain of a wildcard domain (*.acme.examplematchesshop.acme.exampleandeu.shop.acme.example, notacme.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.nullis accepted only when the installation has the Web Pixel enabled. An origin that does not match answers403.Example:https://shop.acme.example
Request bodytext/plain, application/json
keystringThe installation's public key.
pattern ^pk_[0-9a-f]{32}$Example:pk_3f9a1c7e5b2d4f60a8c1e9b7d3f5a2c4sentAtstring (date-time)RFC 3339 with a
Zor an explicit offset, as produced byDate.prototype.toISOString().max length 40pattern ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$Example:2026-10-04T09:30:12.004ZconsentstringrequiredCase-insensitive; surrounding spaces are ignored.
One ofgranteddeniedpendingrevokedpurposesarray of stringConsent purposes. Only
personalisationchanges 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'sanalyticsProcessingAllowed()toanalyticsandmarketingAllowed()topersonalisation.max items 10consentSourcestringWhere the consent decision came from. Any other value is ignored (stored as null).
One ofshopifymanualgpcvisitorobjectidis required forgranted; optional fordenied,revokedandpending.Child attributes
idstringOpaque browser identifier generated by the storefront: Shopify's
_shopify_ycookie value (so the theme and the checkout Web Pixel share the same visitor) or 32 random hex characters. Ids starting withdemo(any case) are reserved and rejected.pattern ^[A-Za-z0-9_-]{16,64}$Example:8b1f0c2e9d7a4b3c6e5f1a2b3c4d5e6fconsentSourcestringWhere the consent decision came from. Any other value is ignored (stored as null).
One ofshopifymanualgpcpurposesarray of stringConsent purposes. Only
personalisationchanges 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'sanalyticsProcessingAllowed()toanalyticsandmarketingAllowed()topersonalisation.max items 10
teststringTest 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
Responses
200The consent change was applied (or counted).application/json
HeadersAccess-Control-Allow-OriginEchoes the requestOriginwhen it was present and allowed.VaryAlwaysOrigin.
visitorstring | nullrequiredThe visitor id, or null when none was sent.
consentstringrequiredCase-insensitive; surrounding spaces are ignored.
One ofgranteddeniedpendingrevokedstoredbooleanWhether a visitor row was written. Absent when
deletedis present.countedbooleanrequiredWhether the installation's anonymous consent counters moved.
deletedobjectPresent when a known visitor was erased.
Child attributes
eventsintegeridentitiesintegercartsinteger
testtruetestKnownboolean
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. application/json
messagestringrequired
404The key is missing, malformed or unknown (no detail is given on purpose).application/json
messagestringrequired
409The store behind the key is not provisioned yet.application/json
messagestringrequired
413The body exceeds 64 KB.application/json
messagestringrequired
422The consent update is not valid.
errorsmaps each field to a single message string.application/jsonmessagestringrequirederrorsobjectrequiredField path → one message string.
429Rate limit exceeded (120 requests per minute per IP, or 20,000 per minute per key). Nothing is written. application/json
HeadersRetry-AfterSeconds to wait before retrying.X-RateLimit-LimitRequests allowed per minute for the client IP.X-RateLimit-RemainingAlways0on a 429.X-RateLimit-ResetUnix timestamp at which the window resets.
messagestringrequired
curl -X POST https://api-catalog.nucleoplatform.com/api/collect/v1/consent \
-H "X-Nucleo-Key: $NUCLEO_COLLECTOR_KEY" \
-H 'Origin: https://shop.acme.example' \
-H 'Content-Type: text/plain' \
-d '{
"visitor": {
"id": "8b1f0c2e9d7a4b3c6e5f1a2b3c4d5e6f",
"consentSource": "shopify"
},
"consent": "revoked",
"purposes": []
}'const res = await fetch("https://api-catalog.nucleoplatform.com/api/collect/v1/consent", {
method: "POST",
headers: {
"X-Nucleo-Key": `${process.env.NUCLEO_COLLECTOR_KEY}`,
Origin: "https://shop.acme.example",
"Content-Type": "text/plain",
},
body: JSON.stringify({
"visitor": {
"id": "8b1f0c2e9d7a4b3c6e5f1a2b3c4d5e6f",
"consentSource": "shopify"
},
"consent": "revoked",
"purposes": []
}),
});
const data = await res.json();
console.log(res.status, data);<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$response = $client->request('POST', 'https://api-catalog.nucleoplatform.com/api/collect/v1/consent', [
'headers' => [
'X-Nucleo-Key' => getenv('NUCLEO_COLLECTOR_KEY'),
'Origin' => 'https://shop.acme.example',
'Content-Type' => 'text/plain',
],
'json' => [
'visitor' => [
'id' => '8b1f0c2e9d7a4b3c6e5f1a2b3c4d5e6f',
'consentSource' => 'shopify',
],
'consent' => 'revoked',
'purposes' => [],
],
'http_errors' => false,
]);
echo $response->getStatusCode(), PHP_EOL;
echo $response->getBody();{
"visitor": "8b1f0c2e9d7a4b3c6e5f1a2b3c4d5e6f",
"consent": "granted",
"stored": true,
"counted": true
}{
"visitor": "8b1f0c2e9d7a4b3c6e5f1a2b3c4d5e6f",
"consent": "revoked",
"deleted": {
"events": 57,
"identities": 1,
"carts": 2
},
"counted": true
}{
"visitor": "8b1f0c2e9d7a4b3c6e5f1a2b3c4d5e6f",
"consent": "denied",
"stored": false,
"counted": true
}{
"visitor": null,
"consent": "denied",
"stored": false,
"counted": true
}{
"visitor": "8b1f0c2e9d7a4b3c6e5f1a2b3c4d5e6f",
"consent": "pending",
"stored": false,
"counted": false
}{
"visitor": "8b1f0c2e9d7a4b3c6e5f1a2b3c4d5e6f",
"consent": "granted",
"stored": false,
"counted": false,
"test": true,
"testKnown": false
}{
"message": "Forbidden."
}{
"message": "Not found."
}{
"message": "Store is not ready."
}{
"message": "Payload too large."
}{
"message": "The consent update is not valid.",
"errors": {
"visitor.id": "visitor.id is required to grant consent: 16-64 characters of [A-Za-z0-9_-]."
}
}{
"message": "The consent update is not valid.",
"errors": {
"consent": "consent must be one of: granted, denied, pending, revoked."
}
}{
"message": "The consent update is not valid.",
"errors": {
"visitor.id": "visitor.id must be 16-64 characters of [A-Za-z0-9_-]."
}
}{
"message": "Too Many Attempts."
}