Fix web checkout redemption codes being dropped on cold start (iOS) - #228
Closed
nhamilton94 wants to merge 1 commit into
Closed
nhamilton94 wants to merge 1 commit into
nhamilton94 wants to merge 1 commit into
Conversation
`handleDeepLink` called the deprecated instance overload `Superwall.shared.handleDeepLink`, which routes immediately with no configuration guard and no replay. `SuperwallProvider` hands the URL over in the same effect that starts `configure()`, so on a cold start the code always arrives before the SDK is configured and is silently discarded. The static `Superwall.handleDeepLink` already handles this: it stores the URL via `DeepLinkRouter.storeDeepLink` and replays it once config is retrieved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L8Ek51PUFYeYWBwhbXRupp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
handleDeepLinkcalls the deprecated instance overload, which routes immediately with no configuration guard and no replay:The static overload is the one that handles a not-yet-configured SDK:
DeepLinkRouter.storeDeepLinkparks the URL inpendingDeepLink, andlistenToConfigreplays it once config is.retrieved. The instance method does neither, so the code is discarded.Why the timing always loses
SuperwallProvidercallshandleDeepLinkfrom auseEffecton mount, in the same commit that kicks offconfigure().Linking.getInitialURL()resolves in microseconds; configuring requires a network round-trip. The code always arrives first — this isn't a race that's sometimes lost, it's one that can't be won.If
configure()hasn't been entered at all yet, it's worse:Superwall.sharedlogs an error, tripsassertionFailure, and returns a throwawaySuperwall()that is never assigned to the singleton, so the URL is routed into a container with no API key.Because the provider registers its own handler, apps can't work around this by gating their own
handleDeepLinkcall — the SDK's copy still fires early.Evidence
Across three Superwall applications, entire project history (2026-06-15 → 2026-09-10):
redemption_startEXISTING_CODESredemption_completeEXISTING_CODESredemption_failEXISTING_CODESCODE575 redemption events, 100%
EXISTING_CODES. Aredeem(.code)has never once reached Superwall's servers from any device.Why this is hard to notice
Two things make the broken path look healthy:
registerAppTransactionIdIfNeeded(ReceiptManager.swift:115-135) firesredeem(.existingCodes)once on first launch, gated by theAppTransactionIdSentflag. It emitsredemption_startandredemption_completewhile resending an empty stored-code set, so it grants nothing. On a fresh install this appears ~500ms afterapp_installand reads as a successful redemption.willRedeemLinkisguard case .code = type else { return }(WebEntitlementRedeemer.swift:413-414), and everydidRedeemLinkcall site (:561,:604,:712) sits inside.code-only paths. So neither callback fires for theEXISTING_CODESevents integrators actually observe — it reads as a logging gap rather than a missing redemption.Nothing is logged when the code is dropped.
Reproduction
SuperwallProvider, Web Checkout in Redeem mode.yourscheme://superwall/redeem?code=…).redemption_startwith$type: CODE, no entitlement, nowillRedeemLink/didRedeemLink, no error.Not verified
android/.../SuperwallExpoModule.ktalready callsSuperwall.handleDeepLink(url)on the companion object. I couldn't check whether the Android SDK applies the same pre-config guard — only the iOS SDK source was available to me. Worth confirming on your side.A changeset is included (patch).
🤖 Generated with Claude Code
https://claude.ai/code/session_01L8Ek51PUFYeYWBwhbXRupp