> For the complete documentation index, see [llms.txt](https://developers.didomi.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers.didomi.io/api-and-platform/reference/releases.md).

# Releases

***

## Consents API update

<sub>***Updated***</sub><sub>*: 30 September 2026*</sub>

In order to improve the experience and performance of the Didomi Consents API, we are updating the logic by which end-users are identified in the API and how their consent records can be merged/calculated into a comprehensive consent profile.&#x20;

* [Overview of changes](#overview-of-changes)
* [Benefits](#benefits)
* [Rollout and opt-in](#rollout-and-opt-in)
* [Impact assessment](#impact-assessment)
* [Changes per endpoint](#changes-per-endpoint)
* [Downstream - webhooks and batch exports](#downstream-webhooks-and-batch-exports)

### Overview of changes

Once the new API behavior is activated for your organization, the Consents API will guarantee that **one organization user ID (OUID) corresponds to exactly one end-user record**. The end-user's full consent state will live on that single record.

Most clients use the OUID to refer to a concrete end-user in their own system. The Consents API historically did not enforce that one-to-one relationship (*i.e. multiple end-user records could share the same OUID, with their consent state spread across those records*). This update aligns the API with the way most clients already treat the OUID.

The update is a single design change with two complementary halves (both designed to avoid breaking existing requests) and will enable Didomi to enforce this one-to-one relationship. Each half affects a different part of your system.

* [Prevention (affects how requests are handled)](#prevention-affects-how-requests-are-handled)
* [Consolidation (affects existing data)](#consolidation-affects-existing-data)

#### Prevention (affects how requests are handled)

New end-users are written under the one-OUID-one-user rule from the moment the behavior is activated. The API contract is unchanged — no new error codes, no payload-shape changes, no removed fields.&#x20;

* When creating an event or a user, an OUID supplied in the request payload becomes authoritative. The service resolves the user via the OUID — reusing the existing user if one already exists under that OUID, or creating a new one if none does — and ignores any UID also supplied in the same payload.
* New users created with an OUID receive the same value as their user ID to ensure unicity.
* `PATCH /consents/users/{id}` no longer allows mutating the OUID.

Details per endpoint is available in [Changes per endpoint](#changes-per-endpoint).

#### Consolidation (affects existing data)

Existing multi-user OUIDs in your historic data are consolidated into a single canonical user record via a live migration. The migration runs on `GET /users/{id}?$by_organization_user_id=true&$merge_users=true` .

* When the Consents API is asked for a user via an OUID that maps to multiple users on this endpoint, it computes the merged consent state and **persists** it as a new canonical user record under that OUID.
* From that point onward, the new API behavior resolves every request targeting that OUID — reads and writes alike, including events — to the canonical record, instead of re-merging on each call.
* The historic user records and their events are retained and remain fully reachable on demand. See: [Retrieve historic source records](#retrieve-historic-source-records).

Additional details available in [Consolidation of existing multi-user OUIDs](#consolidation-of-existing-multi-user-ouids).

### Benefits

This change is not just a contract cleanup — it is a measurable upgrade on two fronts your integration touches every day, ergonomics and latency:

<table><thead><tr><th width="239.5999755859375">Benefit</th><th>Description</th></tr></thead><tbody><tr><td><strong>A single end-user record per OUID</strong></td><td><p>Previously, an OUID could be associated with multiple <code>user_id</code> records, scattering both consent state and event history across those records. Under the new behavior, each OUID resolves to a single canonical end-user record. </p><ul><li>Reads return that consolidated state</li><li>Writes (including event creation) land on the same record instead of spawning siblings. </li></ul><p>A single canonical record per OUID makes a broad range of API interactions noticeably more straightforward and convenient.</p></td></tr><tr><td><strong>Lower read latency</strong></td><td>Consolidated state is now precomputed and persisted which significantly improves latency of user read requests.</td></tr></tbody></table>

### Rollout and opt-in

The `2026-01-01` behavior is being rolled out in phases. Your organization's path through the rollout depends on whether your organization is an existing or a new client. Click the tabs below for more information.

{% tabs %}
{% tab title="Existing client" %}
Existing Didomi clients will fall into one of two groups depending on how these organizations use the Consents API.

* [Not calling the API directly or using it only through Didomi SDKs](#not-calling-the-api-directly-or-using-it-only-through-didomi-sdks)
* [Calling Consents API directly](#calling-consents-api-directly)

#### Not calling the API directly or using it only through Didomi SDKs

If your organization does not call the Consents API directly — or calls it only through Didomi's SDKs (web or mobile) — the new behavior is enabled for you **automatically** by Didomi. The changes are transparent at the SDK boundary and no integration work is required on your side.

{% hint style="warning" %}
**Note**: if your organization utilizes Didomi **cross-device sync**, your historic data is still subject to consolidation of multi-user OUIDs (see: [Consolidation of existing multi-user OUIDs](#consolidation-of-existing-multi-user-ouids)).
{% endhint %}

#### Calling Consents API directly

Clients integrating directly against the Consents API have a grace period during which the previous behavior remains the default. The new behavior is gated entirely behind a request header. In order to activate the behavior on a given request, include the following in the header:

```
x-didomi-consents-api-behavior: 2026-01-01
```

Requests without this header continue to behave exactly as they did before the update.&#x20;

Use the provided grace period to audit your integration against the scenarios posed in the [Impact assessment](#impact-assessment) section below and to verify your downstream consumers against the new behavior.

After the grace period ends, Didomi will enable the new behavior automatically for these clients.

{% hint style="info" %}
Keep the following in mind as you test the behavior:

* **Send the header on every request.** The API evaluates the header independently per endpoint call — including reads. Sending it on writes only, or on a subset of endpoints, produces inconsistent behavior across requests and may surface different views of the same end-user. Apply it uniformly across your integration for the duration of the grace period.
* **Pre-authorized (API-generated) consent links capture the opt-in at creation time.** When you create a link via `POST /consents/links` while sending the `x-didomi-consents-api-behavior: 2026-01-01` header, the behavior is embedded in the link's signed token and replayed when the link is executed from a browser. The end-user's browser click does not need to carry the header.
* **Digest-authorized (client-generated) consent links cannot opt in via the header.** These links are generated on your server with a digest/HMAC signature, carry no signed behavior claim, and the browser click that follows them cannot carry custom headers. The header has no effect on them — the new behavior applies to digest-authorized consent link traffic only once Didomi activates the behavior organization-wide.
  {% endhint %}
  {% endtab %}

{% tab title="New client" %}
Organizations that begin integrating with the Consents API after the rollout will receive the `2026-01-01` behavior unconditionally. The header is not required, and there is no historic-behavior fallback for these clients — the API exposes the new contract immediately.
{% endtab %}
{% endtabs %}

### Impact Assessment

Didomi has collated the following questions in order to help your organization understand whether it is impacted by the new behavior.

{% hint style="warning" %}
**Note**: If your organization answers ***Yes*** to any of the questions then you should continue reading as directed in the appropriate section.
{% endhint %}

1. **Do you send both `user.id` (UID) and `user.organization_user_id` (OUID) on the same write request?**\
   The OUID is now authoritative; the UID is ignored. See the following:\
   \- [Create a new event](#create-a-new-event)\
   \- [Create a new user](#create-a-new-user)\
   \- [Consent link events](#consent-link-events)
2. **Do you call `PATCH /consents/users/{id}` expecting to change `organization_user_id`?**\
   The API no longer allows patching the OUID. See: [Patch a user](#patch-a-user)
3. **Do you use Didomi's cross-device feature, or do you call read endpoints with `$merge_users=true` today?**\
   This applies only to the subset of end-users whose OUID is shared across multiple `user_id` records **and** are being read using `$merge_users=true` — for those, the merged consent state is now persisted as a canonical user record. End-users with a single record per OUID are not touched. Cross-device clients receive this behavior automatically. See: [Consolidation of existing multi-user OUIDs](#consolidation-of-existing-multi-user-ouids)
4. **Do any downstream consumers (data warehouse, BI joins, schema validators) assume `user.id` is a UUID?** \
   Under the new behavior, new OUID-keyed users — including those produced by the migration — receive the OUID as their `user.id`. Since OUIDs are typically client-supplied hashes rather than generated UUIDs, the `user.id` format shifts away from UUID for those records. See the following:\
   \- [Changes per endpoint](#changes-per-endpoint)\
   \- [Consolidation of existing multi-user OUIDs](#consolidation-of-existing-multi-user-ouids)

### Changes per endpoint

The matrices below describe the new behavior under the `2026-01-01` profile. The thread running through all four endpoints is the same: **when an OUID is provided, the OUID wins**. Each endpoint section opens with one or two request examples that show the divergent behavior directly, before the matrix.

{% hint style="info" %}
Cells marked **New behavior** are the points where the new contract diverges from the historic behavior. Cells not marked **New behavior** continue to work as they did before.
{% endhint %}

* [Create a new event](#create-a-new-event)
* [Create a new user](#create-a-new-user)
* [Patch a user](#patch-a-user)
* [Consent link events](#consent-link-events)

#### Create a new event

```shell
POST https://api.didomi.io/v1/consents/events
```

The endpoint enables your organization to create a new consent event for a new or existing end-user. In the body of the request, your organization identifies the end-user via `user.id` (UID) or `user.organization_user_id` (OUID).

{% hint style="success" %}
**New contract:** when both UID and OUID are sent in the same event, the OUID is the authoritative selector and the UID is ignored. New users created get the OUID as their UID.
{% endhint %}

<details>

<summary><strong>Example — UID and OUID both provided, both exist</strong></summary>

```shell
POST https://api.didomi.io/v1/consents/events?organization_id={organization_id}
```

```json
{
  "user": {
    "id": "uid-existing",
    "organization_user_id": "ouid-existing"
  },
  "consents": {
    "purposes": [{ "id": "cookies", "enabled": true, "enabled_li": true }]
  }
}
```

<table data-header-hidden><thead><tr><th width="205.79998779296875"></th><th></th></tr></thead><tbody><tr><td><strong>Historic</strong></td><td>The event updates the user identified by <code>uid-existing</code>. That user's OUID is patched to <code>ouid-existing</code>, even if another user already owned that OUID.</td></tr><tr><td><strong>New (<code>2026-01-01</code>):</strong></td><td><code>uid-existing</code> is ignored. The event updates the user identified by <code>ouid-existing</code> instead.</td></tr></tbody></table>

</details>

<details>

<summary><strong>Example — UID provided, OUID provided and does not exist</strong></summary>

```shell
POST https://api.didomi.io/v1/consents/events?organization_id={organization_id}
```

```json
{
  "user": {
    "id": "uid-existing-or-not",
    "organization_user_id": "ouid-new"
  },
  "consents": {
    "purposes": [{ "id": "cookies", "enabled": true, "enabled_li": true }]
  }
}
```

<table data-header-hidden><thead><tr><th width="205.79998779296875"></th><th></th></tr></thead><tbody><tr><td><strong>Historic</strong></td><td><code>uid-existing-or-not</code> is created or updated; its OUID is set to <code>ouid-new</code>.</td></tr><tr><td><strong>New (<code>2026-01-01</code>):</strong></td><td><code>uid-existing-or-not</code> is ignored. A new user is created with <code>id = organization_user_id = "ouid-new"</code>.</td></tr></tbody></table>

</details>

<table data-header-hidden><thead><tr><th width="176.7332763671875"></th><th width="173.20001220703125"></th><th></th><th></th></tr></thead><tbody><tr><td></td><td><br><strong>OUID not provided (guest consent)</strong></td><td><strong>OUID provided &#x26; exists</strong></td><td><strong>OUID provided &#x26; does not exist</strong></td></tr><tr><td><strong>UID not provided</strong></td><td>Return 400</td><td>User updated via OUID</td><td>User created with provided OUID. <em>UID for the user duplicates the provided OUID.</em></td></tr><tr><td><strong>UID provided &#x26; exists</strong></td><td>User updated via UID</td><td><strong>New behavior</strong> — Ignore provided UID and update end-user consent record via provided OUID</td><td><strong>New behavior</strong> — Ignore provided UID (that end-user record is not updated). Create a new user with the provided OUID. <em>UID for the new user duplicates the provided OUID.</em></td></tr><tr><td><strong>UID provided &#x26; does not exist</strong></td><td>User created with provided UID</td><td><strong>New behavior</strong> — Ignore provided UID and update end-user consent record via provided OUID</td><td><strong>New behavior</strong> — Ignore provided UID and create a new user with the provided OUID. <em>UID for the new user duplicates the provided OUID.</em></td></tr></tbody></table>

#### Create a new user

```shell
POST https://api.didomi.io/v1/consents/users
```

The endpoint enables your organization to create a new user to store their consent record. In the body of the request, your organization identifies the new end-user via `id` (UID) or `organization_user_id` (OUID).

{% hint style="success" %}
**New contract:**&#x20;

* a user cannot be created under an OUID that already has a record. The API returns `204` and creates nothing.&#x20;
* When both UID and OUID are sent, the OUID is the authoritative selector and the UID is ignored.&#x20;
* If a user is created using OUID, the record's UID will be a duplicate of the provided OUID.
  {% endhint %}

<details>

<summary><strong>Example — OUID exists, UID is anything</strong></summary>

```shell
POST https://api.didomi.io/v1/consents/users?organization_id={organization_id}
```

```json
{
  "id": "uid-any",
  "organization_user_id": "ouid-existing"
}
```

<table data-header-hidden><thead><tr><th width="205.79998779296875"></th><th></th></tr></thead><tbody><tr><td><strong>Historic</strong></td><td>Behavior depended on the UID — could create a user, could no-op, could create a parallel record sharing the OUID.</td></tr><tr><td><strong>New (<code>2026-01-01</code>):</strong></td><td><code>204 No Content</code>. No user is created. The existing record under <code>ouid-existing</code> is left untouched.</td></tr></tbody></table>

</details>

<details>

<summary><strong>Example — OUID does not exist, UID exists</strong></summary>

```shell
POST https://api.didomi.io/v1/consents/users?organization_id={organization_id}
```

```json
{
  "id": "uid-existing",
  "organization_user_id": "ouid-new"
}
```

<table data-header-hidden><thead><tr><th width="205.79998779296875"></th><th></th></tr></thead><tbody><tr><td><strong>Historic</strong></td><td><code>204 No Content</code>. No user created (because the UID already exists).</td></tr><tr><td><strong>New (<code>2026-01-01</code>):</strong></td><td>the UID is ignored. A new user is created with <code>id = organization_user_id = "ouid-new"</code>.</td></tr></tbody></table>

</details>

|                                   | <p><br><strong>OUID not provided (guest consent)</strong></p> | **OUID provided & exists**                          | **OUID provided & does not exist**                                                                                  |
| --------------------------------- | ------------------------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| **UID not provided**              | User created with a generated `id`                            | **New behavior** — Returns `204`. User not created. | User created with the provided OUID. *UID for the user duplicates the provided OUID.*                               |
| **UID provided & exists**         | Returns `204`. User not created.                              | Returns `204`. User not created.                    | **New behavior** — Ignore UID. Create user with the provided OUID. *UID for the user duplicates the provided OUID.* |
| **UID provided & does not exist** | User created with the provided UID                            | **New behavior** — Returns `204`. User not created. | **New behavior** — Ignore UID. Create user with the provided OUID. *UID for the user duplicates the provided OUID.* |

#### Patch a user

```shell
PATCH https://api.didomi.io/v1/consents/users/{id}
```

The endpoint enables your organization to update an existing user's data and/or consent status. Your organization identifies the end-user via `id` (UID) or `organization_user_id` (OUID).

{% hint style="success" %}
**New contract:** the OUID cannot be changed via `PATCH`. The `organization_user_id` field is silently ignored in the payload, regardless of its value. All other fields patch as before.
{% endhint %}

<details>

<summary><strong>Example — attempt to reassign the OUID</strong></summary>

```shell
PATCH https://api.didomi.io/v1/consents/users/uid-existing?organization_id={organization_id}
```

```json
{
  "organization_user_id": "ouid-new",
  "consents": {
    "purposes": [{ "id": "analytics", "enabled": true, "enabled_li": true }]
  }
}
```

<table data-header-hidden><thead><tr><th width="205.79998779296875"></th><th></th></tr></thead><tbody><tr><td><strong>Historic</strong></td><td>The user is patched, <strong>and</strong> the OUID is changed to <code>ouid-new</code>.</td></tr><tr><td><strong>New (<code>2026-01-01</code>):</strong></td><td>The user is patched (consents are updated). The <code>organization_user_id</code> field is silently ignored — the OUID retains its previous value. The response is <code>200</code>; no error is raised.</td></tr></tbody></table>

</details>

|                                  | **User has no OUID (`null`)**                                                | **User has OUID**                                                                      |
| -------------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| **OUID not provided**            | User is patched, OUID is unchanged (`null`)                                  | User is patched, OUID is unchanged                                                     |
| **OUID provided as `null`**      | User is patched, OUID is unchanged (`null`)                                  | **New behavior** — User is patched; OUID is not patched and retains its original value |
| **OUID provided & not modified** | User is patched, OUID is unchanged (`null`)                                  | User is patched, OUID is unchanged                                                     |
| **OUID provided & different**    | **New behavior** — User is patched; OUID is not patched and remains (`null`) | **New behavior** — User is patched; OUID is not patched and retains its original value |

#### Consent link events

A consent link is a pre-authenticated URL that creates a consent event when accessed from a browser. End-users use these links to update their consent status. Didomi supports two types of authorization for consent links:

* **Pre-authorized** — link created beforehand via the Didomi API; carries a unique signed token in the URL.
* **Digest** — link created ad-hoc with a unique hash digest or HMAC signature; no API call required and can be generated on your server.

{% hint style="warning" %}
**Note**: Opting into the new `2026-01-01` behavior will depend on the link's authorization type:

* **Pre-authorized links opt in via the creation request.** Send the `x-didomi-consents-api-behavior: 2026-01-01` header on the `POST /consents/links` call. The behavior is embedded in the link's signed token and replayed automatically when the link is executed, so the end-user's browser click carries the opt-in without needing to send the header itself.
* **Digest-authorized links cannot opt in.** They are generated on your server without an API call and carry no signed behavior claim, and the browser redirect to `/execute` cannot carry custom headers. They continue to follow the historic behavior until Didomi activates the new behavior organization-wide.
  {% endhint %}

Consent link events follow the same matrix as **Create a new event** — when both UID and OUID are present, the OUID is authoritative. See the request examples in [Create a new event](#create-a-new-event) — they apply equally to the event a consent link generates.

<table data-header-hidden><thead><tr><th width="176.7332763671875"></th><th width="173.20001220703125"></th><th></th><th></th></tr></thead><tbody><tr><td></td><td><br><strong>OUID not provided (guest consent)</strong></td><td><strong>OUID provided &#x26; exists</strong></td><td><strong>OUID provided &#x26; does not exist</strong></td></tr><tr><td><strong>UID not provided</strong></td><td>Return 400</td><td>User updated via OUID</td><td>User created with provided OUID. <em>UID for the user duplicates the provided OUID.</em></td></tr><tr><td><strong>UID provided &#x26; exists</strong></td><td>User updated via UID</td><td><strong>New behavior</strong> — Ignore provided UID and update end-user consent record via provided OUID</td><td><strong>New behavior</strong> — Ignore provided UID (that end-user record is not updated). Create a new user with the provided OUID. <em>UID for the new user duplicates the provided OUID.</em></td></tr><tr><td><strong>UID provided &#x26; does not exist</strong></td><td>User created with provided UID</td><td><strong>New behavior</strong> — Ignore provided UID and update end-user consent record via provided OUID</td><td><strong>New behavior</strong> — Ignore provided UID and create a new user with the provided OUID. <em>UID for the new user duplicates the provided OUID.</em></td></tr></tbody></table>

### Consolidation of existing multi-user OUIDs

{% hint style="warning" %}
**If your organization uses Didomi's cross-device feature (web or mobile), consolidation will be enabled for you automatically.** You are not opting into this section. This section details what is happening with your organization's records.
{% endhint %}

Multi-user OUIDs are consolidated by a live migration that runs on a single endpoint:

```shell
GET https://api.didomi.io/v1/consents/users/{organization_user_id}?$by_organization_user_id=true&$merge_users=true&organization_id={organization_id}
```

This is the canonical read path for OUID-keyed user lookups, and is also the endpoint Didomi's cross-device sync uses. Historically, when this endpoint encountered an OUID associated with multiple user records, it computed a consolidated consent state across those records **in memory** and returned the merged view in the response — this operation is referred to as **user merging**. The underlying records remained unmerged in storage; the next call recomputed the merge from scratch.

Under the `2026-01-01` behavior, the first call for a multi-user OUID still triggers the same in-memory merge — but the result is now **persisted as a new canonical user record** under that OUID, with user `id` set to the OUID value. From that point onward, every request targeting that OUID — reads on this endpoint, events written against the OUID, anything else — resolves to the new canonical record instead of re-merging.

{% hint style="info" %}
**Consolidated canonical records are visible only under the new API behavior.** Once a canonical record exists, it lives permanently in storage, but it is resolved only on requests operating under the `2026-01-01` behavior (header sent, or organization-wide activation in effect). Requests still on the historic behavior do not see the canonical record and continue to resolve via the historic source records. See [Rollout and opt-in](#rollout-and-opt-in).
{% endhint %}

* [Consolidated record overview](#consolidated-record-overview)
* [Enable user merging and consolidation](#enable-user-merging-and-consolidation)
* [Retrieve historic source records](#retrieve-historic-source-records)

#### Consolidated record overview

The consolidated record adopts the OUID as its user `id`, matching the identity rule applied to any new user created with an OUID under the new behavior. See [Prevention (affects your request code)](#prevention-affects-your-request-code).

The persisted record holds the same merged state that the original `$merge_users=true` call returned in its response body. All consent statuses across all regulations covered by the historic users of this OUID are carried over.

Event histories are preserved but stay on their original user records. Events that the historic users accumulated are not deleted, and they are not moved either: they remain attached to their original `user.id` in the database and can be queried through the patterns described in [Retrieving historic source records](#retrieve-historic-source-records). The canonical record starts with its own event history, which begins accumulating from the first event submitted against the OUID after consolidation.

A consolidated record is tagged so downstream systems can recognize it as a consolidation product. A user record produced by the migration carries a non-empty `api_metadata` object:

```json
{
  "api_metadata": {
    "merge_fix_user": true,
    "merge_fix_version": 1
  }
}
```

User records that were not produced by consolidation carry an empty `api_metadata` object (`{}`), so a simple presence check on `api_metadata.merge_fix_user` is sufficient to distinguish the two.

#### Enable user merging and consolidation

Historically, clients managed user merging via the `$merge_users` query parameter. The parameter is still honored for clients who already rely on it. It is disabled by default for clients who have not used it and for new clients.

Consolidation of merged users can be activated either by sending the `2026-01-01` opt-in header on the request, or by an organization-level feature flag managed by Didomi. Either mechanism is sufficient; both can be used in combination during a staged rollout.

<table><thead><tr><th width="388">Didomi account settings</th><th>Effect on user merging</th></tr></thead><tbody><tr><td>Merging disabled</td><td>No merging takes place.</td></tr><tr><td><code>$merge_users=true</code> enabled and consolidation of merged users <strong>not</strong> activated (no <code>2026-01-01</code> header and no organization-level flag)</td><td>Merged state is computed in memory per request and not persisted.</td></tr><tr><td><code>$merge_users=true</code> enabled and consolidation of merged users activated (either the <code>2026-01-01</code> header or the organization-level flag)</td><td>Merged state is computed once, persisted as a canonical record, and returned on subsequent reads.</td></tr></tbody></table>

#### Retrieve historic source records

The historic user records involved in the merge are not deleted. They are hidden from default reads but remain fully reachable on demand. The Consents API offers three patterns for working with historic records:

<details>

<summary>List historic users under an OUID</summary>

Pass `$deduplicate_users=false` on a user listing request to return the complete set of users under an OUID, including the historic ones that the canonical record consolidates.

```shell
GET https://api.didomi.io/v1/consents/users?$deduplicate_users=false&organization_id={organization_id}&organization_user_id={organization_user_id}
```

The response includes one entry per historic record. Each entry is addressable by its original `user_id` (UID), which is the handle used by the two patterns below.

</details>

<details>

<summary>Interact with single historic user</summary>

Use the canonical user endpoint addressed by **UID** — not by OUID — to read or update a specific historic record returned by the listing call above.

```shell
GET https://api.didomi.io/v1/consents/users/{user_id}?organization_id={organization_id}
```

```shell
PATCH https://api.didomi.io/v1/consents/users/{user_id}?organization_id={organization_id}
```

The historic record continues to behave like any other user record at this address. The canonical consolidated record under the OUID is not affected by these calls.

</details>

<details>

<summary>List events of a historic user</summary>

Query events scoped to a specific historic user by passing the `user_id` query parameter — not `organization_user_id`. This is the only way to retrieve the event history that was originally written against that UID before consolidation.

```shell
GET https://api.didomi.io/v1/consents/events?user_id={user_id}&organization_id={organization_id}
```

</details>

### Downstream - webhooks and batch exports

There is no change to the shape of webhook events or batch exports. However, the data flowing through them reflects the new contract:

* **New canonical user records.** Consolidated user records produced by the migration are emitted as new user records with `id == organization_user_id` and the `api_metadata.merge_fix_user: true` flag set.
* **OUID-keyed new users emit matching user `id`.** For new users created with an OUID under the new contract, `id` is the OUID value. Downstream joins keyed on either field continue to work.
* **Fewer user-creation events.** Requests that previously spawned a new user record for an already-known OUID now reuse the existing OUID-keyed user. Webhooks and batch exports may see a meaningfully lower volume of user-creation events as a result, especially for clients that historically produced many user records per OUID.
* **Historic UIDs stop receiving new events.** Once a canonical record is in place under an OUID, all subsequent events route to it. The pre-consolidation UIDs are still queryable via `$deduplicate_users=false` but will not appear in new event streams.


---

# 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://developers.didomi.io/api-and-platform/reference/releases.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
