Skip to content

feat(klaviyo): resolve unique_id from a mapping rule - #709

Open
chrizzellu wants to merge 2 commits into
elbwalker:mainfrom
WakeSys:feat/klaviyo-unique-id
Open

feat(klaviyo): resolve unique_id from a mapping rule#709
chrizzellu wants to merge 2 commits into
elbwalker:mainfrom
WakeSys:feat/klaviyo-unique-id

Conversation

@chrizzellu

@chrizzellu chrizzellu commented Aug 18, 2026

Copy link
Copy Markdown

Stacked on #708 — this branch contains that commit. Merge #708 first and this diff reduces to the uniqueId commit alone. Happy to squash the two into one PR if you'd prefer.

What

Mapping rules accept settings.uniqueId, resolved onto the event's uniqueId attribute (unique_id on the wire).

Why

Klaviyo deduplicates on the tuple (profile, metric, unique_id). Without the field, the API defaults it to "the time to the second", so two events for one profile and metric landing in the same second collide and one is dropped — data loss, not just a missed dedup.

With it, the same event can reach Klaviyo from several producers — browser tracking running alongside this destination, a CSV or backfill import, a retried delivery — without being counted twice. There is currently no way to express that key through this destination.

Verified against the live API

I ran a dedup probe against a real Klaviyo account (four groups, two sends each, controls included):

group sent recorded
server unique_id ×2 (control) 2 1
client unique_id ×2 (control) 2 1
client unique_id → server unique_id 2 1
client $event_id property → server unique_id 2 2

Both controls dedup, so the method holds. unique_id deduplicates across the browser (/client/events/) and server (/api/events/) boundary; a $event_id event property does not participate at all. That is what makes this the right field to expose.

Details

Rule-level only, mirroring the existing settings.value — a dedup key is inherently per-event, so a destination-level default did not seem worth it (happy to add if you disagree).

Numeric ids are coerced to strings: order ids are commonly numeric, and silently dropping one would fall back to the one-per-second window and re-admit exactly the duplicates the key exists to prevent. NaN, Infinity, empty string, booleans and objects all resolve to no key rather than a garbage one.

Verification

  • npm run verify:touched -- server-destination-klaviyo — green
  • npm run verify:affected — 77/77 green
  • Package tests 10/10; two new step examples (one public, one public: false regression fixture for the numeric case), each watched fail before implementing.

Changeset included (minor).

Summary by CodeRabbit

  • New Features
    • Added Klaviyo event deduplication using optional unique identifiers, including numeric ID conversion.
    • Revenue values now use Klaviyo’s event-level value and currency attributes.
  • Documentation
    • Updated Klaviyo mapping guidance with revenue tracking, deduplication, fallback behavior, and numeric identifier handling.
  • Examples
    • Added examples demonstrating string and numeric identifiers for event deduplication.

chrizzellu and others added 2 commits August 18, 2026 11:46
A mapping rule's `settings.value` was assigned to `properties.value`, where
Klaviyo treats it as a segmentable custom property and ignores it for revenue
reporting, while `valueCurrency` was emitted on the event attributes with no
sibling `value` to denominate.

`klaviyo-api`'s ObjectSerializer builds the request body from
EventCreateQueryV2ResourceObjectAttributes.attributeTypeMap, which maps
`value` -> `value` and `valueCurrency` -> `value_currency` at the attributes
level; `properties` is typed `object` and passes through untouched. So the
currency was reaching Klaviyo and the amount never was.

Both now sit together on the attributes. Properties are unaffected -- map a
value through the rule's `data` mapping to also keep it as a custom property.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mapping rules accept `settings.uniqueId`, resolved onto the event's `uniqueId`
attribute (`unique_id` on the wire). Klaviyo keeps only the first event with a
given value for one profile and metric, so the same order can reach it from
several producers -- browser tracking, a CSV or backfill import, a retried
delivery -- without being counted twice.

Absent the field Klaviyo dedups on the event time truncated to the second,
which admits one event per profile per metric per second. Numeric ids are
coerced to strings: order ids are commonly numeric, and dropping one would
silently fall back to that window and re-admit the duplicates the key exists
to prevent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 44733ea4-3a46-410e-bbbf-e389bb147e90

📥 Commits

Reviewing files that changed from the base of the PR and between b9a7466 and 16728ad.

📒 Files selected for processing (7)
  • .changeset/klaviyo-revenue-value-attribute.md
  • .changeset/klaviyo-unique-id.md
  • packages/server/destinations/klaviyo/src/examples/step.ts
  • packages/server/destinations/klaviyo/src/push.ts
  • packages/server/destinations/klaviyo/src/schemas/mapping.ts
  • packages/server/destinations/klaviyo/src/types/index.ts
  • website/docs/destinations/server/klaviyo.mdx

Included review availability: Your plan includes up to 2 reviews per rolling hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The Klaviyo destination now emits revenue as top-level value and valueCurrency attributes. It also supports optional uniqueId mapping, including numeric-to-string coercion for event deduplication. Examples, documentation, and changesets describe the updated behavior.

Changes

Klaviyo event mapping

Layer / File(s) Summary
Mapping contracts and descriptions
packages/server/destinations/klaviyo/src/types/index.ts, packages/server/destinations/klaviyo/src/schemas/mapping.ts
The mapping contract documents top-level revenue attributes and adds optional uniqueId support.
Event serialization and ID coercion
packages/server/destinations/klaviyo/src/push.ts
Klaviyo events emit value, valueCurrency, and uniqueId as top-level attributes. Finite numeric identifiers convert to strings.
Examples and release documentation
packages/server/destinations/klaviyo/src/examples/step.ts, website/docs/destinations/server/klaviyo.mdx, .changeset/*
Examples and documentation cover revenue serialization, string and numeric unique identifiers, fallback behavior, and release notes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 16728

The change adds rule-level resolution of Klaviyo's unique event identifier, including numeric values, while invalid values remain unset. No actionable merge-blocking risk remains after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant Mapping
  participant Push as push.ts
  participant ResolveId as resolveId
  participant Klaviyo
  Mapping->>Push: Provide value and uniqueId mappings
  Push->>ResolveId: Resolve mapped uniqueId
  ResolveId-->>Push: Return string identifier
  Push->>Klaviyo: Emit value, valueCurrency, and uniqueId
Loading

Suggested reviewers: alexanderkirtzel

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: resolving Klaviyo's unique identifier from a mapping rule.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant