@walkeros/web-destination-pinterest 3.3.0-next-1776098542393

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 elbWalker GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,249 @@
1
+ # Pinterest Tag Web Destination for walkerOS
2
+
3
+ Forward walkerOS events to the Pinterest Tag (`window.pintrk`) for conversion
4
+ tracking, enhanced matching, and dynamic retargeting.
5
+
6
+ ## Source Code
7
+
8
+ [github.com/elbwalker/walkerOS/tree/main/packages/web/destinations/pinterest](https://github.com/elbwalker/walkerOS/tree/main/packages/web/destinations/pinterest)
9
+
10
+ ## NPM
11
+
12
+ [`@walkeros/web-destination-pinterest`](https://www.npmjs.com/package/@walkeros/web-destination-pinterest)
13
+
14
+ ## Documentation
15
+
16
+ [walkeros.io/docs/destinations/web/pinterest](https://www.walkeros.io/docs/destinations/web/pinterest)
17
+
18
+ ## Features
19
+
20
+ - **Standard event taxonomy** — explicit `mapping.name` rename to Pinterest's
21
+ lowercase concatenated names (`pagevisit`, `addtocart`, `checkout`,
22
+ `viewcontent`, `lead`, `signup`, `search`, `custom`, ...).
23
+ - **Inline `line_items` for multi-product** — single
24
+ `pintrk('track', 'checkout', { line_items: [...] })` call (NOT N separate
25
+ calls). Built via the standard walkerOS `loop` mapping syntax.
26
+ - **Enhanced matching** — strict allow-list of `em` (email) and `external_id`.
27
+ The Pinterest JS tag auto-hashes `em` with SHA-256 — the destination passes
28
+ raw values through and never hashes.
29
+ - **Auto `event_id` for dedup** — every `pintrk('track', ...)` call attaches the
30
+ walkerOS event `id` as `event_id`, ready for cross-channel deduplication with
31
+ a future server (Conversions API) destination.
32
+ - **Consent-aware suppression** — `on('consent')` flips a runtime state flag.
33
+ After revocation, subsequent track calls are suppressed even though Pinterest
34
+ has no `opt_in`/`opt_out` SDK API.
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ npm install @walkeros/web-destination-pinterest
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ```json
45
+ {
46
+ "destinations": {
47
+ "pinterest": {
48
+ "package": "@walkeros/web-destination-pinterest",
49
+ "config": {
50
+ "consent": { "marketing": true },
51
+ "loadScript": true,
52
+ "settings": {
53
+ "apiKey": "2612345678901"
54
+ },
55
+ "mapping": {
56
+ "page": {
57
+ "view": { "name": "pagevisit" }
58
+ },
59
+ "order": {
60
+ "complete": {
61
+ "name": "checkout",
62
+ "data": {
63
+ "map": {
64
+ "value": "data.total",
65
+ "order_id": "data.id",
66
+ "currency": { "key": "data.currency", "value": "EUR" }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ ## Configuration
79
+
80
+ ### Settings
81
+
82
+ | Key | Type | Required | Notes |
83
+ | ------------ | ----------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
84
+ | `apiKey` | `string` | yes | Pinterest Tag ID (numeric string, e.g. `"2612345678901"`). Found in Pinterest Ads Manager under Conversions → Pinterest Tag. Passed to `pintrk('load', tagId)`. |
85
+ | `pageview` | `boolean` | no | Fire `pintrk('page')` once in init after `core.js` loads. Default `true`. Set `false` when walkerOS sources already emit page view events to avoid a duplicate initial fire. |
86
+ | `identify` | `Mapping.Value` | no | Mapping value resolving to `{ em?, external_id? }`. Resolved on the first event and fired via `pintrk('set', data)` whenever the resolved identity changes. |
87
+ | `include` | `string[]` | no | Sections to forward as prefixed properties in track payloads. Pinterest expects specific parameter names — explicit `mapping.data` is usually preferred over `include`. |
88
+ | `loadScript` | `boolean` (on `config`) | no | If `true`, the destination injects `https://s.pinimg.com/ct/core.js` on init. Set `false` when the host page already loads the Pinterest Tag snippet. |
89
+
90
+ ### Mapping (per-event)
91
+
92
+ | Key | Resolved shape | Notes |
93
+ | ------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
94
+ | `name` | Pinterest event string | One of `pagevisit`, `addtocart`, `checkout`, `viewcontent`, `lead`, `signup`, `search`, `custom`, ... Forwarded as the second arg to `track()`. |
95
+ | `data` (top-level) | `EventData` | Standard walkerOS data resolution. The resolved object is sent verbatim as the third arg to `track()`. |
96
+ | `settings.identify` | `{ em?, external_id? }` | Per-event identity override. Fires `pintrk('set', data)` BEFORE the track call when the resolved identity differs from the current state. |
97
+ | `settings.include` | `string[]` | Overrides destination-level `include` for this rule. |
98
+ | `skip` | `boolean` | Process side effects (identify) but suppress the default `pintrk('track', ...)` call. |
99
+
100
+ ## Event Mapping
101
+
102
+ Pinterest expects lowercase concatenated event names. The destination never
103
+ auto-maps — every conversion event needs an explicit `mapping.name`.
104
+
105
+ | walkerOS event | Pinterest event | Notes |
106
+ | ---------------- | --------------- | ---------------------------------------------------------------------------------- |
107
+ | `page view` | `pagevisit` | Fired automatically by `pintrk('page')` on init unless `settings.pageview: false`. |
108
+ | `product view` | `viewcontent` | Single product. Use `value`, `currency`, `product_id`, `product_name`. |
109
+ | `product add` | `addtocart` | Single product. Wrap in `line_items` array if you also want item-level data. |
110
+ | `order complete` | `checkout` | Multi-product. Use `mapping.data.line_items.loop` to iterate `event.nested`. |
111
+ | `site search` | `search` | Use `search_query` field. |
112
+ | `user login` | `lead` | Optional `lead_type`. Combine with `settings.identify` for enhanced matching. |
113
+ | `user signup` | `signup` | Same identify pattern as `lead`. |
114
+ | (custom) | `custom` | Send Pinterest's `custom` event for non-standard conversions. |
115
+
116
+ ## Multi-product checkout
117
+
118
+ Pinterest sends a SINGLE `pintrk('track', 'checkout', { line_items: [...] })`
119
+ call. The walkerOS `loop` mapping resolves a nested array INTO the data, NOT
120
+ into N separate SDK calls (different from Amplitude's per-item revenue pattern).
121
+
122
+ ```json
123
+ "order": {
124
+ "complete": {
125
+ "name": "checkout",
126
+ "data": {
127
+ "map": {
128
+ "value": "data.total",
129
+ "order_id": "data.id",
130
+ "currency": { "key": "data.currency", "value": "EUR" },
131
+ "line_items": {
132
+ "loop": [
133
+ "nested",
134
+ {
135
+ "condition": "$code: (v) => typeof v?.data?.price === 'number'",
136
+ "map": {
137
+ "product_id": "data.id",
138
+ "product_name": "data.color",
139
+ "product_price": "data.price",
140
+ "product_quantity": { "value": 1 }
141
+ }
142
+ }
143
+ ]
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ ```
150
+
151
+ ## Enhanced Matching
152
+
153
+ Pinterest's web tag accepts two enhanced matching fields, both auto-hashed by
154
+ the JS tag:
155
+
156
+ | Field | Source | Notes |
157
+ | ------------- | ---------------------------------- | -------------------------------------------------------- |
158
+ | `em` | `data.email` / `user.email` / etc. | Pinterest auto-hashes — pass raw email, do NOT pre-hash. |
159
+ | `external_id` | `data.id` / `user.id` | Stable user identifier. Auto-hashed by Pinterest. |
160
+
161
+ The destination strictly limits to these two fields. CAPI-only fields (`ph`,
162
+ `fn`, `ln`, `address`, `country`, `ip`, `ua`) belong to the future server
163
+ destination and are intentionally not accepted here.
164
+
165
+ ```json
166
+ "user": {
167
+ "login": {
168
+ "name": "lead",
169
+ "settings": {
170
+ "identify": {
171
+ "map": {
172
+ "em": "data.email",
173
+ "external_id": "data.id"
174
+ }
175
+ }
176
+ },
177
+ "data": {
178
+ "map": { "lead_type": { "value": "login" } }
179
+ }
180
+ }
181
+ }
182
+ ```
183
+
184
+ Identity is diffed against the destination's runtime state —
185
+ `pintrk('set', ...)` only fires when the resolved identity actually changes.
186
+ Destination-level `settings.identify` is resolved lazily on the first event
187
+ (init has no event to bind to), so `pintrk('load', tagId)` is called without an
188
+ identity object.
189
+
190
+ ## Consent
191
+
192
+ Pinterest is an advertising platform — the Pinterest Tag falls under
193
+ **marketing** consent:
194
+
195
+ ```json
196
+ "config": {
197
+ "consent": { "marketing": true }
198
+ }
199
+ ```
200
+
201
+ walkerOS's collector blocks all events to this destination until `marketing` is
202
+ granted. **Pinterest has no `opt_in`/`opt_out` SDK API**, so the destination
203
+ implements consent revocation as a runtime suppression flag:
204
+
205
+ 1. `on('consent')` reads `config.consent` to determine which keys to check.
206
+ 2. If any required key resolves to `false`, the destination flips
207
+ `_state.consentGranted = false` and silently suppresses all subsequent
208
+ `pintrk('track', ...)` calls.
209
+ 3. If consent is later re-granted (all required keys `true`), the flag flips
210
+ back and tracking resumes.
211
+
212
+ The Pinterest Tag itself stays loaded throughout — only the walkerOS bridge goes
213
+ silent.
214
+
215
+ ### Skipping a fully configured rule
216
+
217
+ ```json
218
+ "user": {
219
+ "update": {
220
+ "skip": true,
221
+ "settings": {
222
+ "identify": {
223
+ "map": { "em": "data.email", "external_id": "data.id" }
224
+ }
225
+ }
226
+ }
227
+ }
228
+ ```
229
+
230
+ `mapping.skip: true` processes side effects (the `pintrk('set', ...)` identity
231
+ update) but suppresses the default `pintrk('track', ...)` call. Useful when you
232
+ only want to refresh enhanced matching without firing a conversion.
233
+
234
+ ## Future: Conversions API
235
+
236
+ A server-side Pinterest Conversions API destination is planned as a follow-up,
237
+ matching the Meta / TikTok / LinkedIn web+server split. It will share the
238
+ `event_id` field with this web destination for cross-channel deduplication.
239
+
240
+ ## Related
241
+
242
+ - [Pinterest Tag documentation](https://help.pinterest.com/en/business/article/install-the-pinterest-tag)
243
+ - [Enhanced match documentation](https://help.pinterest.com/en/business/article/enhanced-match)
244
+ - walkerOS
245
+ [understanding-destinations skill](https://www.walkeros.io/docs/destinations)
246
+
247
+ ## License
248
+
249
+ MIT
package/dist/dev.d.mts ADDED
@@ -0,0 +1,239 @@
1
+ import * as _walkeros_core_dev from '@walkeros/core/dev';
2
+ import { z } from '@walkeros/core/dev';
3
+ import { DestinationWeb } from '@walkeros/web-core';
4
+ import { Flow } from '@walkeros/core';
5
+
6
+ declare const SettingsSchema: z.ZodObject<{
7
+ apiKey: z.ZodString;
8
+ pageview: z.ZodOptional<z.ZodBoolean>;
9
+ identify: z.ZodOptional<z.ZodUnknown>;
10
+ }, z.core.$strip>;
11
+ type Settings = z.infer<typeof SettingsSchema>;
12
+
13
+ declare const MappingSchema: z.ZodObject<{
14
+ identify: z.ZodOptional<z.ZodUnknown>;
15
+ }, z.core.$strip>;
16
+ type Mapping = z.infer<typeof MappingSchema>;
17
+
18
+ declare const settings: _walkeros_core_dev.JSONSchema;
19
+ declare const mapping: _walkeros_core_dev.JSONSchema;
20
+
21
+ type index$1_Mapping = Mapping;
22
+ declare const index$1_MappingSchema: typeof MappingSchema;
23
+ type index$1_Settings = Settings;
24
+ declare const index$1_SettingsSchema: typeof SettingsSchema;
25
+ declare const index$1_mapping: typeof mapping;
26
+ declare const index$1_settings: typeof settings;
27
+ declare namespace index$1 {
28
+ export { type index$1_Mapping as Mapping, index$1_MappingSchema as MappingSchema, type index$1_Settings as Settings, index$1_SettingsSchema as SettingsSchema, index$1_mapping as mapping, index$1_settings as settings };
29
+ }
30
+
31
+ /**
32
+ * Pinterest Tag global. The queue-based function created by the Pinterest
33
+ * snippet. Before core.js loads, calls are queued on `pintrk.queue`.
34
+ * After core.js resolves the queue, `pintrk` is replaced with the real
35
+ * implementation. Both shapes accept the same argument signatures.
36
+ */
37
+ interface Pintrk {
38
+ (command: 'load', tagId: string, identify?: IdentifyFields): void;
39
+ (command: 'page'): void;
40
+ (command: 'track', eventName: string, eventData?: EventData, callback?: (didInit: boolean, error?: unknown) => void): void;
41
+ (command: 'set', userData: IdentifyFields): void;
42
+ queue?: unknown[][];
43
+ version?: string;
44
+ }
45
+ declare global {
46
+ interface Window {
47
+ pintrk?: Pintrk;
48
+ }
49
+ }
50
+ /**
51
+ * Enhanced matching fields. Web destination strictly limits to the two
52
+ * documented client-side Pinterest Tag fields. CAPI-only fields (ph, fn,
53
+ * ln, address, country, ip, ua) belong to a future server destination
54
+ * and are intentionally not accepted here.
55
+ *
56
+ * The Pinterest JS tag auto-hashes `em` with SHA-256 before transmission —
57
+ * the destination does NOT hash.
58
+ */
59
+ interface IdentifyFields {
60
+ em?: string;
61
+ external_id?: string;
62
+ }
63
+ /**
64
+ * Event data payload for pintrk('track', ...). Pinterest has a prescribed
65
+ * set of parameter names. Common parameters across events plus an open
66
+ * `[key: string]: unknown` for event-specific fields (search_query,
67
+ * video_title, lead_type, opt_out_type, custom properties, etc.).
68
+ */
69
+ interface EventData {
70
+ value?: number;
71
+ order_quantity?: number;
72
+ currency?: string;
73
+ event_id?: string;
74
+ order_id?: string;
75
+ promo_code?: string;
76
+ property?: string;
77
+ product_name?: string;
78
+ product_id?: string;
79
+ product_category?: string;
80
+ product_brand?: string;
81
+ product_price?: number;
82
+ product_quantity?: number;
83
+ product_variant_id?: string;
84
+ product_variant?: string;
85
+ line_items?: LineItem[];
86
+ search_query?: string;
87
+ video_title?: string;
88
+ lead_type?: string;
89
+ opt_out_type?: 'LDP';
90
+ [key: string]: unknown;
91
+ }
92
+ interface LineItem {
93
+ product_name?: string;
94
+ product_id?: string;
95
+ product_category?: string;
96
+ product_brand?: string;
97
+ product_price?: number;
98
+ product_quantity?: number;
99
+ product_variant_id?: string;
100
+ product_variant?: string;
101
+ [key: string]: unknown;
102
+ }
103
+ /**
104
+ * Env — mock surface for tests. Pinterest Tag uses window.pintrk, so
105
+ * the env extends DestinationWeb.Env (which already provides window
106
+ * and document).
107
+ */
108
+ interface Env extends DestinationWeb.Env {
109
+ window: {
110
+ pintrk?: Pintrk;
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Pre-init environment — pintrk is not yet on the window.
116
+ * Used to validate the init path that creates the queue function.
117
+ */
118
+ declare const init: Env;
119
+ /**
120
+ * Post-init environment — pintrk is available as a spy-able function.
121
+ * Tests clone this and replace `pintrk` with a jest.fn() to record calls.
122
+ */
123
+ declare const push: Env;
124
+ /** Simulation tracking paths for CLI --simulate. */
125
+ declare const simulation: string[];
126
+
127
+ declare const env_init: typeof init;
128
+ declare const env_push: typeof push;
129
+ declare const env_simulation: typeof simulation;
130
+ declare namespace env {
131
+ export { env_init as init, env_push as push, env_simulation as simulation };
132
+ }
133
+
134
+ /**
135
+ * Output convention:
136
+ * - Single SDK call: out = ['window.pintrk', 'track', 'checkout', { ... }]
137
+ * (flat array: first element is the dot-path, remaining elements are args)
138
+ * - Multiple SDK calls: out = [ [path, ...args], [path, ...args] ]
139
+ * - Zero SDK calls: out = []
140
+ *
141
+ * The step-examples test runner normalizes both shapes via flatten().
142
+ */
143
+ /**
144
+ * Default event forwarding — no rule. Without a mapping.name, the walkerOS
145
+ * event name is forwarded as-is. Pinterest accepts it but won't match a
146
+ * standard conversion event. event_id is auto-attached for deduplication.
147
+ */
148
+ declare const defaultForward: Flow.StepExample;
149
+ /**
150
+ * Wildcard ignore pattern — the standard walkerOS way to suppress noisy
151
+ * events. `mapping.ignore: true` at rule level produces zero SDK calls.
152
+ */
153
+ declare const wildcardIgnored: Flow.StepExample;
154
+ /**
155
+ * Standard Pinterest event rename via mapping.name. walkerOS "page view"
156
+ * → Pinterest "pagevisit". The ONLY way to get Pinterest to recognize
157
+ * a conversion event — the destination never auto-maps.
158
+ */
159
+ declare const pageViewRename: Flow.StepExample;
160
+ /**
161
+ * Search — walkerOS site search → Pinterest "search" with search_query.
162
+ * Illustrates single-field mapping.data resolution.
163
+ */
164
+ declare const siteSearch: Flow.StepExample;
165
+ /**
166
+ * Single-product viewcontent. Illustrates:
167
+ * - currency fallback via { key, value: 'EUR' }
168
+ * - flat product_* parameter names
169
+ */
170
+ declare const productViewContent: Flow.StepExample;
171
+ /**
172
+ * Add-to-cart with an inline line_items array.
173
+ *
174
+ * Pinterest sends a single track() call with line_items as an ARRAY
175
+ * INSIDE the event data — NOT a loop of N separate calls.
176
+ */
177
+ declare const productAddToCart: Flow.StepExample;
178
+ /**
179
+ * Multi-product checkout — the canonical Pinterest ecommerce pattern.
180
+ * `line_items.loop: ["nested", { condition, map }]` iterates event.nested
181
+ * and produces ONE array inside a SINGLE track() call.
182
+ *
183
+ * Default fixture has 3 nested entries: ers (420 black), cc (42, no color),
184
+ * gift (no price → filtered out by condition).
185
+ */
186
+ declare const orderCompleteCheckout: Flow.StepExample;
187
+ /**
188
+ * Lead conversion with per-event enhanced matching update.
189
+ *
190
+ * The rule-level settings.identify resolves to { em, external_id } and
191
+ * triggers pintrk('set', data) BEFORE the track() call — enhanced
192
+ * matching data is associated with the same event via event_id.
193
+ */
194
+ declare const userLoginLead: Flow.StepExample;
195
+ /**
196
+ * mapping.skip — processes side effects (identify set) but suppresses
197
+ * the default pintrk('track', ...) call. Useful when an upstream
198
+ * destination already fired the conversion and you only want to update
199
+ * enhanced matching.
200
+ */
201
+ declare const identifyOnlySkip: Flow.StepExample;
202
+ /**
203
+ * Consent revocation — walkerOS walker consent { marketing: false }.
204
+ * The destination's on('consent') handler flips the runtime state flag
205
+ * and stops calling pintrk('track', ...) for subsequent events. There
206
+ * is NO opt_out SDK call — Pinterest has no such API.
207
+ *
208
+ * Expected out: [] — no pintrk calls fire as a direct result of the
209
+ * consent dispatch.
210
+ */
211
+ declare const consentRevoke: Flow.StepExample;
212
+ /**
213
+ * Consent grant — explicit opt-in. Same behavior: the destination's
214
+ * on('consent') flips the flag. No SDK call fires.
215
+ */
216
+ declare const consentGrant: Flow.StepExample;
217
+
218
+ declare const step_consentGrant: typeof consentGrant;
219
+ declare const step_consentRevoke: typeof consentRevoke;
220
+ declare const step_defaultForward: typeof defaultForward;
221
+ declare const step_identifyOnlySkip: typeof identifyOnlySkip;
222
+ declare const step_orderCompleteCheckout: typeof orderCompleteCheckout;
223
+ declare const step_pageViewRename: typeof pageViewRename;
224
+ declare const step_productAddToCart: typeof productAddToCart;
225
+ declare const step_productViewContent: typeof productViewContent;
226
+ declare const step_siteSearch: typeof siteSearch;
227
+ declare const step_userLoginLead: typeof userLoginLead;
228
+ declare const step_wildcardIgnored: typeof wildcardIgnored;
229
+ declare namespace step {
230
+ export { step_consentGrant as consentGrant, step_consentRevoke as consentRevoke, step_defaultForward as defaultForward, step_identifyOnlySkip as identifyOnlySkip, step_orderCompleteCheckout as orderCompleteCheckout, step_pageViewRename as pageViewRename, step_productAddToCart as productAddToCart, step_productViewContent as productViewContent, step_siteSearch as siteSearch, step_userLoginLead as userLoginLead, step_wildcardIgnored as wildcardIgnored };
231
+ }
232
+
233
+ declare const index_env: typeof env;
234
+ declare const index_step: typeof step;
235
+ declare namespace index {
236
+ export { index_env as env, index_step as step };
237
+ }
238
+
239
+ export { index as examples, index$1 as schemas };