Skip to content

docs: three Web2App facts the release notes surfaced - #155

Merged
kherembourg merged 2 commits into
mainfrom
docs/6.1.0-web2app-notes
Sep 7, 2026
Merged

docs: three Web2App facts the release notes surfaced#155
kherembourg merged 2 commits into
mainfrom
docs/6.1.0-web2app-notes

Conversation

@kherembourg

Copy link
Copy Markdown
Collaborator

Writing the 6.1.0 release notes against React Native's turned up three integrator-facing facts our docs never stated. Docs only, no code change.

1. Compare an anonymous user id case-insensitively

An id passed to anonymousUserId is stored uppercase on both platforms. An id the SDK generates is not consistent across them — measured in this release's own E2E runs on 6.1.0:

generated id
iOS 1933C4CC-5A7E-44DD-929C-0BA9AAE4858B — uppercase
Android ff394b92-2dc0-4861-a31a-2eaef1aa1708 — lowercase

So == against Purchasely.anonymousUserId is a real footgun.

Worth noting: React Native's notes state the SDK generates ids "in lowercase". That is true on Android and false on iOS — I had the counter-example sitting in my own E2E logs, so I documented the measured behaviour rather than copying the claim. Same class of mistake as the "the masked email hint is iOS only" line that had to be corrected earlier in this release.

2. A successful redemption restores the web purchase's user attributes

Built-in and custom, applied before the entitlements refresh, so every later event and audience already sees them. Verified in the Android 6.1.0 source (WebRedemptionContextMapper.kt, attributesToRestore / builtInAttributes) rather than taken on faith. Readable through the existing getters or setUserAttributeListener.

3. The redemption token never reaches the app

Not the listener, not a log line, not an analytics event — it is a bearer credential. Also documented: REDEMPTION_CONSUMED fires when the user taps an already redeemed link, because a replay is a success; purchase_context.replay tells the two apart.

🤖 Generated with Claude Code

Modelling the 6.1.0 release notes on React Native's turned up three
integrator-facing facts we never wrote down.

1. **Compare an anonymous user id case-insensitively.** An id passed to
   `anonymousUserId` is stored uppercase on both platforms, but an id the SDK
   *generates* is not consistent across them. Measured in this release's own E2E
   runs on 6.1.0: iOS generated `1933C4CC-…` (uppercase), Android generated
   `ff394b92-…` and `209c9287-…` (lowercase). So `==` against
   `Purchasely.anonymousUserId` is a real footgun.

   React Native's notes say the SDK generates ids "in lowercase". That is true on
   Android and false on iOS — I had the counter-example in my own logs and
   documented the measured behaviour instead of copying the claim. Same class of
   mistake as the "the masked email hint is iOS only" line that had to be
   corrected earlier in this release.

2. **A successful redemption restores the web purchase's user attributes**,
   built-in and custom, applied *before* the entitlements refresh, so every later
   event and audience already sees them. Verified in the Android 6.1.0 source
   (`WebRedemptionContextMapper.kt`, `attributesToRestore` / `builtInAttributes`).
   Readable through the existing getters or `setUserAttributeListener`.

3. **The redemption token never reaches the app** — not the listener, not a log
   line, not an analytics event. And `REDEMPTION_CONSUMED` also fires when the
   user taps an already redeemed link: a replay is a success, and
   `purchase_context.replay` tells the two apart.

Docs only; no code change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Sep 7, 2026

Copy link
Copy Markdown

Greptile Summary

This documentation-only PR adds integration guidance for anonymous-user ID normalization and Web2App redemption behavior.

  • Recommends case-insensitive anonymous-user ID comparisons across Android and iOS.
  • Describes restoration of user attributes during successful redemption.
  • Documents replay behavior and redemption analytics events.
  • Incorrectly assures integrators that redemption bearer tokens cannot enter analytics events, despite the public event model exposing them.

Confidence Score: 3/5

The PR should not merge until the redemption-token guidance reflects that analytics event payloads expose the bearer credential and require redaction.

The documentation directly contradicts the public event model and tested decoder, creating a realistic path for applications to forward a redemption bearer token into third-party analytics under a false safety assumption.

Files Needing Attention: sdk_public_doc.md

Security Review

The new token-safety guidance contradicts the SDK’s public redemption event contract. REDEMPTION_CONSUMED and REDEMPTION_FAILED payloads can expose the bearer token through PLYEventProperties.redemption.token, so consumers must redact that field before forwarding events.

Important Files Changed

Filename Overview
sdk_public_doc.md Adds Web2App integration guidance, but incorrectly states that redemption analytics events never expose the bearer token.
purchasely/lib/src/purchasely_builder.dart Adds accurate guidance to compare canonical anonymous-user UUIDs case-insensitively.

Fix all with Greploop Fix All in Claude Code Fix All in Cursor Fix All in Codex

Prompt To Fix All With AI
### Issue 1
sdk_public_doc.md:338-339
**Token Exposure Claim Is False**

The new guarantee that the bearer token never reaches an analytics event contradicts the public event contract. Both redemption events expose `PLYEventProperties.redemption.token`, and the event mapper copies the native `redemption.token` value into Dart. An integrator trusting this statement could forward redemption events without redaction and leak the credential to an analytics system. Document the token-bearing event field and require consumers to exclude it from telemetry.

**How this was verified:** The public redemption event model and mapper expose the native event payload’s `token` field through `PLYEventProperties.redemption.token`.

```suggestion
The redemption analytics events expose the bearer credential through
`PLYEventProperties.redemption.token`. Never forward this field to analytics or
logs. `PLYWebRedemptionResult.errorMessage` never contains the token.
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "docs: three Web2App facts the release no..." | Re-trigger Greptile

Comment thread sdk_public_doc.md Outdated
Comment on lines +338 to +339
Not through the listener, not in a log line, not in an analytics event. It is a
bearer credential; `PLYWebRedemptionResult.errorMessage` never contains it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Token Exposure Claim Is False

The new guarantee that the bearer token never reaches an analytics event contradicts the public event contract. Both redemption events expose PLYEventProperties.redemption.token, and the event mapper copies the native redemption.token value into Dart. An integrator trusting this statement could forward redemption events without redaction and leak the credential to an analytics system. Document the token-bearing event field and require consumers to exclude it from telemetry.

How this was verified: The public redemption event model and mapper expose the native event payload’s token field through PLYEventProperties.redemption.token.

Suggested change
Not through the listener, not in a log line, not in an analytics event. It is a
bearer credential; `PLYWebRedemptionResult.errorMessage` never contains it.
The redemption analytics events expose the bearer credential through
`PLYEventProperties.redemption.token`. Never forward this field to analytics or
logs. `PLYWebRedemptionResult.errorMessage` never contains the token.

Knowledge Base Used: Actions, events, and interception

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdk_public_doc.md
Line: 338-339

Comment:
**Token Exposure Claim Is False**

The new guarantee that the bearer token never reaches an analytics event contradicts the public event contract. Both redemption events expose `PLYEventProperties.redemption.token`, and the event mapper copies the native `redemption.token` value into Dart. An integrator trusting this statement could forward redemption events without redaction and leak the credential to an analytics system. Document the token-bearing event field and require consumers to exclude it from telemetry.

**How this was verified:** The public redemption event model and mapper expose the native event payload’s `token` field through `PLYEventProperties.redemption.token`.

```suggestion
The redemption analytics events expose the bearer credential through
`PLYEventProperties.redemption.token`. Never forward this field to analytics or
logs. `PLYWebRedemptionResult.errorMessage` never contains the token.
```

**Knowledge Base Used:** [Actions, events, and interception](https://app.greptile.com/purchasely/-/custom-context/knowledge-base/purchasely/purchasely-flutter/-/docs/actions-events-and-interception.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Cursor Fix in Codex

Greptile flagged it and it is right. I wrote "the redemption token never reaches
your app, a log line or an analytics event", copied from the React Native release
notes. It is false, and it is the security-relevant direction to be wrong in.

Verified against the Android 6.1.0 source: `RedemptionOutcome` builds the events
with `PLYEvent.RedemptionConsumed(redemption.copy(token = token))` and
`PLYEvent.RedemptionFailed(code, message, token)` — the **raw** token lands on
`PLYEventProperties.redemption.token`. An integrator trusting my sentence could
forward redemption events unredacted and ship a bearer credential to their
analytics stack.

Replaced with what the sources actually say, per channel:

| Channel | Token |
| --- | --- |
| `PLYEventProperties.redemption.token` | raw token — exclude from telemetry |
| `DEEPLINK_OPENED` `deeplink_identifier` | redacted to a 6-character prefix |
| `PLYWebRedemptionResult.errorMessage` | never contains it |
| SDK logs | never contains it |

Also corrected the dartdoc on the field itself, which described it as "the
redemption link token this event reports on" without saying it is the credential.

Note for the React Native release notes: they carry the same false claim.

Second time this release that copying a cross-wrapper claim instead of checking
the native source produced a wrong statement — after "the masked email hint is
iOS only". Both were caught by review rather than by me.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kherembourg
kherembourg merged commit 7054452 into main Sep 7, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants