Description
On Android, after calling customConsentGDPR (or deleteCustomConsentTo), the returned SPConsent is internally inconsistent:
gdpr.grants (the structured map) is correct — the updated vendor has granted = true.
webConsents is stale — inside gdpr.webConsentPayload, the same vendor still has vendorGrant: false and is missing from customVendorsResponse.consentedVendors.
Since preloadConsent injects webConsents into the WebView, embeds that depend on the vendor keep showing the consent placeholder after the grant — i.e. the consent doesn't appear to persist on WebView reopen, even though the native consent state is updated. iOS is not affected.
Root cause
In SourcePointUnifiedCmpData.kt:
fun SPConsents.toHostAPISPConsent() = HostAPISPConsent(
gdpr = gdpr?.toHostAPIGDPRConsent(),
webConsents = this.toWebViewConsentsJsonObject().toString()
)
toWebViewConsentsJsonObject() (from the SourcePoint native SDK) serializes from the SDK's cached state, which doesn't reflect the just-applied custom consent. So gdpr.grants and webConsents are derived inconsistently from the same object.
Proposed fix
Re-derive the webConsentPayload.grants from the authoritative structured grants:
fun SPConsents.toHostAPISPConsent() = HostAPISPConsent(
gdpr = gdpr?.toHostAPIGDPRConsent(),
webConsents = this.toWebViewConsentsJsonObject()
.syncGrantsWith(gdpr?.consent?.grants)
.toString()
)
private fun JSONObject.syncGrantsWith(grants: Map<String, GDPRPurposeGrants>?): JSONObject {
if (grants == null) return this
val gdpr = optJSONObject("gdpr") ?: return this
val payloadStr = gdpr.optString("webConsentPayload").takeIf { it.isNotEmpty() } ?: return this
val payload = JSONObject(payloadStr)
val newGrants = JSONObject()
val consentedVendors = JSONArray()
grants.forEach { (vendorId, grant) ->
newGrants.put(vendorId, JSONObject().apply {
put("vendorGrant", grant.granted)
put("purposeGrants", JSONObject(grant.purposeGrants as Map<*, *>))
})
if (grant.granted) consentedVendors.put(JSONObject().put("_id", vendorId))
}
payload.put("grants", newGrants)
payload.optJSONObject("customVendorsResponse")?.put("consentedVendors", consentedVendors)
gdpr.put("webConsentPayload", payload.toString())
return this
}
This makes customConsentGDPR and deleteCustomConsentTo return a webConsents blob consistent with gdpr.grants, matching the iOS behavior. Ideally the underlying issue should also be reported to SourcePoint so toWebViewConsentsJsonObject() reflects custom consent natively.
Description
On Android, after calling customConsentGDPR (or deleteCustomConsentTo), the returned SPConsent is internally inconsistent:
gdpr.grants (the structured map) is correct — the updated vendor has granted = true.
webConsents is stale — inside gdpr.webConsentPayload, the same vendor still has vendorGrant: false and is missing from customVendorsResponse.consentedVendors.
Since preloadConsent injects webConsents into the WebView, embeds that depend on the vendor keep showing the consent placeholder after the grant — i.e. the consent doesn't appear to persist on WebView reopen, even though the native consent state is updated. iOS is not affected.
Root cause
In SourcePointUnifiedCmpData.kt:
toWebViewConsentsJsonObject() (from the SourcePoint native SDK) serializes from the SDK's cached state, which doesn't reflect the just-applied custom consent. So gdpr.grants and webConsents are derived inconsistently from the same object.
Proposed fix
Re-derive the webConsentPayload.grants from the authoritative structured grants:
This makes customConsentGDPR and deleteCustomConsentTo return a webConsents blob consistent with gdpr.grants, matching the iOS behavior. Ideally the underlying issue should also be reported to SourcePoint so toWebViewConsentsJsonObject() reflects custom consent natively.