> For the complete documentation index, see [llms.txt](https://docs.dinmo.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.dinmo.io/identity-resolution/event-stitching/event-stitching-how-it-works.md).

# How Event Stitching works

Event Stitching builds an event profile graph from event records and the identifiers observed on those events.

The graph is deterministic: the same input data, identifier mappings, and policy produce the same stitching decisions. The goal is not to merge every event that shares any value. The goal is to connect events when the configured evidence is strong enough, while keeping weak or polluted identifiers from collapsing unrelated activity.

## Mental model

Think of each event as evidence.

```
event
  ├─ user_id = 123
  ├─ email = alice@example.com
  ├─ anonymous_id = anon_abc
  └─ session_id = sess_1
```

DinMo standardizes the identifier values, evaluates them in policy order, and assigns the event to a `dinmo_stitched_profile_id` when matching is safe.

The example profile IDs below are shortened for readability.

```
dinmo_stitched_profile_id: profile_001
  ├─ user_id: 123              active anchor
  ├─ email: alice@example.com  active anchor
  ├─ anonymous_id: anon_abc    active
  └─ session_id: sess_1        active until its stitching lifetime expires
```

The profile is called **Known** when it has at least one active anchor identifier. It is called **Anonymous** when it only has weak identifiers.

## Processing order

For each run, DinMo follows the same high-level flow:

1. Select the next event window.
2. Read events from the configured models.
3. Extract identifier observations from mapped fields.
4. Standardize values and remove blocked or expired values from matching.
5. Evaluate identifiers from strongest to weakest priority.
6. Attach events to existing profiles, create new profiles, or merge compatible profiles.
7. Apply protections for unsafe or excessive identifier values.
8. Publish attribution, profiles, identifier values, redirects, audit events, and run metrics.

Priority matters because a strong identifier can protect the graph from weaker evidence.

Example order:

| Priority | Identifier   | Typical role                                    |
| -------- | ------------ | ----------------------------------------------- |
| 1        | User ID      | Strong authenticated anchor.                    |
| 2        | Email        | Strong anchor when standardized and governed.   |
| 3        | Anonymous ID | Useful pre-login identifier.                    |
| 4        | Session ID   | Short-lived activity context.                   |
| 5        | IP address   | Weak signal, usually short-lived or audit-only. |

## Common outcomes

Each processed event and identifier value has an explainable outcome.

| Outcome                                           | What it means                                                                                            | Where to inspect it                              |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| Event stitched                                    | The event was attached to a `dinmo_stitched_profile_id`.                                                 | `identity_event_profile_attribution`             |
| Event excluded with `no_matchable_identifier`     | The event had no usable identifier after standardization, blocked values, and stitching lifetime checks. | Attribution and run metrics                      |
| Event excluded with `conflicted_identifier_value` | The event depended on a quarantined identifier value.                                                    | Attribution, identifier values, and audit events |
| Event excluded with `limit_conflict`              | Stitching the event would violate a configured identifier policy.                                        | Attribution, policy, and audit events            |
| Value blocked                                     | The value is ignored for matching because it is a placeholder, test value, or known bad value.           | Identifier values and audit events               |
| Value expired                                     | The value is past its stitching lifetime and no longer matches future events.                            | Identifier values                                |
| Value demoted                                     | The value remains evidence but no longer participates in active matching.                                | Identifier values and audit events               |
| Profile redirected                                | A profile merged into another surviving profile.                                                         | Redirects and profiles                           |

## Example 1: anonymous visitor becomes known

At first, the user browses anonymously.

| Time | Event        | Identifiers               |
| ---- | ------------ | ------------------------- |
| `t1` | Page view    | `anonymous_id = anon_abc` |
| `t2` | Product view | `anonymous_id = anon_abc` |

DinMo creates an anonymous event profile:

```
profile_001
  └─ anonymous_id: anon_abc
```

Later, the user logs in.

| Time | Event | Identifiers                                |
| ---- | ----- | ------------------------------------------ |
| `t3` | Login | `user_id = 123`, `anonymous_id = anon_abc` |

The strong `user_id` becomes active on the same profile. The profile becomes Known.

```
profile_001
  ├─ user_id: 123              anchor
  └─ anonymous_id: anon_abc
```

Future events with `user_id = 123` or with the still-active `anonymous_id = anon_abc` can attach to `profile_001`.

## Example 2: one person uses two devices

The same user logs in on a laptop and then on a phone.

| Time | Event        | Identifiers                             |
| ---- | ------------ | --------------------------------------- |
| `t1` | Laptop login | `user_id = 123`, `device_id = laptop_1` |
| `t2` | Phone login  | `user_id = 123`, `device_id = phone_1`  |

The strong `user_id` connects both devices to the same event profile.

```
profile_001
  ├─ user_id: 123
  ├─ device_id: laptop_1
  └─ device_id: phone_1
```

This is expected. A strong anchor explains why the two device identifiers belong to the same event profile.

If `Max unique values per profile` is configured for `device_id`, DinMo keeps only a bounded number of active device IDs for the profile. Older device IDs can remain visible as audit evidence but stop creating new matches.

## Example 3: two users share a weak identifier

Two different people use the same shared device or browser.

| Time | Event       | Identifiers                                    |
| ---- | ----------- | ---------------------------------------------- |
| `t1` | Alice login | `user_id = alice`, `cookie_id = shared_cookie` |
| `t2` | Bob login   | `user_id = bob`, `cookie_id = shared_cookie`   |

The `user_id` values are strong anchors and identify different people. DinMo should not collapse Alice and Bob into one event profile only because they share a cookie.

Expected graph:

```
profile_alice
  └─ user_id: alice

profile_bob
  └─ user_id: bob

cookie_id: shared_cookie
  └─ weak evidence, not enough to merge incompatible anchors
```

If the shared cookie touches too many profiles, `Max profiles per value` quarantines it. The value remains explainable in audit, but it no longer matches future events.

## Example 4: weak-only event on a shared value

Now imagine a later event arrives with only the shared cookie:

| Time | Event     | Identifiers                 |
| ---- | --------- | --------------------------- |
| `t3` | Page view | `cookie_id = shared_cookie` |

If `shared_cookie` is already quarantined, the event is excluded with `reason = conflicted_identifier_value`.

If it is not quarantined but the value is weak and ambiguous, the attribution depends on the current active graph and policy. This is why weak identifiers need clear protections:

* lower priority than anchors
* finite stitching lifetime
* `Max profiles per value`
* blocked values for placeholders and known bad values

Do not rely on weak-only events from shared identifiers as high-confidence person-level attribution.

## Example 5: account switching on the same browser

A browser can legitimately be used by more than one person.

| Time | Event               | Identifiers                                |
| ---- | ------------------- | ------------------------------------------ |
| `t1` | Anonymous page view | `cookie_id = browser_1`                    |
| `t2` | Alice login         | `user_id = alice`, `cookie_id = browser_1` |
| `t3` | Bob login           | `user_id = bob`, `cookie_id = browser_1`   |
| `t4` | Anonymous page view | `cookie_id = browser_1`                    |

The cookie is useful evidence, but it should not be trusted more than authenticated anchors.

```
profile_alice
  └─ user_id: alice

profile_bob
  └─ user_id: bob

cookie_id: browser_1
  └─ shared weak value
```

If the cookie crosses `Max profiles per value`, DinMo quarantines it. The later weak-only event can then be excluded with `reason = conflicted_identifier_value` instead of being forced onto Alice or Bob.

This protects the graph from account switching, family devices, kiosks, call-center devices, and other shared-browser patterns.

## Example 6: bad placeholder value

A tracking implementation emits the same placeholder value on many events.

| Time | Event     | Identifiers                                      |
| ---- | --------- | ------------------------------------------------ |
| `t1` | Page view | `email = test@test.com`, `anonymous_id = anon_1` |
| `t2` | Purchase  | `email = test@test.com`, `anonymous_id = anon_2` |
| `t3` | Signup    | `email = test@test.com`, `anonymous_id = anon_3` |

If `test@test.com` is treated as a real email, it can connect unrelated activity.

Add it to blocked values. Then it remains useful as audit evidence, but it is ignored for matching.

```
email: test@test.com
  └─ blocked value, not used for stitching
```

## Example 7: value-level quarantine vs active-value compaction

Event Stitching has two different protections that are easy to confuse.

### Max profiles per value

This asks: “Is this one value touching too many profiles?”

Example:

```
cookie_id: kiosk_cookie
  ├─ profile_001
  ├─ profile_002
  ├─ profile_003
  └─ profile_004
```

If the limit is 3 and the value touches 4 profiles, DinMo quarantines the value. It is no longer used for future matching.

### Max unique values per profile

This asks: “Is this one profile keeping too many active values of the same identifier?”

Example:

```
profile_001
  ├─ device_id: old_device_1
  ├─ device_id: old_device_2
  ├─ device_id: old_device_3
  └─ device_id: new_device_4
```

If the limit is 3, DinMo keeps the most recent active device IDs and demotes older ones from active matching.

This does not mean the old device value is globally bad. It means the profile has reached its active-value capacity for that identifier.

## Profile merges and stable IDs

Sometimes new evidence connects two event profiles safely.

| Before                                                         | New evidence                                                  | Result                                               |
| -------------------------------------------------------------- | ------------------------------------------------------------- | ---------------------------------------------------- |
| `profile_001` has `anonymous_id = anon_abc`                    | Later event has `anonymous_id = anon_abc` and `user_id = 123` | `profile_001` becomes known through `user_id = 123`. |
| `profile_001` has one device, `profile_002` has another device | Later event shows the same strong `user_id` on both           | One profile survives and the other redirects to it.  |

When a merge happens, always use `resolved_dinmo_stitched_profile_id` for downstream joins. Redirects preserve the history of older profile IDs.

## What to monitor

After each run, monitor:

| Signal              | Why it matters                                                        |
| ------------------- | --------------------------------------------------------------------- |
| Stitch rate         | How much processed activity is attached to event profiles.            |
| Known profile share | How much of the active graph is reachable through anchors.            |
| Exclusion reasons   | Whether events are excluded for coverage, quality, or policy reasons. |
| Conflicted values   | Whether weak identifiers are creating unsafe graph connections.       |
| Active conflicts    | Should normally be zero after protections are applied.                |
| Demoted values      | Shows values kept as evidence but removed from active matching.       |

Use [Review and monitor Event Stitching](/identity-resolution/event-stitching/event-stitching-review-and-monitor.md) for the UI workflow and [Investigate Event Stitching results](/identity-resolution/event-stitching/event-stitching-investigate-results.md) for warehouse queries.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.dinmo.io/identity-resolution/event-stitching/event-stitching-how-it-works.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
