From 336f20c51e54fadb972f625fa062526ef8e3a78f Mon Sep 17 00:00:00 2001 From: Brian Anglin Date: Fri, 26 Jun 2026 12:45:43 -0700 Subject: [PATCH 1/5] Add Singular integration docs --- content/docs/integrations/meta.json | 1 + content/docs/integrations/singular.mdx | 243 +++++++++++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100644 content/docs/integrations/singular.mdx diff --git a/content/docs/integrations/meta.json b/content/docs/integrations/meta.json index 84244d66..5a6f7a83 100644 --- a/content/docs/integrations/meta.json +++ b/content/docs/integrations/meta.json @@ -11,6 +11,7 @@ "meta-conversion-api", "facebook-pixel", "adjust", + "singular", "mixpanel", "amplitude", "customer-io", diff --git a/content/docs/integrations/singular.mdx b/content/docs/integrations/singular.mdx new file mode 100644 index 00000000..1c9a19c5 --- /dev/null +++ b/content/docs/integrations/singular.mdx @@ -0,0 +1,243 @@ +--- +title: "Singular" +description: "The Singular integration sends Superwall subscription and payment events to Singular through the server-to-server EVENT API, with SDID attribution support." +--- + +In the **Analytics** section within **Integrations**, you can connect your Singular account to Superwall. + +## Features + +- **Server-to-Server Event Delivery**: Events are sent to Singular's EVENT endpoint. +- **SDID Attribution**: Uses Singular Device ID (SDID) when you set `singularDeviceId` or `sdid` on the Superwall user. +- **Device ID Fallbacks**: Falls back to Singular's V1 device identifiers when SDID is not available. +- **Revenue Tracking**: Sends amount and currency for events whose Superwall payload includes a nonzero amount. +- **Event Name Mapping**: Uses Singular-friendly default event names, with optional Superwall event name remapping. +- **Sandbox Support**: Sends sandbox events only when `sandbox_sdk_key` is configured. +- **Custom Attributes**: Sends Superwall event metadata in Singular's `e` parameter. + +## Configuration + +Singular uses an SDK key to authenticate server-to-server events. The app identifier sent to Singular is the bundle ID or package name from the Superwall event. + +| Field | Description | +| ------------------------- | ---------------------------------------------------------------------------------------------------------- | +| `integration_id` | Must be `singular`. | +| `sdk_key` | Your production Singular SDK key. | +| `sandbox_sdk_key` | Optional Singular SDK key for sandbox events. Sandbox events are skipped when this is blank. | +| `sales_reporting` | Revenue reporting mode: `"Revenue"` (gross) or `"Proceeds"` (net after store fees). | +| `anonymous_user_behavior` | Optional. Set to `"dontSend"` to skip events without an app user ID. Defaults to sending anonymous events. | +| `eventNameMappings` | Optional map of Superwall standard event types to custom Singular event names. | + +### Example configuration + +```json +{ + "integration_id": "singular", + "sdk_key": "your_singular_sdk_key", + "sandbox_sdk_key": "your_sandbox_singular_sdk_key", + "sales_reporting": "Revenue", + "anonymous_user_behavior": "send", + "eventNameMappings": { + "sw_subscription_start": "subscription_start", + "sw_renewal": "subscription_renewal", + "sw_refund": "subscription_refund" + } +} +``` + +## SDK Setup + +For the strongest attribution match, set Singular Device ID (SDID) in Superwall as soon as your app receives it from the Singular SDK. + + + Singular's SDID callback may require account enablement. If your Singular SDK setup does not + expose SDID retrieval, contact Singular before relying on SDID attribution. + + +### iOS + +```swift +// After Singular returns an SDID +Superwall.shared.setIntegrationAttributes([ + .singularDeviceId: sdid +]) +``` + +### Android + +```kotlin +import com.superwall.sdk.models.attribution.AttributionProvider + +// After Singular returns an SDID +Superwall.instance.setIntegrationAttributes( + mapOf(AttributionProvider.SINGULAR_DEVICE_ID to sdid) +) +``` + +### Raw attribute fallback + +If you are using an SDK that does not expose a typed Singular integration attribute yet, set the raw key: + +```json +{ + "singularDeviceId": "sdid-from-singular" +} +``` + +The integration also accepts `sdid` as an alias. When either value is present, Superwall sends the event to Singular's V2 EVENT endpoint with the `sdid` parameter. + +## Device Identification + +Superwall prefers SDID because it is Singular's own device identifier. + +| Superwall attribute | Singular parameter | Endpoint | +| ------------------- | ------------------ | -------- | +| `singularDeviceId` | `sdid` | V2 | +| `sdid` | `sdid` | V2 | + +When SDID is not present, the integration falls back to Singular's V1 endpoint with platform identifiers: + +| Platform | Required attributes | Optional attributes | Singular parameters | +| -------- | -------------------------------------------------- | ------------------- | ------------------------------------------ | +| iOS | `idfv` | `idfa`, `attStatus` | `idfv`, `idfa`, `att_authorization_status` | +| Android | `googleAppSetId` plus `advertisingId` or `gpsAdid` | - | `asid`, `aifa` | + +Events are skipped when neither SDID nor the required platform fallback identifiers are present. Events are also skipped when no operating system version is available from `deviceInfo.osVersion` or the `osVersion` attribute. + +## Event Mapping + +Superwall maps subscription lifecycle events to Singular event names. You can override any default with `eventNameMappings`. + +| Superwall standard event | Default Singular event name | +| ----------------------------- | --------------------------- | +| `sw_trial_start` | `sng_start_trial` | +| `sw_trial_cancelled` | `trial_cancelled` | +| `sw_trial_uncancelled` | `trial_uncancelled` | +| `sw_trial_expired` | `sng_end_trial` | +| `sw_trial_converted` | `sng_subscribe` | +| `sw_intro_offer_start` | `introOffer_start` | +| `sw_intro_offer_cancelled` | `introOffer_cancel` | +| `sw_intro_offer_uncancelled` | `introOffer_uncancel` | +| `sw_intro_offer_expired` | `introOffer_expire` | +| `sw_intro_offer_converted` | `sng_subscribe` | +| `sw_subscription_start` | `sng_subscribe` | +| `sw_renewal` | `subscription_renewed` | +| `sw_subscription_cancelled` | `subscription_cancelled` | +| `sw_subscription_uncancelled` | `subscription_uncancelled` | +| `sw_subscription_expired` | `subscription_expire` | +| `sw_subscription_paused` | `subscription_pause` | +| `sw_billing_issue` | `billing_issue` | +| `sw_product_change` | `product_change` | +| `sw_non_renewing_purchase` | `sng_ecommerce_purchase` | +| `sw_refund` | `subscription_refunded` | +| `sw_test` | `event_test` | + +Refund detection (`price < 0`) takes precedence over the subscription period mapping. + +## Singular API Fields + +Superwall sends a GET request to Singular. The endpoint depends on which identifier is available: + +| Identifier source | Endpoint | +| ----------------------------------- | ------------------------------------- | +| SDID (`singularDeviceId` or `sdid`) | `https://s2s.singular.net/api/v2/evt` | +| Platform fallback identifiers | `https://s2s.singular.net/api/v1/evt` | + +Common request parameters: + +| Singular parameter | Source | +| ------------------ | ---------------------------------------------------------- | +| `a` | `sdk_key` or `sandbox_sdk_key`, based on event environment | +| `p` | `iOS` or `Android` | +| `i` | Event bundle ID or Android package name | +| `n` | Default or mapped Singular event name | +| `utime` | Superwall event timestamp in seconds | +| `ve` | Operating system version | +| `country` | Event country code | +| `custom_user_id` | Superwall app user ID, when present | +| `ip` | User IP address, when present | +| `use_ip` | `true` when no explicit IP address is present | +| `lc` | Device locale, with `-` converted to `_` | +| `e` | JSON-encoded Superwall event attributes | + +Revenue parameters are included when the calculated amount is nonzero: + +| Singular parameter | Source | +| ------------------------- | -------------------------------------------------------- | +| `is_revenue_event` | `true` | +| `amt` | Gross or proceeds amount, depending on `sales_reporting` | +| `cur` | Transaction currency code | +| `purchase_product_id` | Product identifier | +| `purchase_transaction_id` | Transaction identifier | + +## Custom Event Attributes + +Every event includes the `e` parameter with JSON-encoded Superwall metadata: + +| Attribute | Description | +| ---------------------------- | -------------------------------------------------------- | +| `sw_event_id` | Superwall event ID | +| `sw_event_type` | Original Superwall event name | +| `sw_period_type` | `TRIAL`, `INTRO`, or `NORMAL` | +| `sw_store` | Store that produced the event | +| `sw_environment` | `PRODUCTION` or `SANDBOX` | +| `sw_product_id` | Product identifier | +| `sw_transaction_id` | Transaction identifier | +| `sw_original_transaction_id` | Original transaction identifier | +| `sw_original_app_user_id` | Superwall app user ID | +| `sw_is_trial_conversion` | Whether the renewal converted a trial | +| `sw_is_refund` | Whether the event is treated as a refund | +| `sw_country_code` | Event country code | +| `sw_offer_code` | Offer code, when present | +| `sw_new_product_id` | New product identifier for product changes, when present | + +## Revenue Tracking + +Revenue is calculated from the Superwall event payload: + +| Setting | Formula | Description | +| ------------ | ----------------------------------------------- | --------------------------------------- | +| `"Revenue"` | `priceInPurchasedCurrency` | Gross revenue in the purchased currency | +| `"Proceeds"` | `priceInPurchasedCurrency * takehomePercentage` | Net revenue after store fees | + +If the calculated amount is `0`, revenue parameters are omitted. Negative amounts are sent as revenue events and mapped to the refund event name when `price < 0`. + +## Platform Support + +The integration determines the platform from the event store: + +| Store | Platform behavior | +| ------------ | ---------------------------------------------------------- | +| `APP_STORE` | Sent as `iOS` | +| `PLAY_STORE` | Sent as `Android` | +| `STRIPE` | Sent only when `deviceInfo.platform` is `ios` or `android` | +| Other stores | Skipped | + +## Sandbox Handling + +Sandbox events use `sandbox_sdk_key`. If `sandbox_sdk_key` is not configured, sandbox events are skipped with no request to Singular. + +Sandbox events still include `sw_environment: "SANDBOX"` in the custom event attributes, so you can separate test traffic from production traffic in Singular. + +## Troubleshooting + +### Events are not appearing in Singular + +1. Confirm `sdk_key` is configured for production events. +2. For sandbox events, confirm `sandbox_sdk_key` is configured. +3. Set `singularDeviceId` or `sdid` before the subscription event occurs. +4. If SDID is not available, confirm the fallback identifiers are present: iOS needs `idfv`; Android needs `googleAppSetId` plus `advertisingId` or `gpsAdid`. +5. Confirm an operating system version is present in `deviceInfo.osVersion` or `osVersion`. +6. Confirm the event maps to iOS or Android. Unsupported stores are skipped. + +### Revenue is missing + +1. Confirm the Superwall event amount is nonzero. +2. Confirm the event includes a currency code. +3. Check whether `sales_reporting` is set to `"Revenue"` or `"Proceeds"`. + +### SDID attribution is missing + +1. Confirm the Singular SDK is returning SDID before the purchase flow. +2. Confirm Superwall receives the value as `singularDeviceId` or `sdid`. +3. Confirm the request is using `https://s2s.singular.net/api/v2/evt`. From 4808cb95ec0afa015587c448beabe29f07268b1a Mon Sep 17 00:00:00 2001 From: Brian Anglin Date: Fri, 26 Jun 2026 12:52:37 -0700 Subject: [PATCH 2/5] Clarify Singular SDID docs --- content/docs/integrations/singular.mdx | 36 +++++++++++++++----------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/content/docs/integrations/singular.mdx b/content/docs/integrations/singular.mdx index 1c9a19c5..a76a865b 100644 --- a/content/docs/integrations/singular.mdx +++ b/content/docs/integrations/singular.mdx @@ -11,7 +11,7 @@ In the **Analytics** section within **Integrations**, you can connect your Singu - **SDID Attribution**: Uses Singular Device ID (SDID) when you set `singularDeviceId` or `sdid` on the Superwall user. - **Device ID Fallbacks**: Falls back to Singular's V1 device identifiers when SDID is not available. - **Revenue Tracking**: Sends amount and currency for events whose Superwall payload includes a nonzero amount. -- **Event Name Mapping**: Uses Singular-friendly default event names, with optional Superwall event name remapping. +- **Event Name Mapping**: Uses Singular-friendly default event names for Superwall subscription events. - **Sandbox Support**: Sends sandbox events only when `sandbox_sdk_key` is configured. - **Custom Attributes**: Sends Superwall event metadata in Singular's `e` parameter. @@ -26,7 +26,6 @@ Singular uses an SDK key to authenticate server-to-server events. The app identi | `sandbox_sdk_key` | Optional Singular SDK key for sandbox events. Sandbox events are skipped when this is blank. | | `sales_reporting` | Revenue reporting mode: `"Revenue"` (gross) or `"Proceeds"` (net after store fees). | | `anonymous_user_behavior` | Optional. Set to `"dontSend"` to skip events without an app user ID. Defaults to sending anonymous events. | -| `eventNameMappings` | Optional map of Superwall standard event types to custom Singular event names. | ### Example configuration @@ -36,12 +35,7 @@ Singular uses an SDK key to authenticate server-to-server events. The app identi "sdk_key": "your_singular_sdk_key", "sandbox_sdk_key": "your_sandbox_singular_sdk_key", "sales_reporting": "Revenue", - "anonymous_user_behavior": "send", - "eventNameMappings": { - "sw_subscription_start": "subscription_start", - "sw_renewal": "subscription_renewal", - "sw_refund": "subscription_refund" - } + "anonymous_user_behavior": "send" } ``` @@ -76,15 +70,27 @@ Superwall.instance.setIntegrationAttributes( ### Raw attribute fallback -If you are using an SDK that does not expose a typed Singular integration attribute yet, set the raw key: +If you are using an SDK that does not expose a typed Singular integration attribute yet, set the raw user attribute key. -```json -{ - "singularDeviceId": "sdid-from-singular" -} +```swift +Superwall.shared.setUserAttributes([ + "singularDeviceId": sdid +]) +``` + +```kotlin +Superwall.instance.setUserAttributes( + mapOf("singularDeviceId" to sdid) +) +``` + +```dart +await Superwall.shared.setUserAttributes({ + 'singularDeviceId': sdid, +}); ``` -The integration also accepts `sdid` as an alias. When either value is present, Superwall sends the event to Singular's V2 EVENT endpoint with the `sdid` parameter. +The integration also accepts `sdid` as a backend/raw user-attribute alias. The typed SDK attribute is `singularDeviceId`. When either value is present, Superwall sends the event to Singular's V2 EVENT endpoint with the `sdid` parameter. ## Device Identification @@ -106,7 +112,7 @@ Events are skipped when neither SDID nor the required platform fallback identifi ## Event Mapping -Superwall maps subscription lifecycle events to Singular event names. You can override any default with `eventNameMappings`. +Superwall maps subscription lifecycle events to these Singular event names. | Superwall standard event | Default Singular event name | | ----------------------------- | --------------------------- | From 585135de94d78146e31c0bce926140fd72f3019e Mon Sep 17 00:00:00 2001 From: Brian Anglin Date: Fri, 26 Jun 2026 13:00:33 -0700 Subject: [PATCH 3/5] Address Singular docs review --- content/docs/integrations/singular.mdx | 41 ++++++++++++++++++++------ 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/content/docs/integrations/singular.mdx b/content/docs/integrations/singular.mdx index a76a865b..a7d8d0e4 100644 --- a/content/docs/integrations/singular.mdx +++ b/content/docs/integrations/singular.mdx @@ -9,7 +9,7 @@ In the **Analytics** section within **Integrations**, you can connect your Singu - **Server-to-Server Event Delivery**: Events are sent to Singular's EVENT endpoint. - **SDID Attribution**: Uses Singular Device ID (SDID) when you set `singularDeviceId` or `sdid` on the Superwall user. -- **Device ID Fallbacks**: Falls back to Singular's V1 device identifiers when SDID is not available. +- **Device ID Fallbacks**: Falls back to supported iOS and Google Play Android V1 identifiers when SDID is not available. - **Revenue Tracking**: Sends amount and currency for events whose Superwall payload includes a nonzero amount. - **Event Name Mapping**: Uses Singular-friendly default event names for Superwall subscription events. - **Sandbox Support**: Sends sandbox events only when `sandbox_sdk_key` is configured. @@ -41,13 +41,24 @@ Singular uses an SDK key to authenticate server-to-server events. The app identi ## SDK Setup -For the strongest attribution match, set Singular Device ID (SDID) in Superwall as soon as your app receives it from the Singular SDK. +### Prerequisites + +This integration sends subscription **EVENT** requests to Singular. It does not create Singular **SESSION** requests. Before Superwall forwards subscription events, your app must already be integrated with the Singular SDK, or you must send Singular S2S SESSION requests yourself, so Singular has a session to attach later events to. + +See Singular's [EVENT endpoint reference](https://support.singular.net/hc/en-us/articles/31496864868635-Server-to-Server-EVENT-Endpoint-API-Reference) and [subscription event guide](https://support.singular.net/hc/en-us/articles/30301510608283-Subscription-Event-Technical-Implementation-Guide) for their SESSION-before-EVENT requirement. + +### SDID + +For the strongest attribution match, set Singular Device ID (SDID) in Superwall as soon as your app receives it from an SDID-enabled Singular SDK flow. - Singular's SDID callback may require account enablement. If your Singular SDK setup does not - expose SDID retrieval, contact Singular before relying on SDID attribution. + Singular requires account enablement for iOS and Android SDID usage with the V2 EVENT endpoint. If + your Singular SDK setup does not expose SDID retrieval, contact Singular before relying on SDID + attribution. +Singular documents SDID retrieval through `sdidReceivedHandler` on iOS and `withSdidAccessorHandler` on Android. See Singular's [device ID guide](https://support.singular.net/hc/en-us/articles/4411780525979-Types-of-Device-IDs), [iOS SDK configuration reference](https://support.singular.net/hc/en-us/articles/42098616285339-iOS-SDK-Configuration-Methods-Reference), and [Android SDK configuration reference](https://support.singular.net/hc/en-us/articles/37157782717851-Android-SDK-Configuration-Methods-Reference). + ### iOS ```swift @@ -101,7 +112,7 @@ Superwall prefers SDID because it is Singular's own device identifier. | `singularDeviceId` | `sdid` | V2 | | `sdid` | `sdid` | V2 | -When SDID is not present, the integration falls back to Singular's V1 endpoint with platform identifiers: +When SDID is not present, the integration falls back to Singular's V1 endpoint with platform identifiers. Superwall currently supports the iOS and Google Play Android fallback identifiers below: | Platform | Required attributes | Optional attributes | Singular parameters | | -------- | -------------------------------------------------- | ------------------- | ------------------------------------------ | @@ -110,9 +121,16 @@ When SDID is not present, the integration falls back to Singular's V1 endpoint w Events are skipped when neither SDID nor the required platform fallback identifiers are present. Events are also skipped when no operating system version is available from `deviceInfo.osVersion` or the `osVersion` attribute. +For iOS fallback events, Singular requires `att_authorization_status`. `attStatus` is optional to provide to Superwall; when it is missing, Superwall sends `att_authorization_status=0`. + + + Singular's V1 EVENT endpoint also supports other Android identifiers, such as Amazon, OAID, and + Android ID, but Superwall does not send those fields for this integration. + + ## Event Mapping -Superwall maps subscription lifecycle events to these Singular event names. +Superwall maps subscription lifecycle events to these Singular event names. Some defaults are Singular standard or subscription-recommended event names, and others are Superwall-specific custom event names. All are under Singular's 32 ASCII-character event name limit. | Superwall standard event | Default Singular event name | | ----------------------------- | --------------------------- | @@ -156,7 +174,7 @@ Common request parameters: | `a` | `sdk_key` or `sandbox_sdk_key`, based on event environment | | `p` | `iOS` or `Android` | | `i` | Event bundle ID or Android package name | -| `n` | Default or mapped Singular event name | +| `n` | Default Singular event name | | `utime` | Superwall event timestamp in seconds | | `ve` | Operating system version | | `country` | Event country code | @@ -166,6 +184,8 @@ Common request parameters: | `lc` | Device locale, with `-` converted to `_` | | `e` | JSON-encoded Superwall event attributes | +Singular's broader mobile S2S device-data guidance recommends additional device fields such as make (`ma`), model (`mo`), and build (`bd`). Superwall currently sends locale when present and operating system version as `ve`, but does not send `ma`, `mo`, or `bd` in this integration because those fields are not all available in the normalized subscription event payload. + Revenue parameters are included when the calculated amount is nonzero: | Singular parameter | Source | @@ -208,6 +228,8 @@ Revenue is calculated from the Superwall event payload: If the calculated amount is `0`, revenue parameters are omitted. Negative amounts are sent as revenue events and mapped to the refund event name when `price < 0`. +Superwall sends already-validated revenue metadata. This integration does not send `purchase_receipt` or `receipt_signature`, so Singular store receipt validation is not performed by this integration. + ## Platform Support The integration determines the platform from the event store: @@ -245,5 +267,6 @@ Sandbox events still include `sw_environment: "SANDBOX"` in the custom event att ### SDID attribution is missing 1. Confirm the Singular SDK is returning SDID before the purchase flow. -2. Confirm Superwall receives the value as `singularDeviceId` or `sdid`. -3. Confirm the request is using `https://s2s.singular.net/api/v2/evt`. +2. Confirm Singular has enabled SDID usage for your account and app. +3. Confirm Superwall receives the value as `singularDeviceId` or `sdid`. +4. Confirm the request is using `https://s2s.singular.net/api/v2/evt`. From f16d9619dd0fa77a9c7fe1c813fe6d2823930ce4 Mon Sep 17 00:00:00 2001 From: Brian Anglin Date: Fri, 26 Jun 2026 13:08:03 -0700 Subject: [PATCH 4/5] Update Singular docs for review feedback --- content/docs/integrations/singular.mdx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/content/docs/integrations/singular.mdx b/content/docs/integrations/singular.mdx index a7d8d0e4..88579113 100644 --- a/content/docs/integrations/singular.mdx +++ b/content/docs/integrations/singular.mdx @@ -114,10 +114,10 @@ Superwall prefers SDID because it is Singular's own device identifier. When SDID is not present, the integration falls back to Singular's V1 endpoint with platform identifiers. Superwall currently supports the iOS and Google Play Android fallback identifiers below: -| Platform | Required attributes | Optional attributes | Singular parameters | -| -------- | -------------------------------------------------- | ------------------- | ------------------------------------------ | -| iOS | `idfv` | `idfa`, `attStatus` | `idfv`, `idfa`, `att_authorization_status` | -| Android | `googleAppSetId` plus `advertisingId` or `gpsAdid` | - | `asid`, `aifa` | +| Platform | Required attributes | Optional attributes | Singular parameters | +| -------- | ------------------- | ---------------------------- | ------------------------------------------ | +| iOS | `idfv` | `idfa`, `attStatus` | `idfv`, `idfa`, `att_authorization_status` | +| Android | `googleAppSetId` | `advertisingId` or `gpsAdid` | `asid`, `aifa` | Events are skipped when neither SDID nor the required platform fallback identifiers are present. Events are also skipped when no operating system version is available from `deviceInfo.osVersion` or the `osVersion` attribute. @@ -186,7 +186,7 @@ Common request parameters: Singular's broader mobile S2S device-data guidance recommends additional device fields such as make (`ma`), model (`mo`), and build (`bd`). Superwall currently sends locale when present and operating system version as `ve`, but does not send `ma`, `mo`, or `bd` in this integration because those fields are not all available in the normalized subscription event payload. -Revenue parameters are included when the calculated amount is nonzero: +Revenue parameters are included when the calculated amount is at least `0.001`: | Singular parameter | Source | | ------------------------- | -------------------------------------------------------- | @@ -226,7 +226,7 @@ Revenue is calculated from the Superwall event payload: | `"Revenue"` | `priceInPurchasedCurrency` | Gross revenue in the purchased currency | | `"Proceeds"` | `priceInPurchasedCurrency * takehomePercentage` | Net revenue after store fees | -If the calculated amount is `0`, revenue parameters are omitted. Negative amounts are sent as revenue events and mapped to the refund event name when `price < 0`. +If the calculated amount is below `0.001`, revenue parameters are omitted. Refunds are mapped to the refund event name when `price < 0`, but revenue parameters are omitted for those events. Superwall sends already-validated revenue metadata. This integration does not send `purchase_receipt` or `receipt_signature`, so Singular store receipt validation is not performed by this integration. @@ -254,13 +254,13 @@ Sandbox events still include `sw_environment: "SANDBOX"` in the custom event att 1. Confirm `sdk_key` is configured for production events. 2. For sandbox events, confirm `sandbox_sdk_key` is configured. 3. Set `singularDeviceId` or `sdid` before the subscription event occurs. -4. If SDID is not available, confirm the fallback identifiers are present: iOS needs `idfv`; Android needs `googleAppSetId` plus `advertisingId` or `gpsAdid`. +4. If SDID is not available, confirm the fallback identifiers are present: iOS needs `idfv`; Android needs `googleAppSetId`. 5. Confirm an operating system version is present in `deviceInfo.osVersion` or `osVersion`. 6. Confirm the event maps to iOS or Android. Unsupported stores are skipped. ### Revenue is missing -1. Confirm the Superwall event amount is nonzero. +1. Confirm the Superwall event amount is at least `0.001`. 2. Confirm the event includes a currency code. 3. Check whether `sales_reporting` is set to `"Revenue"` or `"Proceeds"`. From bf2441de6173312b9566ddb38dac3b2c1d7f3b23 Mon Sep 17 00:00:00 2001 From: Brian Anglin Date: Fri, 26 Jun 2026 13:33:58 -0700 Subject: [PATCH 5/5] Simplify Singular integration docs --- content/docs/integrations/singular.mdx | 251 +++++++------------------ 1 file changed, 73 insertions(+), 178 deletions(-) diff --git a/content/docs/integrations/singular.mdx b/content/docs/integrations/singular.mdx index 88579113..86eab1b2 100644 --- a/content/docs/integrations/singular.mdx +++ b/content/docs/integrations/singular.mdx @@ -1,63 +1,44 @@ --- title: "Singular" -description: "The Singular integration sends Superwall subscription and payment events to Singular through the server-to-server EVENT API, with SDID attribution support." +description: "Connect Singular to receive Superwall subscription lifecycle and revenue events, with SDID support for stronger attribution." --- In the **Analytics** section within **Integrations**, you can connect your Singular account to Superwall. +Use this integration when Singular is your attribution or marketing analytics destination and you want Superwall subscription events delivered there automatically. + ## Features -- **Server-to-Server Event Delivery**: Events are sent to Singular's EVENT endpoint. -- **SDID Attribution**: Uses Singular Device ID (SDID) when you set `singularDeviceId` or `sdid` on the Superwall user. -- **Device ID Fallbacks**: Falls back to supported iOS and Google Play Android V1 identifiers when SDID is not available. -- **Revenue Tracking**: Sends amount and currency for events whose Superwall payload includes a nonzero amount. -- **Event Name Mapping**: Uses Singular-friendly default event names for Superwall subscription events. -- **Sandbox Support**: Sends sandbox events only when `sandbox_sdk_key` is configured. -- **Custom Attributes**: Sends Superwall event metadata in Singular's `e` parameter. +- **Subscription lifecycle events**: Sends trials, subscription starts, renewals, cancellations, expirations, billing issues, product changes, refunds, and one-time purchases. +- **Revenue tracking**: Includes amount and currency for paid subscription events when revenue is available. +- **SDID-first attribution**: Uses Singular Device ID (SDID) when you pass it to Superwall. +- **Device ID fallbacks**: Uses supported iOS and Android identifiers when SDID is not available. +- **Sandbox controls**: Keeps sandbox events separate by using a sandbox SDK key, or skips sandbox events when one is not configured. +- **Superwall context**: Includes key Superwall event details such as product, transaction, user, store, environment, and country. ## Configuration -Singular uses an SDK key to authenticate server-to-server events. The app identifier sent to Singular is the bundle ID or package name from the Superwall event. - -| Field | Description | -| ------------------------- | ---------------------------------------------------------------------------------------------------------- | -| `integration_id` | Must be `singular`. | -| `sdk_key` | Your production Singular SDK key. | -| `sandbox_sdk_key` | Optional Singular SDK key for sandbox events. Sandbox events are skipped when this is blank. | -| `sales_reporting` | Revenue reporting mode: `"Revenue"` (gross) or `"Proceeds"` (net after store fees). | -| `anonymous_user_behavior` | Optional. Set to `"dontSend"` to skip events without an app user ID. Defaults to sending anonymous events. | - -### Example configuration - -```json -{ - "integration_id": "singular", - "sdk_key": "your_singular_sdk_key", - "sandbox_sdk_key": "your_sandbox_singular_sdk_key", - "sales_reporting": "Revenue", - "anonymous_user_behavior": "send" -} -``` - -## SDK Setup +Singular requires an SDK key to accept server-side events from Superwall. Configure the production key, and optionally configure a sandbox key if you want test transactions to appear in Singular. -### Prerequisites +| Field | Description | +| ----- | ----------- | +| Production SDK key | Your Singular SDK key for production events. | +| Sandbox SDK key | Optional. Used for sandbox events. If blank, sandbox events are skipped. | +| Sales reporting | Choose **Revenue** for gross revenue or **Proceeds** for net revenue after store fees. | +| Anonymous users | Optional. Choose whether to send events that do not have an app user ID. | -This integration sends subscription **EVENT** requests to Singular. It does not create Singular **SESSION** requests. Before Superwall forwards subscription events, your app must already be integrated with the Singular SDK, or you must send Singular S2S SESSION requests yourself, so Singular has a session to attach later events to. - -See Singular's [EVENT endpoint reference](https://support.singular.net/hc/en-us/articles/31496864868635-Server-to-Server-EVENT-Endpoint-API-Reference) and [subscription event guide](https://support.singular.net/hc/en-us/articles/30301510608283-Subscription-Event-Technical-Implementation-Guide) for their SESSION-before-EVENT requirement. +## SDK Setup -### SDID +Superwall forwards subscription events to Singular after they happen. Your app should already initialize the Singular SDK, or otherwise create Singular sessions, so Singular can connect later subscription events to the right device. -For the strongest attribution match, set Singular Device ID (SDID) in Superwall as soon as your app receives it from an SDID-enabled Singular SDK flow. +For the strongest attribution match, pass Singular Device ID (SDID) to Superwall as soon as Singular makes it available. - Singular requires account enablement for iOS and Android SDID usage with the V2 EVENT endpoint. If - your Singular SDK setup does not expose SDID retrieval, contact Singular before relying on SDID - attribution. + Singular requires account enablement for iOS and Android SDID usage. If your Singular SDK setup + does not expose SDID retrieval, contact Singular before relying on SDID attribution. -Singular documents SDID retrieval through `sdidReceivedHandler` on iOS and `withSdidAccessorHandler` on Android. See Singular's [device ID guide](https://support.singular.net/hc/en-us/articles/4411780525979-Types-of-Device-IDs), [iOS SDK configuration reference](https://support.singular.net/hc/en-us/articles/42098616285339-iOS-SDK-Configuration-Methods-Reference), and [Android SDK configuration reference](https://support.singular.net/hc/en-us/articles/37157782717851-Android-SDK-Configuration-Methods-Reference). +Singular documents SDID retrieval in its [device ID guide](https://support.singular.net/hc/en-us/articles/4411780525979-Types-of-Device-IDs), [iOS SDK configuration reference](https://support.singular.net/hc/en-us/articles/42098616285339-iOS-SDK-Configuration-Methods-Reference), and [Android SDK configuration reference](https://support.singular.net/hc/en-us/articles/37157782717851-Android-SDK-Configuration-Methods-Reference). ### iOS @@ -79,9 +60,9 @@ Superwall.instance.setIntegrationAttributes( ) ``` -### Raw attribute fallback +### Other SDKs -If you are using an SDK that does not expose a typed Singular integration attribute yet, set the raw user attribute key. +If your Superwall SDK does not yet expose a typed Singular integration attribute, set `singularDeviceId` as a user attribute instead. ```swift Superwall.shared.setUserAttributes([ @@ -101,172 +82,86 @@ await Superwall.shared.setUserAttributes({ }); ``` -The integration also accepts `sdid` as a backend/raw user-attribute alias. The typed SDK attribute is `singularDeviceId`. When either value is present, Superwall sends the event to Singular's V2 EVENT endpoint with the `sdid` parameter. +Superwall also accepts `sdid` as a raw user-attribute alias, but `singularDeviceId` is the recommended key. ## Device Identification -Superwall prefers SDID because it is Singular's own device identifier. - -| Superwall attribute | Singular parameter | Endpoint | -| ------------------- | ------------------ | -------- | -| `singularDeviceId` | `sdid` | V2 | -| `sdid` | `sdid` | V2 | +Superwall uses the best available Singular-compatible identifier for each event. -When SDID is not present, the integration falls back to Singular's V1 endpoint with platform identifiers. Superwall currently supports the iOS and Google Play Android fallback identifiers below: +| Priority | Identifier | Notes | +| -------- | ---------- | ----- | +| 1 | `singularDeviceId` or `sdid` | Preferred. Uses Singular's SDID flow. | +| 2 | iOS `idfv` | Used when SDID is unavailable. `idfa` and `attStatus` are included when present. | +| 3 | Android `googleAppSetId` | Used when SDID is unavailable. `advertisingId` or `gpsAdid` is included when present. | -| Platform | Required attributes | Optional attributes | Singular parameters | -| -------- | ------------------- | ---------------------------- | ------------------------------------------ | -| iOS | `idfv` | `idfa`, `attStatus` | `idfv`, `idfa`, `att_authorization_status` | -| Android | `googleAppSetId` | `advertisingId` or `gpsAdid` | `asid`, `aifa` | - -Events are skipped when neither SDID nor the required platform fallback identifiers are present. Events are also skipped when no operating system version is available from `deviceInfo.osVersion` or the `osVersion` attribute. - -For iOS fallback events, Singular requires `att_authorization_status`. `attStatus` is optional to provide to Superwall; when it is missing, Superwall sends `att_authorization_status=0`. - - - Singular's V1 EVENT endpoint also supports other Android identifiers, such as Amazon, OAID, and - Android ID, but Superwall does not send those fields for this integration. - +Events are skipped when Superwall cannot find SDID or the required platform fallback identifier. Events are also skipped when no operating system version is available from the event payload or user attributes. ## Event Mapping -Superwall maps subscription lifecycle events to these Singular event names. Some defaults are Singular standard or subscription-recommended event names, and others are Superwall-specific custom event names. All are under Singular's 32 ASCII-character event name limit. - -| Superwall standard event | Default Singular event name | -| ----------------------------- | --------------------------- | -| `sw_trial_start` | `sng_start_trial` | -| `sw_trial_cancelled` | `trial_cancelled` | -| `sw_trial_uncancelled` | `trial_uncancelled` | -| `sw_trial_expired` | `sng_end_trial` | -| `sw_trial_converted` | `sng_subscribe` | -| `sw_intro_offer_start` | `introOffer_start` | -| `sw_intro_offer_cancelled` | `introOffer_cancel` | -| `sw_intro_offer_uncancelled` | `introOffer_uncancel` | -| `sw_intro_offer_expired` | `introOffer_expire` | -| `sw_intro_offer_converted` | `sng_subscribe` | -| `sw_subscription_start` | `sng_subscribe` | -| `sw_renewal` | `subscription_renewed` | -| `sw_subscription_cancelled` | `subscription_cancelled` | -| `sw_subscription_uncancelled` | `subscription_uncancelled` | -| `sw_subscription_expired` | `subscription_expire` | -| `sw_subscription_paused` | `subscription_pause` | -| `sw_billing_issue` | `billing_issue` | -| `sw_product_change` | `product_change` | -| `sw_non_renewing_purchase` | `sng_ecommerce_purchase` | -| `sw_refund` | `subscription_refunded` | -| `sw_test` | `event_test` | - -Refund detection (`price < 0`) takes precedence over the subscription period mapping. - -## Singular API Fields - -Superwall sends a GET request to Singular. The endpoint depends on which identifier is available: - -| Identifier source | Endpoint | -| ----------------------------------- | ------------------------------------- | -| SDID (`singularDeviceId` or `sdid`) | `https://s2s.singular.net/api/v2/evt` | -| Platform fallback identifiers | `https://s2s.singular.net/api/v1/evt` | - -Common request parameters: - -| Singular parameter | Source | -| ------------------ | ---------------------------------------------------------- | -| `a` | `sdk_key` or `sandbox_sdk_key`, based on event environment | -| `p` | `iOS` or `Android` | -| `i` | Event bundle ID or Android package name | -| `n` | Default Singular event name | -| `utime` | Superwall event timestamp in seconds | -| `ve` | Operating system version | -| `country` | Event country code | -| `custom_user_id` | Superwall app user ID, when present | -| `ip` | User IP address, when present | -| `use_ip` | `true` when no explicit IP address is present | -| `lc` | Device locale, with `-` converted to `_` | -| `e` | JSON-encoded Superwall event attributes | - -Singular's broader mobile S2S device-data guidance recommends additional device fields such as make (`ma`), model (`mo`), and build (`bd`). Superwall currently sends locale when present and operating system version as `ve`, but does not send `ma`, `mo`, or `bd` in this integration because those fields are not all available in the normalized subscription event payload. - -Revenue parameters are included when the calculated amount is at least `0.001`: - -| Singular parameter | Source | -| ------------------------- | -------------------------------------------------------- | -| `is_revenue_event` | `true` | -| `amt` | Gross or proceeds amount, depending on `sales_reporting` | -| `cur` | Transaction currency code | -| `purchase_product_id` | Product identifier | -| `purchase_transaction_id` | Transaction identifier | - -## Custom Event Attributes - -Every event includes the `e` parameter with JSON-encoded Superwall metadata: - -| Attribute | Description | -| ---------------------------- | -------------------------------------------------------- | -| `sw_event_id` | Superwall event ID | -| `sw_event_type` | Original Superwall event name | -| `sw_period_type` | `TRIAL`, `INTRO`, or `NORMAL` | -| `sw_store` | Store that produced the event | -| `sw_environment` | `PRODUCTION` or `SANDBOX` | -| `sw_product_id` | Product identifier | -| `sw_transaction_id` | Transaction identifier | -| `sw_original_transaction_id` | Original transaction identifier | -| `sw_original_app_user_id` | Superwall app user ID | -| `sw_is_trial_conversion` | Whether the renewal converted a trial | -| `sw_is_refund` | Whether the event is treated as a refund | -| `sw_country_code` | Event country code | -| `sw_offer_code` | Offer code, when present | -| `sw_new_product_id` | New product identifier for product changes, when present | +Superwall maps subscription lifecycle events to Singular-friendly event names. The most important mappings are: + +| Superwall lifecycle | Singular event name | +| ------------------- | ------------------- | +| Trial start | `sng_start_trial` | +| Trial end | `sng_end_trial` | +| Subscription start or trial conversion | `sng_subscribe` | +| Renewal | `subscription_renewed` | +| Cancellation | `subscription_cancelled` | +| Uncancellation | `subscription_uncancelled` | +| Expiration | `subscription_expire` | +| Pause | `subscription_pause` | +| Billing issue | `billing_issue` | +| Product change | `product_change` | +| One-time purchase | `sng_ecommerce_purchase` | +| Refund | `subscription_refunded` | + +Refund detection takes precedence over other lifecycle mappings. ## Revenue Tracking -Revenue is calculated from the Superwall event payload: - -| Setting | Formula | Description | -| ------------ | ----------------------------------------------- | --------------------------------------- | -| `"Revenue"` | `priceInPurchasedCurrency` | Gross revenue in the purchased currency | -| `"Proceeds"` | `priceInPurchasedCurrency * takehomePercentage` | Net revenue after store fees | +Superwall includes revenue on paid events when the calculated amount is at least `0.001`. -If the calculated amount is below `0.001`, revenue parameters are omitted. Refunds are mapped to the refund event name when `price < 0`, but revenue parameters are omitted for those events. +| Setting | What Singular receives | +| ------- | ---------------------- | +| **Revenue** | Gross revenue in the purchased currency. | +| **Proceeds** | Net revenue after store fees, based on Superwall's event payload. | -Superwall sends already-validated revenue metadata. This integration does not send `purchase_receipt` or `receipt_signature`, so Singular store receipt validation is not performed by this integration. +Revenue events include the amount, currency, product identifier, and transaction identifier. Refund events use the refund event name, but do not include revenue fields. ## Platform Support -The integration determines the platform from the event store: - -| Store | Platform behavior | -| ------------ | ---------------------------------------------------------- | -| `APP_STORE` | Sent as `iOS` | -| `PLAY_STORE` | Sent as `Android` | -| `STRIPE` | Sent only when `deviceInfo.platform` is `ios` or `android` | -| Other stores | Skipped | +| Store | Behavior | +| ----- | -------- | +| App Store | Sent as iOS events. | +| Google Play | Sent as Android events. | +| Stripe | Sent only when the event includes an iOS or Android platform. | +| Other stores | Skipped. | ## Sandbox Handling -Sandbox events use `sandbox_sdk_key`. If `sandbox_sdk_key` is not configured, sandbox events are skipped with no request to Singular. +Sandbox events use the sandbox SDK key. If you do not configure one, Superwall skips sandbox events instead of sending them with the production key. -Sandbox events still include `sw_environment: "SANDBOX"` in the custom event attributes, so you can separate test traffic from production traffic in Singular. +Sandbox events still include Superwall's sandbox environment context, so you can separate test traffic from production traffic in Singular. ## Troubleshooting ### Events are not appearing in Singular -1. Confirm `sdk_key` is configured for production events. -2. For sandbox events, confirm `sandbox_sdk_key` is configured. -3. Set `singularDeviceId` or `sdid` before the subscription event occurs. -4. If SDID is not available, confirm the fallback identifiers are present: iOS needs `idfv`; Android needs `googleAppSetId`. -5. Confirm an operating system version is present in `deviceInfo.osVersion` or `osVersion`. -6. Confirm the event maps to iOS or Android. Unsupported stores are skipped. +1. Confirm the production SDK key is configured. +2. For sandbox events, confirm the sandbox SDK key is configured. +3. Confirm your app initializes Singular and creates Singular sessions. +4. Set `singularDeviceId` before subscription events occur. +5. If SDID is unavailable, confirm fallback identifiers are present: iOS needs `idfv`; Android needs `googleAppSetId`. +6. Confirm an operating system version is available. ### Revenue is missing -1. Confirm the Superwall event amount is at least `0.001`. +1. Confirm the event has a calculated amount of at least `0.001`. 2. Confirm the event includes a currency code. -3. Check whether `sales_reporting` is set to `"Revenue"` or `"Proceeds"`. +3. Check whether sales reporting is set to **Revenue** or **Proceeds**. ### SDID attribution is missing -1. Confirm the Singular SDK is returning SDID before the purchase flow. +1. Confirm the Singular SDK returns SDID before the purchase flow. 2. Confirm Singular has enabled SDID usage for your account and app. 3. Confirm Superwall receives the value as `singularDeviceId` or `sdid`. -4. Confirm the request is using `https://s2s.singular.net/api/v2/evt`.