flowsk.com
Reference

De-duplication and choosing a dedup key

The three rules a good key obeys, the right key for each event type, and what happens when you get it wrong. This is the shortest doc here and the one most worth reading.

Aug 4, 2026· 3 min read ·Docs
Quick answer

exists before the event · identical on both sides · unique per fact

A key that obeys all three rules makes client and server tracking additive. A key that breaks any one of them makes it worse than client-only — you will either double count or silently drop real sales.

Order id passes. Timestamp fails rule 2. Session id fails rule 3.

The mechanic

Flowsk stores events keyed on (property, dedup_key). When an event arrives with a key that already exists, it merges rather than inserting:

  • The conversion is marked source: "both" — confirmed independently by two systems.
  • Properties from both sides are combined.
  • The server’s value wins. It talked to the payment processor; the browser did not.

Order does not matter. Client-first and server-first produce identical records.

The three rules

1. It must exist before the event fires. Both sides need to derive the same string at the moment they send. You cannot reconcile later on a key you invented at report time.

2. It must be identical on both sides. Same case, same prefix, same string. order_10482 and 10482 are two different facts, and the system has no way to know you meant them to be one.

3. It must be unique per fact. One key per order, not per session and not per customer. Reusing a key across facts silently swallows the second one.

Every broken de-duplication we have seen violates exactly one of these.

The right key by event type

Event Key Why
Purchase order_10482 The order id exists on both sides at confirmation
Subscription renewal invoice_in_1a2b3c Invoice, not subscription — one key per charge
Refund refund_re_9x8y Its own fact; references the order in properties
Signup user_4821 The user id, once the record exists
Lead / form submission_9f2a The submission id, not the email
Trial start trial_4821 The trial record’s id

Note what is missing: the email address. It works for identification (one person, one email) and fails badly for purchases, because people buy more than once and every subsequent order would be discarded as a duplicate.

Keys that fail

Timestamps. purchase_1754239281. The two sides will never agree to the second, let alone the millisecond. Rule 2.

Session ids. A session can contain two purchases. Rule 3.

Email for purchases. A returning customer’s second order is dropped. Rule 3.

Random UUIDs generated independently on each side. Two different values for one fact. Rule 2, in its purest form.

Cart id. Often reused after checkout, or regenerated mid-session. Rule 3, unpredictably.

When the client cannot know the key

This is the common case on hosted checkouts, and the solution is always the same: generate the key on the server and hand it to the client.

  • Shopify — put the visitor id in a cart attribute; read the order id back in the orders/paid webhook and let the client-side confirmation page read it from the order object.
  • Stripe Checkout — the session id is available in both the success-page redirect and the webhook. It is a near-perfect key.
  • Custom checkout — render the order id into the confirmation page as a data attribute and read it from the snippet.

If neither side can obtain a shared key, send server-side only. An accurate count without a client-side companion is far better than a double count.

The free bonus: idempotency

Payment webhooks retry. Stripe retries. Shopify retries. Your own job queue retries after a deploy.

Because de-duplication is keyed permanently rather than within a time window, a webhook delivered five times produces one conversion. You get idempotency for free, which is a good enough reason to use a dedup key even if you never send a single client-side purchase event.

Verifying it

Open the journey explorer and find a recent purchase. The event should read source: both. If you see two purchase rows for one order, your keys are not matching — check case and prefix first, since that is nearly always the cause.

Frequently asked questions

What key should I use for purchases?

The order id, or the payment intent / checkout session id. Something the browser can read at confirmation time and your webhook receives independently.

What about subscriptions and renewals?

The invoice id, not the subscription id. One key per charge, or every renewal after the first will be swallowed as a duplicate.

What if the client cannot know the key?

Generate it on the server and pass it to the client — in checkout metadata, a cart attribute, or a data attribute on the confirmation page. A key invented at report time is not a key.

Does de-duplication expire?

No. The key is unique per property, permanently. That is what makes it safe for webhooks that retry days later.

Do I need keys on behavioural events?

No. Pageviews and clicks are cheap and expendable; a duplicate changes nothing that matters. Reserve keys for identity and money.

Watch it go wrong

Four configurations, one known truth. See client-only under-count, both-without-a-key double count, and a shared key fix both.

Open the simulator

Stop guessing which ad made the sale.

Flowsk Signals stitches the anonymous click to the email to the purchase — first-party, server-side, de-duplicated. One snippet, $29/mo, and every conversion comes with a receipt you can inspect.

Keep reading