# Singular Cloud Mode Integration


RudderStack supports server-to-server (S2S) API integration with Singular. See the [Singular API endpoint reference](https://support.singular.net/hc/en-us/articles/360048588672-Server-to-Server-S2S-API-Endpoint-Reference) for more information.

{{< info >}}
RudderStack uses [Singular's API v2](https://support.singular.net/hc/en-us/articles/31496864868635-Server-to-Server-EVENT-Endpoint-API-Reference#API_Endpoint_Selection) when the event contains the Singular Device ID (SDID). Otherwise, it uses [API v1](https://support.singular.net/hc/en-us/articles/31496864868635-Server-to-Server-EVENT-Endpoint-API-Reference#API_Endpoint_Selection) with platform-specific identifiers.

See [Integration Setup Approaches]({{< ref "destinations/streaming-destinations/singular/setup-options.md" >}}) to choose the right setup for your use case.
{{< /info >}}

RudderStack supports two types of [`track`]({{< ref "event-spec/standard-events/track.md" >}}) events that you can send to Singular via [cloud mode]({{< ref "destinations/rudderstack-connection-modes.md#cloud-mode" >}}):

- Session events
- Custom events

## Session events

RudderStack lets you specify the event names to be used as session events in the [Session Event Name]({{< ref "destinations/streaming-destinations/singular/setup-guide.md#connection-settings" >}}) dashboard setting.

RudderStack sends the session events to Singular via its [`launch`](https://support.singular.net/hc/en-us/articles/31394799175963-Server-to-Server-SESSION-Endpoint-API-Reference?navigation_side_bar=true) API.

{{< info >}}
RudderStack considers an event as a session event only if it is specified in the dashboard settings or if any of the following three [lifecycle events]({{< ref "event-spec/standard-events/application-lifecycle-events-spec.md" >}}) occur:

- Application Installed
- Application Opened
- Application Updated

RudderStack automatically tracks the above three lifecycle events if lifecycle event tracking is enabled.
{{< /info >}}

### Common mappings {#common-mappings-session}

{{< info >}}
These session event mappings apply regardless of which Singular API version (v1 or v2) RudderStack uses to send events.
{{< /info >}}

#### Attributes automatically captured by RudderStack {#attributes-automatically-captured-session}

The following table lists the mapping of the attributes **automatically captured** by RudderStack for the **mobile platforms** — [Android (Java)]({{< ref "sources/event-streams/sdks/rudderstack-android-sdk/_index.md" >}}) and [iOS (Obj-C)]({{< ref "sources/event-streams/sdks/rudderstack-ios-sdk/_index.md" >}}):

| RudderStack property | Singular attribute | <div style="width: 350px;">Description</div> |
| :----| :----| :----|  
| `context.os.name` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `p` |  The source platform (Android or iOS). |
| `context.app.namespace` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `i` | The package name (Android) or bundle ID (iOS) of your app. |
| `context.app.version` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span>| `app_v` |  The app version. |
| `context.ip` <br/> `request_ip` (in that order) <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `ip` |  The user's IP address. Refer to the note below for information on anonymizing your IP. |
| `context.os.version` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `ve` | The device OS version at session time. |
| `context.device.model` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span>| `mo` | The device model. This parameter must be used with the `ma` parameter. |
| `context.device.manufacturer` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `ma` | The make of the device hardware. This parameter must be used with the `mo` parameter. |
| `context.locale` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `lc` | The device's IETF local tag using two-lettered language and country code, separated by an underscore. |
| `context.app.build` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `bd` |  The device build (URL encoded). |
| `context.device.adTrackingEnabled` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `dnt` | Pass `true` if do not track(`dnt`) is disabled (`dnt=0`), else pass `false`(`dnt=1`). This is automatically captured if you pass the advertising ID to the SDK. |
| `context.app.name` | `n` | The human-readable app name as displayed in the UI. |
| [`timestamp`]({{< ref "event-spec/standard-events/common-fields.md#clock-skew-considerations" >}}) <br /> `originalTimestamp` | `utime` | Optional | The session time (in UNIX time). |
| `context.network.wifi` | `c` | The connection type (WiFi or carrier). |
| `context.network.carrier` | `cn` | The carrier name of the internet provider. |
| `integrations.Singular.limitDataSharing` | `data_sharing_options.limit_data_sharing` | JSON URL-encoded user consent for data sharing. It must persist and pass on all the subsequent event requests. |

{{< success >}}
To anonymize your IP, you can send a placeholder IP in the `context.ip` field. RudderStack uses it as the IP address instead of capturing it automatically from the backend. 

For mobile SDKs, you can leverage [Transformations]({{< ref "transformations/overview.md" >}}) to anonymize IPs when sending events in [cloud mode]({{< ref "destinations/rudderstack-connection-modes.md#cloud-mode" >}}).
{{< /success >}}

#### Attributes to pass via event properties {#attributes-passed-via-properties-session}

The following table lists the mapping of the attributes that **must be passed via the event properties**:

{{< warning >}}
These properties are not persisted in the SDK and must be passed with every event.
{{< /warning >}}

| RudderStack property | Singular attribute | Description |
| :----| :----| :----|
| `properties.install_ref` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `install_ref` | The Google Install Referrer Information. |
| `properties.referring_application` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `install_source` | The install source package name in Android. Use [`getInitiatingPackageName()`](https://developer.android.com/reference/android/content/pm/InstallSourceInfo#getInitiatingPackageName()) to retrieve this. |
| `properties.install_receipt` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `install_receipt` | The receipt received from the install. To retrieve this, follow the [iOS Install Receipt](https://support.singular.net/hc/en-us/articles/360037640812#Reference_Retrieving_the_iOS_Install_Receipt) guide. |
| `properties.url` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `openui` | If the app is opened via a deep link/universal link, the value of the encoded deep link URL. |
| `context.device.attTrackingStatus` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `att_authorization_status` | The [App Tracking Transparency authorization status](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus). |
| `userId` | `custom_user_id` | The user ID passed through the `identify` call. |
| `properties.attribution_token` | `attribution_token` |  Used to attribute Apple Search Ads for iOS 14.3 and above. More information [here](https://support.singular.net/hc/en-us/articles/360037640812#asa_adservices). |
| `properties.skan_conversion_value` | `skan_conversion_value` |  The latest `SkAdNetwork` value at the time of the session notification. |
| `properties.skan_first_call_timestamp` | `skan_first_call_timestamp` |  UNIX timestamp of the first call made to the `SkAdNetwork` API. |
| `properties.skan_last_call_timestamp` | `skan_last_call_timestamp` | UNIX timestamp of the last call made to the `SkAdNetwork` API at the time of the session notification. |
| `properties.install` | `install` | The install flag. Set to `true` on the first session after app install, or `false` otherwise. Required for reinstall tracking capability. |
| `properties.install_time` <br />`timestamp` <br /> `originalTimestamp` | `install_time` | The install time (in UNIX). |
| `properties.update_time` <br />`timestamp` <br /> `originalTimestamp` | `update_time` | The update time (in UNIX). |

#### Attributes to pass via event properties just once {#attributes-passed-just-once-session}

The following table lists the mapping of the attributes that **must be passed via the event properties just once** — the RudderStack SDK persists these properties.

| RudderStack property | Singular attribute | <div style="width: 350px;">Description</div> |
| :----| :----| :----|
| `context.device.token` | `fcm` | The Firebase Cloud Messaging Device Token. It is required for uninstall tracking in Android. <br /><br />{{< info >}}RudderStack supports only `fcm` for mapping the device token.{{< /info >}} |
| `context.device.token` | `apns_token` | The Apple Push Notification Service Device Token. It is required for uninstall tracking in iOS. |

For more information on setting the device token, see the [Android (Java)]({{< ref "sources/event-streams/sdks/rudderstack-android-sdk/_index.md#setting-the-android-device-token" >}}) or [iOS (Obj-C)]({{< ref "sources/event-streams/sdks/rudderstack-ios-sdk/_index.md#setting-the-device-token" >}}) SDK documentation.

### Identifier mappings {#identifier-mappings-session}

The following identifiers are **automatically captured** by RudderStack:

| RudderStack property | Singular attribute | <div style="width: 350px;">Description</div> |
| :----| :----| :----|
| `context.device.id` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `idfv` | The raw [`IdentifierForVendor`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIDevice_Class/Reference/UIDevice.html#//apple_ref/occ/instp/UIDevice/identifierForVendor) in upper case with dashes. **This is applicable for iOS apps only**. |
| `context.device.id` <br/> <span style="color: #4D4DFF;font-size:12px;">Required, if `context.device.advertisingId` and `properties.asid` is absent.</span> | `andi` |  The raw [`Android ID`](http://developer.android.com/reference/android/provider/Settings.Secure.html#ANDROID_ID) in lower case. **This is applicable for Android apps only** and is required only when the Android advertising ID is unavailable on the device. See [FAQ]({{< ref "destinations/streaming-destinations/singular/cloud-mode.md#which-device-id-attributes-are-required-for-android" >}}) for more information. |

The following identifier **must be passed via the event properties** with every event (not persisted in the SDK):

| RudderStack property | Singular attribute | <div style="width: 350px;">Description</div> |
| :----| :----| :----|
| `properties.asid` <br/> <span style="color: #4D4DFF;font-size:12px;">Required, if `context.device.advertisingId` and `context.device.id` is absent.</span> | `asid` | The App Set ID for Android v12+ devices. See [FAQ]({{< ref "destinations/streaming-destinations/singular/cloud-mode.md#which-device-id-attributes-are-required-for-android" >}}) for more information. |

The following identifiers **must be passed via the event properties just once** (persisted in the SDK):

| RudderStack property | Singular attribute | <div style="width: 350px;">Description</div> |
| :----| :----| :----|
| `context.device.advertisingId`  <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `idfa` | The raw [advertising ID](https://developer.apple.com/library/ios/documentation/AdSupport/Reference/ASIdentifierManager_Ref/index.html) in upper case with dashes. **This is applicable for iOS apps only**. |
| `context.device.advertisingId` <br/> <span style="color: #4D4DFF;font-size:12px;">Required, if `properties.asid` and `context.device.id` is absent.</span> | `aifa` | This is the lower case raw [advertising ID](http://support.google.com/googleplay/android-developer/answer/6048248?hl=en) with dashes. **This is applicable for Android apps only**. See [FAQ]({{< ref "destinations/streaming-destinations/singular/cloud-mode.md#which-device-id-attributes-are-required-for-android" >}}) for more information. |

{{< info >}}
Singular prefers `aifa` over `andi` (in Android) and `idfa` over `idfv` (in iOS).
{{< /info >}}

## Custom events

RudderStack sends all events other than the session events as custom events via Singular's [`evt` API endpoint](https://support.singular.net/hc/en-us/articles/31496864868635-Server-to-Server-EVENT-Endpoint-API-Reference?navigation_side_bar=true).

### Common mappings {#common-mappings-custom}

{{< info >}}
These custom event mappings apply regardless of which Singular API version (v1 or v2) RudderStack uses to send events.
{{< /info >}}

#### Attributes automatically captured by RudderStack {#attributes-automatically-captured-custom}

The following table lists the mapping of the attributes **automatically captured** by RudderStack for the **mobile platforms** ([Android (Java)]({{< ref "sources/event-streams/sdks/rudderstack-android-sdk/_index.md" >}}) and [iOS (Obj-C)]({{< ref "sources/event-streams/sdks/rudderstack-ios-sdk/_index.md" >}})):

| RudderStack property | Singular attribute | <div style="width: 350px;">Description</div> |
| :----| :----| :----| 
| `context.os.name` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `p` | The source platform (Android or iOS). |
| `context.app.namespace` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `i` | The package name (Android) or bundle ID (iOS) of your app. |
| `context.ip` <br/> `request_ip` (in same order) <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `ip` | The user's IP address. |
| `context.os.version` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `ve` |  The device OS version at session time. |
| [`timestamp`]({{< ref "event-spec/standard-events/common-fields.md#clock-skew-considerations" >}}) <br /> `originalTimestamp` | `utime` | Optional | The session time (in UNIX time). |
| `integrations.Singular.limitDataSharing` | `data_sharing_options.limit_data_sharing` | JSON URL-encoded user consent for data sharing. It must persist and pass on all the subsequent event requests. |

#### Attributes to pass via event properties {#attributes-passed-via-properties-custom}

The following table lists the mapping of the attributes that **must be passed via the event properties**:

| RudderStack property | Singular attribute | Description |
| :----| :----| :----|
| `event` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span>| `n` | The name of the event. **This is user-defined**. |
| `context.device.attTrackingStatus` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `att_authorization_status` | The [App Tracking Transparency authorization status](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus). |
| `userId` | `custom_user_id` | The user ID passed through the `identify` call. |
| `properties.skan_conversion_value` | `skan_conversion_value` | The latest `SkAdNetwork` value at the time of the session notification. |
| `properties.skan_first_call_timestamp` | `skan_first_call_timestamp` | UNIX timestamp of the first call made to the `SkAdNetwork` API. |
| `properties.skan_last_call_timestamp` | `skan_last_call_timestamp` | UNIX timestamp of the last call made to the `SkAdNetwork` API at the time of the session notification. |
| `properties.eventAttributes` | `e` | The custom event attributes in JSON format. You need to pass these with every event as they are not persisted in the SDK. |
| `properties.is_revenue_event` | `is_revenue_event` | Determines if an event is a revenue event. You need to pass this through the properties with every event as it is not persisted in the SDK. |
| `properties.receipt_signature` | `receipt_signature` | The receipt signature. |

#### Attributes to pass via event properties just once {#attributes-passed-just-once-custom}

The following table lists the mapping of the **user-defined attributes specific to revenue events**:

| RudderStack property | Singular attribute | Description |
| :----| :----| :----|
| `properties.total` <br/> `properties.value` <br/> `properties.revenue` | `amt` | The currency amount. |
| `properties.currency` | `cur` | The ISO 4217 three-lettered currency code. This should be in conjunction with the `amt` parameter. |
| `properties.purchase_receipt` | `purchase_receipt` | The receipt received from a purchase. |
| `properties.product_id` <br/> `properties.sku` | `purchase_product_id` | The product SKU identifier. |
| `properties.orderId` <br/> `properties.purchase_transaction_id` (in that order) | `purchase_transaction_id` | The transaction identifier. |

{{< info >}}
If you set any one out of the `value`, `revenue`, or `total` properties, RudderStack automatically considers the event as a revenue event, unless it is explicitly mentioned by the `is_revenue_event` property.
{{< /info >}}

A few important considerations in case of custom events are listed below:

- RudderStack takes the user agent from `context.userAgent` for Android and from the event properties in case of iOS.
- RudderStack stores the extra attributes passed in the custom event in Singular's `e` field.

### Identifier mappings {#identifier-mappings-custom}

The Singular API version RudderStack uses depends on the identifier fields in the events.

{{< info >}}
If the Singular device ID (SDID) is present in the event, RudderStack automatically uses the Singular API v2 and ignores the traditional platform-specific identifiers like `idfa`, `andi`, `idfv`, and `aifa`.

Otherwise, it falls back to the platform-specific identifiers and sends the events using the Singular API v1.
{{< /info >}}

{{< details "**API v1: Platform-specific identifiers**" >}}
<br />

| RudderStack property | Singular attribute | <div style="width: 350px;">Description</div> |
| :----| :----| :----| 
| `context.device.advertisingId` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `idfa` | The raw [`IdentifierForVendor`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIDevice_Class/Reference/UIDevice.html#//apple_ref/occ/instp/UIDevice/identifierForVendor) in upper case with dashes. **This is applicable for iOS apps only**. |
| `context.device.advertisingId` <br/> <span style="color: #4D4DFF;font-size:12px;">Required, if `properties.asid` and `context.device.id` are absent.</span> | `aifa` | This is the lower case raw [advertising ID](http://support.google.com/googleplay/android-developer/answer/6048248?hl=en) with dashes. **This is applicable for Android apps only**. See [FAQ]({{< ref "destinations/streaming-destinations/singular/cloud-mode.md#which-device-id-attributes-are-required-for-android" >}}) for more information. |
| `context.device.id` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `idfv` |  The raw [`IdentifierForVendor`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIDevice_Class/Reference/UIDevice.html#//apple_ref/occ/instp/UIDevice/identifierForVendor) in upper case with dashes. **This is applicable for iOS apps only**. |
| `context.device.id` <br/> <span style="color: #4D4DFF;font-size:12px;">Required, if `context.device.advertisingId` and `properties.asid` are absent.</span> | `andi` |  The raw [`Android ID`](http://developer.android.com/reference/android/provider/Settings.Secure.html#ANDROID_ID) in lower case. **This is applicable for Android apps only** and is required only when the Android Advertising ID is unavailable on the device. See [FAQ]({{< ref "destinations/streaming-destinations/singular/cloud-mode.md#which-device-id-attributes-are-required-for-android" >}}) for more information. |

{{< info >}}
Singular prefers `aifa` over `andi` (in Android) and `idfa` over `idfv` (in iOS).
{{< /info >}}

{{< /details >}}

{{< details "**API v2: Singular Device ID (SDID)**" >}}
<br />

| RudderStack property | Singular attribute | <div style="width: 350px;">Description</div> |
| :----| :----| :----| 
| `integrations.Singular.singularDeviceId` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `sdid` | Singular device ID obtained from the Singular SDK or generated client-side for the device. |

See [Pass the Singular device ID and data sharing options](#pass-singular-device-id) for a code example.

{{< /details >}}

## Pass the Singular device ID and data sharing options {#pass-singular-device-id}

To send the Singular Device ID and data sharing options, include `singularDeviceId` and `limitDataSharing` within your event's integrations object, as shown:

{{< tabs tabTotal="4" >}}
{{% tab tabName="JavaScript" %}}

```javascript
rudderanalytics.track(
  "Order Completed", {
    revenue: 30,
    currency: "USD",
    user_actual_id: 12345
  }, {
    integrations: {
      Singular: {
        singularDeviceId: "<SINGULAR_DEVICE_ID>",
        limitDataSharing: true  // optional
      }
    },
  }
);
```
{{% /tab %}}
{{% tab tabName="Android (Kotlin)" %}}

```kotlin
analytics.track(
    name = "Order Completed",
    properties = Properties(mapOf(
        "revenue" to 30,
        "currency" to "USD",
        "user_actual_id" to 12345
    )),
    options = RudderOption(
        integrations = buildJsonObject {
            put(
                "Singular",
                buildJsonObject {
                    put("singularDeviceId", "<SINGULAR_DEVICE_ID>")
                    put("limitDataSharing", true) // optional
                }
            )
        }
    )
)
```

The corresponding Java snippet is as follows:

```java
String eventName = "Order Completed";

Map<String, Object> propertiesMap = new LinkedHashMap<>();
propertiesMap.put("revenue", 30);
propertiesMap.put("currency", "USD");
propertiesMap.put("user_actual_id", 12345);
Properties properties = new Properties(propertiesMap);

// Singular integration options
Map<String, Object> singularOptions = new LinkedHashMap<>();
singularOptions.put("singularDeviceId", "<SINGULAR_DEVICE_ID>");
singularOptions.put("limitDataSharing", true); // optional

// Integrations map
Map<String, Object> integrations = new LinkedHashMap<>();
integrations.put("Singular", singularOptions);

// Build options
RudderOption option = new RudderOptionBuilder()
        .setIntegrations(integrations)
        .build();

// Track event
analytics.track(eventName, properties, option);
```

{{% /tab %}}
{{% tab tabName="iOS (Swift)" %}}

```swift
analytics.track(
    name: "Order Completed",
    properties: [
        "revenue": 30,
        "currency": "USD",
        "user_actual_id": 12345
    ],
    options: RudderOption(
        integrations: [
            "Singular": [
                "singularDeviceId": "<SINGULAR_DEVICE_ID>",
                "limitDataSharing": true // optional
            ]
        ]
    )
)
```

The corresponding Objective-C snippet is as follows:

```objectivec
RSSOptionBuilder *optionBuilder = [[RSSOptionBuilder alloc] init];

[optionBuilder setIntegrations:@{
    @"Singular": @{
        @"singularDeviceId": @"<SINGULAR_DEVICE_ID>",
        @"limitDataSharing": @YES // optional
    }
}];

NSDictionary *properties = @{
    @"revenue": @30,
    @"currency": @"USD",
    @"user_actual_id": @12345
};

[analytics track:@"Order Completed"
       properties:properties
          options:[optionBuilder build]];
```

{{% /tab %}}
{{% tab tabName="Node.js" %}}

```javascript
client.track({
  userId: "1hKOmRA4GRlm",
  event: "Order Completed",
  properties: {
    revenue: 30,
    currency: "USD",
    user_actual_id: 12345,
  },
  integrations: {
    Singular: {
      singularDeviceId: "<SINGULAR_DEVICE_ID>",
      limitDataSharing: true, // optional
    },
  },
});
```

{{% /tab %}}
{{< /tabs >}}

The resulting `track` payload contains the Singular device ID (SDID) and data sharing options in the `integrations` object, as shown:

```json
{
  "type": "track",
  "event": "Order Completed",
  "properties": {
    "revenue": 30,
    "currency": "USD",
    "user_actual_id": 12345
  },
  "integrations": {
    "Singular": {
      "singularDeviceId": "<SINGULAR_DEVICE_ID>",
      "limitDataSharing": true
    }
  }
}
```

## FAQ 

#### How can I verify if the events are successfully delivered to Singular?

To verify if the events are successfully delivered to Singular, you can use RudderStack's [Destination live events]({{< ref "monitor/live-events.md#destination-live-events" >}}) feature.

You can also verify the event delivery by going to your [Singular dashboard](https://app.singular.net/login?redir=%2F#/) and following these steps:

1. In your Singular dashboard, navigate to **Developer Tools** > **Testing Console**.

{{< image src="images/event-stream-destinations/singular/singular-testing-console.webp" alt="Singular testing console" >}}

2. To add a new device to track, click the **Add Device** option and choose the relevant settings:

{{< image src="images/event-stream-destinations/singular/singular-add-device.webp" alt="Singular add new device to track" >}}

3. You should see a list of tracked devices, as seen below:

{{< image src="images/event-stream-destinations/singular/singular-list-devices.webp" alt="Singular list of tracked devices" >}}

4. Select the tracked device from the list to see the events in real-time.

{{< image src="images/event-stream-destinations/singular/singular-event-log.webp" alt="Singular event log" >}}

See the [Singular documentation](https://support.singular.net/hc/en-us/articles/360002675072-How-to-Test-Your-Singular-SDK-Integration) for more information on using the console for testing and validating your Singular integration.

#### Which device ID attributes are required for Android?

For Android requests, Singular requires either of the following attributes in the mentioned order of preference:
- `aifa`
- `asid` 
- `andi`
 
If none of them are available, you must send at least one with the empty value (instead of `null` or `undefined`). If you send all of them, RudderStack discards the `andi` attribute as per Google's data policies.
