# Share consents across devices

## Introduction

The Didomi CMP supports syncing to allow consent sharing across devices and environments. Consent can be shared between all environments that have syncing enabled in the same organization: multiple websites, multiple apps (same or multiple devices), multiple websites and apps together, etc.

When syncing is enabled and an organization user ID is available, the Didomi CMP will load the previously stored user choices from the Consents API and will apply them locally.

{% hint style="info" %}
Syncing is currently a premium feature. You can schedule a meeting with our Support team [through the console](https://support.didomi.io/how-to-enable-cross-device-from-didomi-console) to enable the feature for your account.
{% endhint %}

## Configuration

### Setting organization user ID

For the sync process to work properly, an organization user ID property needs to be set before the Didomi initialization:

{% tabs %}
{% tab title="Java" %}

```swift
Didomi.getInstance().setUser("organization-user-id");
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
Didomi.getInstance().setUser("organization-user-id");
```

{% endtab %}

{% tab title="Swift" %}

```swift
Didomi.shared.setUser(id: "organization-user-id")
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
Didomi *didomi = [Didomi shared];
[didomi setUserWithId:@"organization-user-id"];
```

{% endtab %}

{% tab title="Flutter (Dart)" %}

```dart
await DidomiSdk.setUser("organization-user-id");
```

{% endtab %}

{% tab title="React-Native (Javascript)" %}

```typescript
await Didomi.setUser("organization-user-id");
```

{% endtab %}

{% tab title="Unity (C#)" %}

```csharp
Didomi.setUser("organization-user-id");
```

{% endtab %}
{% endtabs %}

This value represents the unique identifier with which the organization identifies the user for which it wants to sync the consents. This can be an email address, a user ID, etc. We recommend hashing the value to not communicate plain user IDs to Didomi.

Note: This value is not stored in user data and should be set again at each session.

### Which organization user ID to share with Didomi?

When the user is identified by the application through a login process or any other authentication method, a unique user ID should be shared with the Didomi SDK through the `Didomi.setUser()` method.\
\
The unique user ID can be any string but we recommend sending a hashed version of your internal user ID to not expose any sensitive data on the page or to Didomi. Avoid sharing plain email addresses, user names, etc. with Didomi.

The same unique user ID will need to be shared in other environments like mobile apps. If you are sending a hashed ID, make sure that you will be able to send exactly the same ID in other environments.

{% hint style="info" %}
The user ID shared on the website will be used as the organization user ID in our [Consents API](https://developers.didomi.io/api-and-platform/consents/users). If you are accessing the user status or event from the API, you will need to specify the same user ID as what is shared on the website, including any form of anonymization or hashing method in place.\
\
The organization user ID must be consistent across your organization and all the other Didomi products you are using (APIs, Privacy Center, etc.).
{% endhint %}

### Authentication with a hash digest

{% hint style="danger" %}
Using a hash digest does not guarantee the confidentiality of the user ID passed to Didomi as it is exposed in clear text on the page.
{% endhint %}

As the user ID is provided in a public environment (on a webpage), we need to authenticate it to guarantee that users cannot freely read and write consents for any user ID.

To authenticate the user ID and prove that it was authorized by your application, you will need to compute a hash digest of the user ID concatenated with a secret. This digest must be computed directly by your application in an authenticated context and not on the client-side. When it receives a request, Didomi will re-compute the digest with the secret and make sure that it matches the digest that you provided.

The hash digest should be provided in the `digest` parameter. You will also need to provide the ID of the secret and the hashing algorithm used for generating the hash digest.

Example:

{% tabs %}
{% tab title="Java" %}

```swift
Didomi.getInstance().setUser(new UserAuthWithHashParams(
            "organization-user-id",
            "algorithm",
            "secret_id",
            "digest"
));
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
Didomi.getInstance().setUser(UserAuthWithHashParams(
            id = "organization-user-id",
            algorithm = "algorithm",
            secretId = "secret_id",
            digest = "digest"
));
```

{% endtab %}

{% tab title="Swift" %}

```swift
Didomi.shared.setUser(userAuthParams: UserAuthWithHashParams(
            id: "organization-user-id",
            algorithm: "algorithm",
            secretID: "secret_id",
            digest: "digest"
))
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
[didomi setUserWithUserAuthParams: [[UserAuthWithHashParams alloc]
                                    initWithId:@"organization-user-id"
                                    algorithm: @"algorithm"
                                    secretID: @"secret-id"
                                    digest: @"digest"
                                    salt: NULL]];
```

{% endtab %}

{% tab title="Flutter (Dart)" %}

```dart
await setUserWithAuthParams(new UserAuthWithHashParams(
        "organization-user-id",
        "algorithm",
        "secret_id",
        "digest"
));
```

{% endtab %}

{% tab title="React-Native (Javascript)" %}

```typescript
await Didomi.setUserWithHashAuth(
    "organization-user-id",
    "algorithm",
    "secret_id",
    "digest"
);
```

{% endtab %}

{% tab title="Unity (C#)" %}

```csharp
Didomi.setUserWithHashParams(
    "organization-user-id",
    "algorithm",
    "secret_id",
    "digest"
);
```

{% endtab %}
{% endtabs %}

#### Secrets

When computing a hash digest, a secret needs to be used. Secrets can be managed through the Didomi API to obtain an actual secret and its associated ID. [Read our documentation](https://developers.didomi.io/api/consents/secrets) to manage secrets for your organization.

#### Hashing methods

Didomi supports the following methods for computing a digest:

| Algorithm   | ID            | Description                                                             |
| ----------- | ------------- | ----------------------------------------------------------------------- |
| Hash MD5    | `hash-md5`    | Hexadecimal digest computed with the MD5 algorithm                      |
| Hash SHA1   | `hash-sha1`   | Hexadecimal digest computed with the SHA1 algorithm                     |
| Hash SHA256 | `hash-sha256` | Hexadecimal digest computed with the SHA256 algorithm                   |
| HMAC SHA1   | `hmac-sha1`   | Hexadecimal representation of a HMAC computed with the SHA1 algorithm   |
| HMAC SHA256 | `hmac-sha256` | Hexadecimal representation of a HMAC computed with the SHA256 algorithm |

For all methods, the content to compute the digest on and the secret are the same. The method ID must be provided in the `algorithm` property.

#### Salting

For increased security, you can use a salt when computing the hash digest. You will need to provide the `salt` parameter in the configuration so that Didomi can use it when verifying the user ID.

Example:

{% tabs %}
{% tab title="Java" %}

```swift
Didomi.getInstance().setUser(new UserAuthWithHashParams(
            "organization-user-id",
            "algorithm",
            "secret_id",
            "digest",
            "salt"
));
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
Didomi.getInstance().setUser(UserAuthWithHashParams(
            id = "organization-user-id",
            algorithm = "algorithm",
            secretId = "secret_id",
            digest = "digest",
            salt = "salt"
));
```

{% endtab %}

{% tab title="Swift" %}

```swift
Didomi.shared.setUser(userAuthParams: UserAuthWithHashParams(
            id: "organization-user-id",
            algorithm: "algorithm",
            secretID: "secret_id",
            digest: "digest",
            salt: "salt"
))
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
[didomi setUserWithUserAuthParams: [[UserAuthWithHashParams alloc]
                                    initWithId:@"organization-user-id"
                                    algorithm: @"algorithm"
                                    secretID: @"secret-id"
                                    digest: @"digest"
                                    salt: @"salt"]];
```

{% endtab %}

{% tab title="Flutter (Dart)" %}

```dart
await setUserWithAuthParams(new UserAuthWithHashParams(
        "organization-user-id",
        "algorithm",
        "secret_id",
        "digest",
        "salt"
));
```

{% endtab %}

{% tab title="React-Native (Javascript)" %}

```typescript
await Didomi.setUserWithHashAuth(
    "organization-user-id",
    "algorithm",
    "secret_id",
    "digest",
    "salt"
);
```

{% endtab %}

{% tab title="Unity (C#)" %}

```csharp
Didomi.setUserWithHashParams("organization-user-id",
            "algorithm",
            "secret_id",
            "digest",
            "salt");
```

{% endtab %}
{% endtabs %}

#### Information expiration

The authentication methods guarantee the integrity of the information (i.e., the information originates from your application and cannot be modified by a third-party) but do not guarantee that information cannot be reused.

To prevent the reuse of an encrypted user identifier, we support the addition of expiration information. You can mark the information as having an expiration date so that it cannot be used after a certain date using the `expiration` parameter. This should be a valid unix timestamp value.

Example:

{% tabs %}
{% tab title="Java" %}

```swift
Didomi.getInstance().setUser(new UserAuthWithHashParams(
            "organization-user-id",
            "algorithm",
            "secret_id",
            "digest",
            "salt", // or null
            10000L
));
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
Didomi.getInstance().setUser(UserAuthWithHashParams(
            id = "organization-user-id",
            algorithm = "algorithm",
            secretId = "secret_id",
            digest = "digest",
            salt = "salt", // or null
            expiration = 10000L
))
```

{% endtab %}

{% tab title="Swift" %}

```swift
Didomi.shared.setUser(userAuthParams: UserAuthWithHashParams(
            id: "organization-user-id",
            algorithm: "algorithm",
            secretID: "secret_id",
            digest: "digest",
            salt: "salt", // or nil
            expiration: 10000
))
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
[didomi setUserWithUserAuthParams: [[UserAuthWithHashParams alloc]
                                    initWithId:@"organization-user-id"
                                    algorithm: @"algorithm"
                                    secretID: @"secret-id"
                                    digest: @"digest"
                                    salt: @"salt" // or null
                                    legacyExpiration: 10000.0]];
```

{% endtab %}

{% tab title="Flutter (Dart)" %}

```dart
await setUserWithAuthParams(new UserAuthWithHashParams(
        "organization-user-id",
        "algorithm",
        "secret_id",
        "digest",
        "salt", // or null
        10000
));
```

{% endtab %}

{% tab title="React-Native (Javascript)" %}

```typescript
Didomi.setUserWithHashAuth(
    "organization-user-id",
    "algorithm",
    "secret_id",
    "digest",
    "salt", // or null
    10000
);
```

{% endtab %}

{% tab title="Unity (C#)" %}

```csharp
Didomi.setUserWithHashParamsWithExpiration("organization-user-id",
            "algorithm",
            "secret_id",
            "digest",
            "salt", // or null
            10000);
```

{% endtab %}
{% endtabs %}

The sync process can be enabled with the `sync.enabled` configuration option in the Didomi configuration:

```javascript
{
    "app": {
        ...
    },
    "sync": {
        "enabled": true
    }
}
```

## Behavior

### Frequency

By default, the sync process will run once every day. The value in seconds for the sync process can be specified via the `frequency` configuration option.

```javascript
{
    "app": {
        ...
    },
    "sync": {
        "enabled": true,
        "frequency": 86400
    }
}
```

{% hint style="warning" %}
The `frequency` interval is 24 hours by default (86,400 seconds). It should not be necessary to set this to a lower threshold. Lower settings can create a reduced user experience as page load times would be impacted by syncing intervals being too low and sync occurring when unnecessary.
{% endhint %}

{% hint style="danger" %}
The minimum frequency interval is 6 hours (21,600 seconds). If you set the frequency interval to a lower value, it will be overridden to 6 hours.
{% endhint %}

### Sync timeout

The maximum time allowed for the syncing process to be completed can be specified with a `sync.timeout` configuration option in the Didomi configuration:

```javascript
{
    "app": {
        ...
    },
    "sync": {
        "enabled": true,
        "timeout": 3000
    }
}
```

{% hint style="warning" %}
The `timeout` duration is set to 3 seconds (3,000 milliseconds) by default. It should not be necessary to increase this threshold.
{% endhint %}

The `sync.timeout` configuration option accepts an integer which represents the maximum time allowed (in milliseconds) for the syncing process to be completed.

If the syncing process takes longer than the time specified in the `sync.timeout` configuration option, the initialization continues as if there was no data to sync.

### Delaying the display of the notice

On Android and iOS, the [delayNotice](https://developers.didomi.io/web-sdk/share-consents-across-devices#delaying-notice-showing) parameter is ignored. To avoid showing the notice before user is synchronized, the `setUser` method can be called before the SDK is initialized. In this case, consent synchronization will be performed as part of the initialization process.

It is also possible to call `setupUI()` only after user was synchronized. Alternatively, current `Activity` (Android) or `ViewController` (iOS) can be passed to the setUser method, so if the SDK is already initialized, consent will be asked again if user consent is partial after synchronization.

Example:

{% tabs %}
{% tab title="Java" %}

```swift
Didomi.getInstance().setUser(userParams, activity);
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
Didomi.getInstance().setUser(userParams = userAuthParams, activity = activity);
```

{% endtab %}

{% tab title="Swift" %}

```swift
Didomi.shared.setUser(userAuthParams: userParams, containerController: viewController)
```

{% endtab %}
{% endtabs %}

### Multi-account

After an organization user ID has been set, it is possible to change or remove it (forget this user ID). This will be interpreted as changing the currently active organization user, and will trigger a new synchronization.

* If there was an already synchronized user, and the user changed, the existing consents are `reset`. If this user already gave consent using another device, these consents will be loaded. Otherwise, the user needs to give consent again.
* When the user should not be synchronized anymore, [`clearUser()`](https://developers.didomi.io/cmp/ios/reference/api#clearuser) should be called. When doing this, the Didomi user ID ([UserStatus.userId](https://developers.didomi.io/cmp/ios/reference/api#getuserstatus)) assigned to this user will be reset, but current consents will be kept. The call will also trigger a [consentChanged](https://developers.didomi.io/cmp/android/reference/events#consentchanged) event. If you need to reset the consents as well, you can call the [reset()](https://developers.didomi.io/cmp/android/reference/api#reset) method at the same time.
* By providing the optional parameter `activity` (Android) / `containerController` (iOS) to the [`setUser`](https://developers.didomi.io/cmp/android/reference/api#setuser) method, if the sdk is already initialized, [`setupUI`](https://developers.didomi.io/cmp/android/reference/api#setupui) will be automatically called after synchronization is done. So if synchronized user changes and consent is partial for the new user, consent notice will be displayed automatically.

## Confirmation notice

If desired, your organization can surface a confirmation notice in order to communicate to end-users that their local consent has been synced from the Didomi backend when using cross device. The confirmation notice is an optional configuration.

{% hint style="info" %}
The design and interaction of the confirmation notice is developed and implemented by your organization.
{% endhint %}

Refer to the tabs below for more information:

{% tabs %}
{% tab title="Java" %}
Listen to [syncReady](https://developers.didomi.io/cmp/mobile-sdk/android/reference/events#syncready) event to trigger confirmation notice:

```java
Didomi.getInstance().addEventListener(new EventListener() {
    @Override
    public void syncReady(SyncReadyEvent event) {
        if (event.getStatusApplied) {
            // Surface a message to the user / logs
            System.out.println("User status synchronization was successfully applied.");

            // Acknowledge that the sync has been communicated to the user
            boolean acknowledged = event.syncAcknowledged.invoke();
            System.out.println("Sync acknowledged: " + acknowledged);
        }
    }
});
```

{% endtab %}

{% tab title="Kotlin" %}
Listen to [syncReady](https://developers.didomi.io/cmp/mobile-sdk/android/reference/events#syncready) event to trigger confirmation notice:

```kotlin
Didomi.getInstance().addEventListener(object : EventListener() {
    override fun syncReady(event: SyncReadyEvent) {
        if (event.statusApplied) {
            // Surface a message to the user / logs
            println("User status synchronization was successfully applied.")

            // Acknowledge that the sync has been communicated to the user
            val acknowledged = event.syncAcknowledged.invoke()
            println("Sync acknowledged: $acknowledged")
        }
    }
})
```

{% endtab %}

{% tab title="Swift" %}
Listen to [onSyncReady](https://developers.didomi.io/cmp/mobile-sdk/ios/reference/events#event-types) event to trigger confirmation notice:

```swift
let didomiEventListener = EventListener()

didomiEventListener.onSyncReady = { event in
    if event.statusApplied {
        // Surface a message to the user / logs
        print("User status synchronization was successfully applied.")

        // Acknowledge that the sync has been communicated to the user
        let acknowledged = event.syncAcknowledged()
        print("Sync acknowledged: \(acknowledged)")
    }
}

Didomi.shared.addEventListener(listener: didomiEventListener)
```

{% endtab %}

{% tab title="Objective-C" %}
Listen to [onSyncReady](https://developers.didomi.io/cmp/mobile-sdk/ios/reference/events#event-types) event to trigger confirmation notice:

```objective-c
DDMEventListener *didomiEventListener = [[DDMEventListener alloc] init];

[didomiEventListener setOnSyncReady:^(DDMSyncReadyEvent *event) {
    if (event.statusApplied) {
        // Surface a message to the user / logs
        NSLog(@"User status synchronization was successfully applied.");

        // Acknowledge that the sync has been communicated to the user
        BOOL acknowledged = event.syncAcknowledged();
        NSLog(@"Sync acknowledged: %d", acknowledged);
    }
}];

[didomi addEventListenerWithListener:didomiEventListener];
```

{% endtab %}

{% tab title="Flutter (Dart)" %}
Listen to [onSyncReady](https://developers.didomi.io/cmp/mobile-sdk/flutter/reference#addeventlistener) event to trigger confirmation notice:

```dart
void setupDidomiListener() {
  EventListener didomiListener = EventListener();

  didomiListener.onSyncReady = (event) {
    if (event.statusApplied) {
      // Surface a message to the user / logs
      print("User status synchronization was successfully applied.");

      // Acknowledge that the sync has been communicated to the user
      final acknowledged = event.syncAcknowledged();
      print("Sync acknowledged: $acknowledged");
    }
  };

  DidomiSdk.addEventListener(didomiListener);
}
```

{% endtab %}

{% tab title="React-Native (Javascript)" %}
Listen to [SYNC\_READY](https://developers.didomi.io/cmp/mobile-sdk/react-native/reference#addeventlistener) event to trigger confirmation notice:

```javascript
const registerSyncReadyListener = () => {
  Didomi.addEventListener(DidomiEventType.SYNC_READY, (event: any) => {
    // event is a SyncReadyEvent with statusApplied and syncAcknowledged
    if (event.statusApplied) {
      console.log('User status synchronization was successfully applied.');

      const acknowledged = event.syncAcknowledged();
      console.log('sync.acknowledged sent:', acknowledged);
    }
  });
};

// Register like the other listeners
registerSyncReadyListener();
```

{% endtab %}

{% tab title="Unity (C#)" %}
Listen to [SyncReady](https://developers.didomi.io/cmp/mobile-sdk/unity-sdk/reference#addeventlistener) event to trigger confirmation notice:

```
using Didomi.SDK; // Adjust namespace if needed

private void RegisterEventHandlers()
{
    DidomiEventListener eventListener = new DidomiEventListener();
    eventListener.SyncReady += EventListener_SyncReady;
    Didomi.GetInstance().AddEventListener(eventListener);
}

private void EventListener_SyncReady(object sender, SyncReadyEvent e)
{
    if (e.IsStatusApplied())
    {
        // Surface a message to the user / logs
        UnityEngine.Debug.Log("User status synchronization was successfully applied.");

        // Acknowledge that the sync has been communicated to the user
        bool acknowledged = e.SyncAcknowledged();
        UnityEngine.Debug.Log("Sync acknowledged: " + acknowledged);
    }
}
```

{% endtab %}
{% endtabs %}

## Troubleshooting

* Android Developers, make sure the device is supporting the selected encryption method.\
  For more information, see the [Android documentation](https://developer.android.com/reference/javax/crypto/Cipher).
