For the complete documentation index, see llms.txt. This page is also available as Markdown.

Releases


Consents API update

Updated: 30 September 2026

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.

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)

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.

  • 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.

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.

Additional details available in 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:

Benefit
Description

A single end-user record per OUID

Previously, an OUID could be associated with multiple user_id 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.

  • Reads return that consolidated state

  • Writes (including event creation) land on the same record instead of spawning siblings.

A single canonical record per OUID makes a broad range of API interactions noticeably more straightforward and convenient.

Lower read latency

Consolidated state is now precomputed and persisted which significantly improves latency of user read requests.

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.

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

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.

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:

Requests without this header continue to behave exactly as they did before the update.

Use the provided grace period to audit your integration against the scenarios posed in the 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.

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.

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.

Impact Assessment

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

  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 user - 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

  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

  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 - 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.

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.

Create a new event

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).

Example — UID and OUID both provided, both exist

Historic

The event updates the user identified by uid-existing. That user's OUID is patched to ouid-existing, even if another user already owned that OUID.

New (2026-01-01):

uid-existing is ignored. The event updates the user identified by ouid-existing instead.

Example — UID provided, OUID provided and does not exist

Historic

uid-existing-or-not is created or updated; its OUID is set to ouid-new.

New (2026-01-01):

uid-existing-or-not is ignored. A new user is created with id = organization_user_id = "ouid-new".

OUID not provided (guest consent)

OUID provided & exists

OUID provided & does not exist

UID not provided

Return 400

User updated via OUID

User created with provided OUID. UID for the user duplicates the provided OUID.

UID provided & exists

User updated via UID

New behavior — Ignore provided UID and update end-user consent record via provided OUID

New behavior — Ignore provided UID (that end-user record is not updated). Create a new user with the provided OUID. UID for the new user duplicates the provided OUID.

UID provided & does not exist

User created with provided UID

New behavior — Ignore provided UID and update end-user consent record via provided OUID

New behavior — Ignore provided UID and create a new user with the provided OUID. UID for the new user duplicates the provided OUID.

Create a new user

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).

Example — OUID exists, UID is anything

Historic

Behavior depended on the UID — could create a user, could no-op, could create a parallel record sharing the OUID.

New (2026-01-01):

204 No Content. No user is created. The existing record under ouid-existing is left untouched.

Example — OUID does not exist, UID exists

Historic

204 No Content. No user created (because the UID already exists).

New (2026-01-01):

the UID is ignored. A new user is created with id = organization_user_id = "ouid-new".

OUID not provided (guest consent)

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

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).

Example — attempt to reassign the OUID

Historic

The user is patched, and the OUID is changed to ouid-new.

New (2026-01-01):

The user is patched (consents are updated). The organization_user_id field is silently ignored — the OUID retains its previous value. The response is 200; no error is raised.

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

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.

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 — they apply equally to the event a consent link generates.

OUID not provided (guest consent)

OUID provided & exists

OUID provided & does not exist

UID not provided

Return 400

User updated via OUID

User created with provided OUID. UID for the user duplicates the provided OUID.

UID provided & exists

User updated via UID

New behavior — Ignore provided UID and update end-user consent record via provided OUID

New behavior — Ignore provided UID (that end-user record is not updated). Create a new user with the provided OUID. UID for the new user duplicates the provided OUID.

UID provided & does not exist

User created with provided UID

New behavior — Ignore provided UID and update end-user consent record via provided OUID

New behavior — Ignore provided UID and create a new user with the provided OUID. UID for the new user duplicates the provided OUID.

Consolidation of existing multi-user OUIDs

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

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.

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.

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).

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. 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:

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.

Didomi account settings
Effect on user merging

Merging disabled

No merging takes place.

$merge_users=true enabled and consolidation of merged users not activated (no 2026-01-01 header and no organization-level flag)

Merged state is computed in memory per request and not persisted.

$merge_users=true enabled and consolidation of merged users activated (either the 2026-01-01 header or the organization-level flag)

Merged state is computed once, persisted as a canonical record, and returned on subsequent reads.

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:

List historic users under an OUID

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.

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.

Interact with single historic user

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.

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.

List events of a historic user

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.

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.

Last updated