{"_id":"@dxv-systems/metis-client","_rev":"6-7aabc6a6b78647ab4c413e066332a428","name":"@dxv-systems/metis-client","dist-tags":{"latest":"0.8.0"},"versions":{"0.3.0":{"name":"@dxv-systems/metis-client","version":"0.3.0","keywords":["dxv","metis","metis-client","meta-capi","google-ads","click-tracking","attribution"],"license":"MIT","_id":"@dxv-systems/metis-client@0.3.0","maintainers":[{"name":"dxv-systems","email":"dunninkjesse1@gmail.com"}],"homepage":"https://github.com/dxv-systems/dxv-packages#readme","bugs":{"url":"https://github.com/dxv-systems/dxv-packages/issues"},"dist":{"shasum":"f8eb8c3b39a6ec635aac6377e880a0af012e32e8","tarball":"https://registry.npmjs.org/@dxv-systems/metis-client/-/metis-client-0.3.0.tgz","fileCount":22,"integrity":"sha512-8Yzndm/jWkmthY8xNy5X1mlzncfhFrow+lFUjS0DfZSm73UJEFaqcSPopjIsdORWlInodWc3hwEPsfAR3yiwFQ==","signatures":[{"sig":"MEQCIBhvOM9I67OEvIiqnn4hTXIr5C5zZwdlwxycfV77ukciAiAhunZH3/nlCsUruWXurnyl/zQEpaiwXuaLiW+FNeI00w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":42486},"engines":{"node":">=24.0.0"},"exports":{"./client":{"types":"./dist/client.d.ts","default":"./dist/client.js"},"./server":{"types":"./dist/server.d.ts","default":"./dist/server.js"}},"gitHead":"e047bcc7927e9544facbc8626f4493fc2d4897f9","scripts":{"test":"vitest run","build":"tsc","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"dxv-systems","email":"dunninkjesse1@gmail.com"},"repository":{"url":"git+https://github.com/dxv-systems/dxv-packages.git","type":"git","directory":"packages/metis-client"},"_npmVersion":"11.12.1","description":"DXV Metis conversion tracking SDK — click-ID capture and signed conversion event emission","directories":{},"_nodeVersion":"24.15.0","_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.8","typescript":"^5.6.0","@types/node":"^24"},"_npmOperationalInternal":{"tmp":"tmp/metis-client_0.3.0_1788689164077_0.6009216811145532","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@dxv-systems/metis-client","version":"0.4.0","keywords":["dxv","metis","metis-client","meta-capi","google-ads","click-tracking","attribution"],"license":"MIT","_id":"@dxv-systems/metis-client@0.4.0","maintainers":[{"name":"dxv-systems","email":"dunninkjesse1@gmail.com"}],"homepage":"https://github.com/dxv-systems/dxv-packages#readme","bugs":{"url":"https://github.com/dxv-systems/dxv-packages/issues"},"dist":{"shasum":"75a577a99ccc829383c0e58ec9b10457fe20ece7","tarball":"https://registry.npmjs.org/@dxv-systems/metis-client/-/metis-client-0.4.0.tgz","fileCount":22,"integrity":"sha512-JIXtlsdxwSSixm3gc0irWRD/Ls/wccv5IhMgkO2K9TXDA2pSQGeM8qc44r5e2H1g/CeVAm7C3M/DmnJDH4Pw9w==","signatures":[{"sig":"MEUCIQCyCT6FyfMh9dODmpHftT07xMauJo3KHm9BbEr/0lTuegIgQyfVyEo+8jOaFkmUzeXlT9CVsdcm1VEY2jBnjTSABEM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIG6knxjuQMwbWOm11lU1hn+yMzQkqWkxLYK14udeHzXRAiEAnAn41ufa5l7Vj6Eh8Djn3hnYMmU2sxEPCavy3Y5JiwM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":46181},"engines":{"node":">=24.0.0"},"exports":{"./client":{"types":"./dist/client.d.ts","default":"./dist/client.js"},"./server":{"types":"./dist/server.d.ts","default":"./dist/server.js"}},"gitHead":"32ebf583b94c91e4347296fc46144a6ff5b98110","scripts":{"test":"vitest run","build":"tsc","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:06ef7410-bd67-4b37-8b8f-437594786077"}},"repository":{"url":"git+https://github.com/dxv-systems/dxv-packages.git","type":"git","directory":"packages/metis-client"},"_npmVersion":"11.5.1","description":"DXV Metis conversion tracking SDK — click-ID capture and signed conversion event emission","directories":{},"_nodeVersion":"24.20.0","_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.8","typescript":"^5.6.0","@types/node":"^24"},"_npmOperationalInternal":{"tmp":"tmp/metis-client_0.4.0_1788691330274_0.7060845653640169","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@dxv-systems/metis-client","version":"0.5.0","keywords":["dxv","metis","metis-client","meta-capi","google-ads","click-tracking","attribution"],"license":"MIT","_id":"@dxv-systems/metis-client@0.5.0","maintainers":[{"name":"dxv-systems","email":"dunninkjesse1@gmail.com"}],"homepage":"https://github.com/dxv-systems/dxv-packages#readme","bugs":{"url":"https://github.com/dxv-systems/dxv-packages/issues"},"dist":{"shasum":"a2cb896fd375fc38533ca128afec1b1cc8f0b1ac","tarball":"https://registry.npmjs.org/@dxv-systems/metis-client/-/metis-client-0.5.0.tgz","fileCount":26,"integrity":"sha512-if4nLO4llQ+1qdbTy8mELwUCkowSs4fr9j+AX3GjK6Gg4DwGq0J5qOOsVag656NPiK9HOpWUFNfcDW/eS0SUBQ==","signatures":[{"sig":"MEYCIQDHjFQ+tTrykR91RB5haWSu2mCd8hhmGmMJhiEWtRM2zQIhANFMNWLFazlwxZT7vr9SbY4WE++HQtlAkJquOEdIAagA","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQCA5uNSkElfvonN5ERnBMCxpAhqffujyKKInaunZZNOUAIgOyOOiteYwK8HCcFcXx1x5vXc1j2gJD4fcM0OjgsbzOs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":53463},"engines":{"node":">=24.0.0"},"exports":{"./client":{"types":"./dist/client.d.ts","default":"./dist/client.js"},"./server":{"types":"./dist/server.d.ts","default":"./dist/server.js"}},"gitHead":"59fdc086c7ecd6adb3d3a3cbb117ef2f50b3034c","scripts":{"test":"vitest run","build":"tsc","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:06ef7410-bd67-4b37-8b8f-437594786077"}},"repository":{"url":"git+https://github.com/dxv-systems/dxv-packages.git","type":"git","directory":"packages/metis-client"},"_npmVersion":"11.5.1","description":"DXV Metis conversion tracking SDK — click-ID capture and signed conversion event emission","directories":{},"_nodeVersion":"24.20.0","_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.8","typescript":"^5.6.0","@types/node":"^24"},"_npmOperationalInternal":{"tmp":"tmp/metis-client_0.5.0_1788692929957_0.7945432358977691","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"@dxv-systems/metis-client","version":"0.6.0","keywords":["dxv","metis","metis-client","meta-capi","google-ads","click-tracking","attribution"],"license":"MIT","_id":"@dxv-systems/metis-client@0.6.0","maintainers":[{"name":"dxv-systems","email":"dunninkjesse1@gmail.com"}],"homepage":"https://github.com/dxv-systems/dxv-packages#readme","bugs":{"url":"https://github.com/dxv-systems/dxv-packages/issues"},"dist":{"shasum":"93df83aeadece31552ff0c9a5f55b6cc26b4dc90","tarball":"https://registry.npmjs.org/@dxv-systems/metis-client/-/metis-client-0.6.0.tgz","fileCount":26,"integrity":"sha512-Me2QpcW5lloOZXHBd22k1t6LZvGLpfvhtCbOCRetRY1+fucyYBIyv+5mmdWXw71DkjItxCDZbAoXnYtFv3CucQ==","signatures":[{"sig":"MEQCIH4O5ysJb04S2XzPy5ZQmhMykUhkRe3SM54siHbSrfx5AiApr/6wodaFib06L7bfYUVN+GevoDznYQl90EESyUBxhw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQDVb6oplllsc4POeleTQe59wPfWegfO7ihR+Rx+q60CYwIhAPrFHygYyXO1xBaYUW5WhX+qu3lBm2rm/2wE311Vqhup","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":56163},"engines":{"node":">=24.0.0"},"exports":{"./client":{"types":"./dist/client.d.ts","default":"./dist/client.js"},"./server":{"types":"./dist/server.d.ts","default":"./dist/server.js"}},"gitHead":"2eabd954300eda29f6293e640a86ad75ca59463c","scripts":{"test":"vitest run","build":"tsc","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:06ef7410-bd67-4b37-8b8f-437594786077"}},"repository":{"url":"git+https://github.com/dxv-systems/dxv-packages.git","type":"git","directory":"packages/metis-client"},"_npmVersion":"11.5.1","description":"DXV Metis conversion tracking SDK — click-ID capture and signed conversion event emission","directories":{},"_nodeVersion":"24.20.0","_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.8","typescript":"^5.6.0","@types/node":"^24"},"_npmOperationalInternal":{"tmp":"tmp/metis-client_0.6.0_1788766053288_0.6868188894120575","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"@dxv-systems/metis-client","version":"0.7.0","keywords":["dxv","metis","metis-client","meta-capi","google-ads","click-tracking","attribution"],"license":"MIT","_id":"@dxv-systems/metis-client@0.7.0","maintainers":[{"name":"dxv-systems","email":"dunninkjesse1@gmail.com"}],"homepage":"https://github.com/dxv-systems/dxv-packages#readme","bugs":{"url":"https://github.com/dxv-systems/dxv-packages/issues"},"dist":{"shasum":"1ca5b61839d2a3766d1397ec784b1dbf2b5353e2","tarball":"https://registry.npmjs.org/@dxv-systems/metis-client/-/metis-client-0.7.0.tgz","fileCount":26,"integrity":"sha512-qjCWfcKWzM8/2O7LZqQwWFFVTbD1Ko/rjB2fKIx5W73Dyyd3oMpwjmeH2yf6wAojFZjRr1oluqha/sTeJ4t7RA==","signatures":[{"sig":"MEUCIB9T52cSBQAm9YUClqL/r6rgviG1BpojRuZZ09Aafi3rAiEAvlrHEs6omp8X5SJf4OsaLwzY2obo3RBNQVujMv7sYSc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCICRMCgo2MUfzCUeu566Wb6HuRtKwO3YA2uESQgAT85SyAiEArHmUgbVNdSv9W3CCm1PzKGyM8OOsqhtTBpglKdFuqmQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":60981},"engines":{"node":">=24.0.0"},"exports":{"./client":{"types":"./dist/client.d.ts","default":"./dist/client.js"},"./server":{"types":"./dist/server.d.ts","default":"./dist/server.js"}},"gitHead":"9d5831bba12cce4a98360caad9c05901496f5ee1","scripts":{"test":"vitest run","build":"tsc","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"06ef7410-bd67-4b37-8b8f-437594786077"}},"repository":{"url":"git+https://github.com/dxv-systems/dxv-packages.git","type":"git","directory":"packages/metis-client"},"_npmVersion":"11.5.1","description":"DXV Metis conversion tracking SDK — click-ID capture and signed conversion event emission","directories":{},"_nodeVersion":"24.21.0","_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.8","typescript":"^5.6.0","@types/node":"^24"},"_npmOperationalInternal":{"tmp":"tmp/metis-client_0.7.0_1790680754776_0.20174373515616661","host":"s3://npm-registry-packages-npm-production"}},"0.8.0":{"_id":"@dxv-systems/metis-client@0.8.0","bugs":{"url":"https://github.com/dxv-systems/dxv-packages/issues"},"dist":{"shasum":"12798cc1c63012b6d321a0352f78a943ebc01bd7","tarball":"https://registry.npmjs.org/@dxv-systems/metis-client/-/metis-client-0.8.0.tgz","fileCount":30,"integrity":"sha512-TDmxHuqjQ0uf1bHA23yl4if3kuBl6Sx8qRuC4ab3yhMrMxabL5sGGCHQ++O8ZT68Xhx2/ljptSl9Lbw1YpyWuA==","signatures":[{"sig":"MEYCIQDKwKB8TuZBomAWEjym5NE01Q6HBEt62HbIGIqth4bsfwIhAJJO1xa2/Wo36TE0f1eRYd1pDcsrG89VMGFm3B7jmAHJ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCCju1Sv/Z6PAYSjs6iCvlYEvz19z1nocLNxvbXLhLHLAIga4EWI0iYgppxfq6pjWzmJ1d0+ywVg8xdnotzjfTVw9o="}],"unpackedSize":68482},"name":"@dxv-systems/metis-client","engines":{"node":">=24.0.0"},"exports":{"./client":{"types":"./dist/client.d.ts","default":"./dist/client.js"},"./server":{"types":"./dist/server.d.ts","default":"./dist/server.js"}},"gitHead":"8a812bf3a48c2f4cad295ba7bd592efa78638cac","license":"MIT","scripts":{"test":"vitest run","build":"tsc","test:watch":"vitest","prepublishOnly":"npm run build"},"version":"0.8.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"06ef7410-bd67-4b37-8b8f-437594786077"}},"homepage":"https://github.com/dxv-systems/dxv-packages#readme","keywords":["dxv","metis","metis-client","meta-capi","google-ads","click-tracking","attribution"],"repository":{"url":"git+https://github.com/dxv-systems/dxv-packages.git","type":"git","directory":"packages/metis-client"},"_npmVersion":"11.5.1","description":"DXV Metis conversion tracking SDK — click-ID capture and signed conversion event emission","directories":{},"maintainers":[{"name":"dxv-systems","email":"dunninkjesse1@gmail.com"}],"_nodeVersion":"24.21.0","_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.8","typescript":"^5.6.0","@types/node":"^24"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/metis-client_0.8.0_1790681873304_0.9754874484595641"}}},"time":{"created":"2026-09-06T10:06:03.900Z","modified":"2026-09-29T11:37:53.540Z","0.3.0":"2026-09-06T10:06:04.201Z","0.4.0":"2026-09-06T10:42:10.368Z","0.5.0":"2026-09-06T11:08:50.059Z","0.6.0":"2026-09-07T07:27:33.380Z","0.7.0":"2026-09-29T11:19:14.866Z","0.8.0":"2026-09-29T11:37:53.385Z"},"bugs":{"url":"https://github.com/dxv-systems/dxv-packages/issues"},"license":"MIT","homepage":"https://github.com/dxv-systems/dxv-packages#readme","keywords":["dxv","metis","metis-client","meta-capi","google-ads","click-tracking","attribution"],"repository":{"url":"git+https://github.com/dxv-systems/dxv-packages.git","type":"git","directory":"packages/metis-client"},"description":"DXV Metis conversion tracking SDK — click-ID capture and signed conversion event emission","maintainers":[{"name":"dxv-systems","email":"dunninkjesse1@gmail.com"}],"readme":"# @dxv-systems/metis-client\n\nDXV Metis conversion tracking SDK. Captures click IDs (`gclid`, `fbclid`, `gbraid`, `wbraid`, `utm_*`) on landing and emits signed server-side conversion events to the Metis Portal's relay endpoint.\n\n> Published as `@dxv-systems/conversions` up to 0.3.0. That name is deprecated; the API is identical, only the package name changed.\n\n## Install\n\n```bash\nnpm install @dxv-systems/metis-client\n```\n\n## Onboarding\n\nThe DXV Metis operator creates a customer in the Portal and provisions you with three values:\n\n- `customerId` — integer, identifies your customer record in the Portal\n- `relaySecret` — HMAC signing key (treat as a credential)\n- `relayUrl` — Portal endpoint base, typically `https://metis.dxvagentic.com` (existing integrations may still use `https://marketing.dxvagentic.com` during the rebrand migration; both work)\n\nStore these as environment variables in your webshop's deployment.\n\n## Client side: capture click IDs on landing\n\nIn your landing-page handler (Next.js middleware, Express handler, anywhere):\n\n```ts\nimport { CLICK_IDS_COOKIE, captureClickIds, parseClickIdsCookie, serializeClickIdsCookie } from \"@dxv-systems/metis-client/client\";\n\n// On every request to a public page:\nconst url = new URL(request.url);\nconst captured = captureClickIds(url.searchParams);\n\nif (captured) {\n  // First-touch wins: don't overwrite an existing valid cookie.\n  const existing = parseClickIdsCookie(request.cookies.get(CLICK_IDS_COOKIE)?.value);\n  if (!existing) {\n    response.cookies.set(CLICK_IDS_COOKIE, serializeClickIdsCookie(captured), {\n      maxAge: 30 * 24 * 60 * 60, // 30 days\n      httpOnly: false,           // intentionally readable from client (analytics)\n      sameSite: \"lax\",\n      secure: true,\n    });\n  }\n}\n```\n\n`CLICK_IDS_COOKIE` (`\"dxv_click_ids\"`) is exported from both `/client` and `/server`, so apps no longer repeat the cookie name. Cookies written before 0.7.0 (without `gbraid`/`wbraid`) still parse.\n\n### Cookie encoding\n\n`parseClickIdsCookie` takes the value in whatever form you happen to hold it:\nthe raw percent-encoded string from a `Cookie` header, or the already-decoded\nstring a framework cookie API returns. You do not have to know which you have.\n\nThat tolerance is load-bearing rather than defensive. `next/headers` `cookies()`\nand `NextRequest.cookies.get()` decode the header themselves, while\n`NextResponse.cookies.set()` **encodes** what you give it — so a cookie written\nthrough middleware, on top of `serializeClickIdsCookie`'s own encoding, is\nstored double-encoded. Before this was handled, a value containing a literal\n`%` (a `utm_campaign` of `50%-korting`, say) failed to parse and the function\nreturned `null` with no error, losing attribution silently.\n\n## Server side: emit purchase event\n\nIn your order-completed handler (Stripe webhook, etc.):\n\n```ts\nimport { emitPurchase } from \"@dxv-systems/metis-client/server\";\nimport { parseClickIdsCookie } from \"@dxv-systems/metis-client/client\";\n\nawait emitPurchase({\n  customerId: Number(process.env.DXV_CUSTOMER_ID),\n  relaySecret: process.env.DXV_CONVERSIONS_RELAY_SECRET!,\n  relayUrl: process.env.DXV_CONVERSIONS_RELAY_URL!,\n  eventId: order.id,   // optional; see \"Deduping with a browser pixel\" below\n  order: {\n    id: order.id,\n    totalCents: order.totalCents,\n    currency: \"EUR\",\n  },\n  clickIds: parseClickIdsCookie(cookies.get(CLICK_IDS_COOKIE)),\n  customer: {\n    email: order.customer.email,    // plaintext; package hashes per Meta CAPI spec\n    phone: order.customer.phone,\n  },\n  consent: {\n    adUserData: consentState.adUserData,\n    adPersonalization: consentState.adPersonalization,\n  },\n});\n```\n\n`emitPurchase` retries 3 times on 5xx with exponential backoff. It throws typed errors:\n\n- `InvalidConfig` — missing/empty `relaySecret`, `relayUrl`, or `customerId`. Thrown synchronously before any network call.\n- `RelayBadRequest` — Portal returned 4xx (likely a stale `relaySecret` or wrong `customerId`). Not retried.\n- `RelayUnavailable` — Portal unreachable after 3 attempts.\n\n## Server side: emit lead event\n\nFor form-fill / signup conversions with no order attached (a quote request, a contact form, a demo booking):\n\n```ts\nimport { emitLead } from \"@dxv-systems/metis-client/server\";\nimport { parseClickIdsCookie } from \"@dxv-systems/metis-client/client\";\n\nawait emitLead({\n  customerId: Number(process.env.DXV_CUSTOMER_ID),\n  relaySecret: process.env.DXV_CONVERSIONS_RELAY_SECRET!,\n  relayUrl: process.env.DXV_CONVERSIONS_RELAY_URL!,\n  eventId: submission.id,   // optional; see \"Deduping with a browser pixel\" below\n  lead: {\n    id: submission.id,\n    valueCents: 0,   // optional; omit if the lead has no estimated value\n    currency: \"EUR\", // optional; only meaningful alongside valueCents\n  },\n  clickIds: parseClickIdsCookie(cookies.get(CLICK_IDS_COOKIE)),\n  customer: {\n    email: submission.email,\n    phone: submission.phone,\n  },\n});\n```\n\n`emitLead` shares config validation, PII hashing, signing and retry behavior with `emitPurchase` — same typed errors, same 5xx retry / 4xx-fatal rules. Only the input shape differs: `lead` (no `order`), and `lead.id` is the only required field.\n\nThe Portal stores lead events with `event_type = 'lead'`. As of this release nothing forwards leads to Meta CAPI / Google offline conversions yet — that's a later stage of the conversions rollout; purchases already forward.\n\n## Consent\n\nBoth `emitPurchase` and `emitLead` accept an optional `consent` field:\n\n```ts\nconsent: {\n  adUserData: boolean;        // Google Consent Mode v2 \"ad_user_data\"\n  adPersonalization: boolean; // Google Consent Mode v2 \"ad_personalization\"\n  decided?: boolean;          // has the visitor answered the banner? absent = yes\n}\n```\n\n**This is not optional in practice.** The Portal stores every event regardless, but its Meta CAPI / Google offline-conversions forwarders fail closed: an event that arrives without a `consent` object is never forwarded, only stored. If you want this conversion to actually reach Meta or Google, you must pass `consent` — read it from whatever consent-management platform your storefront already runs (a CMP, a cookie banner, your own preference store) and mirror its ad-consent state here. `adUserData` and `adPersonalization` are required when the object is present; there is no partial form.\n\n### Visitors who have not answered the banner\n\n`decided` (added in 0.7.0) lets Metis tell a visitor who refused from one who never answered. Absent means decided, so existing senders keep their meaning. When the visitor has not answered the banner yet, send:\n\n```ts\nconsent: { adUserData: false, adPersonalization: false, decided: false }\n```\n\nThis still fails closed like any other non-consent: nothing is forwarded to Meta or Google. Omit `consent` altogether only if your integration has no consent information at all; Metis reports that as a sender gap, which is different from \"undecided\". `decided` must be a boolean when present.\n\n## Paid-touch flag\n\n`detectPaidTouch(input)` (from `/client` or `/server`, added in 0.8.0) returns `true` when a landing URL says the visit came from an ad: any of `gclid`, `fbclid`, `gbraid` or `wbraid` is present and non-empty, or `utm_medium` (trimmed, case-insensitive) is one of `cpc`, `ppc`, `cpm`, `cpv`, `paid`, `paidsocial`, `paid_social`, `paid-social`, `paidsearch`, `paid_search`, `paid-search`, `display`. It accepts a `URLSearchParams`, a query string, a full URL string or a `URL`.\n\n```ts\nimport { detectPaidTouch } from \"@dxv-systems/metis-client/client\";\n\nconst paidTouch = detectPaidTouch(window.location.search); // keep in a variable\nawait emitLead({ /* ... */, paidTouch });\n```\n\nBoth `emitPurchase` and `emitLead` accept an optional `paidTouch: boolean`, sent as a top-level field. Metis's consent monitor counts it to split refused and undecided visitors into paid and organic; it is never forwarded to Meta or Google. Leave it out when you do not know (absent means unknown). It must be a boolean when present.\n\n**Hold the result in memory only.** Never write it to a cookie, localStorage or any other storage: that would be device storage without consent. A full reload or a return visit should give \"unknown\" (omit the flag), not a guess.\n\n## Deduping with a browser pixel\n\nIf your site *also* reports this conversion from the browser (a Meta Pixel `Purchase`/`Lead` event, a `gtag` conversion event), pass the same `eventId` to both the pixel call and `emitPurchase`/`emitLead`. Meta and Google dedupe a browser event against a server event by matching event name plus a shared event ID — without it, a conversion reported from both sides gets counted twice once Metis starts forwarding server events onward.\n\n`eventId` is optional — omit it if you have no browser-side counterpart for this conversion. When supplied it must be a non-empty string (whitespace-only is rejected) of at most 256 characters; use whatever ID you already share between your pixel and your server call (a `nanoid`, an order reference, a UUID).\n\n### Deriving a reproducible event id\n\nIf your browser pixel and your server-side emit don't share process state — the common case, since the pixel fires on the confirmation page and the server emit fires later from a webhook — you need a way to derive the *same* id on both sides from something they both already know, like the order number:\n\n```ts\nimport { purchaseEventUuid } from \"@dxv-systems/metis-client/client\"; // or /server\n\nconst eventId = purchaseEventUuid(order.number);\n// Pass this to both the pixel call (fbq('track', 'Purchase', {}, { eventID: eventId })\n// or posthog.capture(..., { uuid: eventId })) and to emitPurchase's `eventId` field.\n```\n\n`purchaseEventUuid` and the more general `deterministicEventUuid` are pure functions available from **both** `/client` and `/server` — the browser needs one to compute the id for its pixel call, and the server needs the identical function to recompute the same id later for the relay emit, without the two ever coordinating. Determinism is the whole point: a random per-event id can't be reproduced after the fact, and Meta/PostHog dedupe a server event against its browser counterpart by matching that id — get it wrong and every forwarded conversion double-counts, silently.\n\nThe output is also shaped as a valid RFC-4122 uuid rather than a readable string like `purchase:ORD-0001`, because several platforms in this chain (PostHog included) accept only a valid uuid as an event id and silently swap in a random one otherwise — again with no error, just a dedup that quietly stops working.\n\nFor lead conversions use `leadEventUuid(lead.id)` the same way (equal to `deterministicEventUuid(\"lead:\" + lead.id)`), so a browser `Lead` pixel and `emitLead` share one event id.\n\n**Both sides must use it, or dedup does not work.** If your storefront's pixel derives its id one way and the server emit derives it another (or one side uses a random id), Meta/PostHog cannot tell the two events are the same conversion, and it gets counted twice once Metis forwards the server-side event onward.\n\n## Conventions\n\n- Money in cents (integer). The Portal validates `totalCents > 0`.\n- Currency as ISO 4217 (e.g. `EUR`, `USD`).\n- PII (email, phone) is **hashed in-memory** before the network call and never persisted by the Portal.\n- Click-ID cookie is **first-touch wins** for 30 days — do not overwrite if a valid cookie already exists.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}