Skip to content

Make custom callbacks work on iOS and Android - #20

Merged
anglinb merged 1 commit into
mainfrom
feat/custom-callbacks
Nov 2, 2026
Merged

anglinb merged 1 commit into
mainfrom
feat/custom-callbacks

Conversation

@anglinb

@anglinb anglinb commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Problem

PaywallPresentationHandler.OnCustomCallback existed but never ran:

  • Neither native bridge registered onCustomCallback, so the SDK found no handler and answered every callback with failure. No C# ran and nothing was logged in Unity.
  • C# had no way to reply. HandleOnCustomCallback built a response and never sent it.
  • The handler was synchronous, so it couldn't wait on a network request without blocking the main thread.

A paywall that asks the game to do something, such as creating an account through its own API, therefore always took its failure branch.

Changes

  • Bridges: both register a native handler, send onCustomCallback to C#, and hold the request until RespondToCustomCallback answers with the same callbackId. A request that can't be delivered or a reply that can't be parsed fails, so the paywall never waits on a reply that can't come.
  • C#: OnCustomCallback is now Func<CustomCallback, Task<CustomCallbackResult>>. If it throws, is cancelled or returns null, or the placement has no handler, the paywall gets a failure. The reply always goes out from the main thread. Adds CustomCallbackResult.Success(data) and Failure(data).
  • Android: an activity-hosted paywall pauses Unity, so with AndroidPaywallPresentation.Activity each callback fails immediately and logs an error naming the fix.
  • iOS: the generated Podfile requires SuperwallKit >= 4.12.10, < 5.0, the first versions with this API, and rewrites the old ~> 4.0 line in a Podfile kept by an Append build. A customer's own pin is left alone.
  • Docs: README section, sample and changelog. Version goes to 0.4.0.

Breaking change

A synchronous OnCustomCallback handler no longer compiles. The fix is an async lambda or Task.FromResult(...). Nothing that works today stops working, because that handler never ran. Keeping the old field alongside a new one would leave a field that looks usable and does nothing.

New CI

  • iOS: native.yml now runs an XCTest target on a simulator, which also covers the Swift half of this contract.
  • C#: a new csharp job runs Tests/Runtime on .NET through ci~/csharp-harness, with no Unity licence. It compiles the real Runtime/, tests and sample against a small UnityEngine stand-in that copies the behaviour the tests rely on: frames, the main-thread context, LogAssert, and failing on an unexpected error log. It also compiles the UNITY_IOS and UNITY_ANDROID branches, which nothing else in CI builds. unity.yml stays parked.

Testing

Not yet run in a real Unity player, or on a physical device. Please run a signup through a real build on both platforms before release.

Live, against a published paywall in an internal test app, with a small native app standing in for Unity and calling the same bridge entry points:

Run Result
iOS simulator, SuperwallKit 4.18.3 Success data, nested data, a failure message and a callback with no variables all reached the paywall, which echoed them back
Android emulator, superwall-android 2.8.7 Same
Android, AndroidPaywallPresentation.Activity Each callback failed immediately with the error log, and the paywall carried on
Android, new wiring removed (control) 0 callbacks reached Unity and the paywall showed okStatus: failure, the reported symptom

These ran before the rebase onto #13 and #18, neither of which touches the callback code. The test paywalls and campaigns are archived.

On this branch:

Suite Result
C# on .NET 48/48, and the UNITY_IOS and UNITY_ANDROID branches compile
Android bridge 17/17 on AGP 8.5.2 and on AGP 9.0.1
iOS bridge, iOS simulator 10/10

Four deliberate breaks to the C# handler were each caught: replying off the main thread, not catching exceptions, no reply for a missing handler, and not waiting for the task. Two payloads captured from the live runs are pinned in CustomCallbackTests. The Editor Podfile code isn't covered by CI. I compiled and ran it once against stubs, for a new Podfile, the old requirement, a customer's own pin and a Podfile with no SuperwallKit, and CocoaPods parsed each result.

The new CI jobs have not run yet; this PR is their first run.

🤖 Generated with Claude Code

PaywallPresentationHandler.OnCustomCallback existed but neither native
bridge called it, and C# had no way to reply. Every callback a paywall
requested failed straight away without reaching the game.

Both bridges now register a native handler, hold each request until C#
replies, and fail instead of waiting when a request can't be delivered or
a reply can't be parsed. On Android, an activity-hosted paywall pauses
Unity, so callbacks fail immediately and log an error naming the fix.

OnCustomCallback becomes Func<CustomCallback, Task<CustomCallbackResult>>
so a handler can wait on a network request. If it throws, is cancelled or
returns null, or the placement has none, the paywall gets a failure.

The generated Podfile now requires SuperwallKit 4.12.10 or newer, the
first version with this API, and updates the old requirement in a Podfile
kept by an Append build.

Adds bridge tests on iOS and Android, and a .NET harness that runs
Tests/Runtime in CI without a Unity licence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anglinb

anglinb commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

📚 Docs required

What changed for customers: Unity games can now handle a paywall's Custom Callback action on iOS and Android, by awaiting their own logic and returning a result that picks the paywall's onSuccess or onFailure branch. A synchronous OnCustomCallback handler no longer compiles.

Why this needs docs:

  • Breaking change: the Unity SDK reference publishes the old OnCustomCallback signature.
  • New API: CustomCallbackResult.Success(data) and Failure(data) appear on no docs page.
  • New prerequisites: callbacks need in-window presentation on Android and SuperwallKit 4.12.10 or later on iOS. The install page still gives ~> 4.0.

Coverage today: stale in places and absent in others. No page says Unity lacks custom callbacks, but none explains how to use them on Unity.

Page Status
content/docs/unity/sdk-reference/index.mdx L74, L165 (live) Stale. Lists custom callbacks as available and shows the old Func<CustomCallback, CustomCallbackResult> signature, which never ran before this PR
content/docs/unity/quickstart/install.mdx L38 (live) Stale. Says the post-build processor adds pod 'SuperwallKit', '~> 4.0'
Unity custom callbacks guide Absent. iOS and Android each have guides/advanced/custom-callbacks.mdx, Unity has none
content/docs/unity/guides/using-superwall-delegate.mdx L83 (live) Incomplete. Custom Paywall Actions is the only related Unity section, and it can't send a result back to the paywall
content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements.mdx L185, content/docs/dashboard/guides/offer-codes-and-promo-codes.mdx L69 Implies iOS and Android SDKs only. Version requirements name those two and leave Unity out
content/docs/dashboard/guides/using-referral-or-promo-codes-with-superwall.mdx L23 Implies iOS only. Links only to the iOS guide
content/docs/unity/changelog.mdx (live) Stale. Stops at 0.2.3, while the package changelog is at 0.3.0 before this PR

Without a docs page this change also gets no changelog entry: superwall/docs publishes a daily GitHub Release that superwall.com renders at /changelog, and every entry must link to a live docs page. Undocumented means invisible to customers.

Prompt for the docs agent — run in superwall/docs
Document a change that ships in superwall/Superwall-Unity#20: Make custom callbacks work on iOS and Android.

This PR was still open when this was written. Confirm Unity SDK 0.4.0 has been released before
publishing, and re-read the final diff in case it changed.

What ships, in customer terms:
Unity games can handle a paywall's Custom Callback action on iOS and Android. Set
PaywallPresentationHandler.OnCustomCallback, do the work (for example create an account through
your own API), and return a result. The result picks the paywall's onSuccess or onFailure branch,
and its Data is readable in the paywall as callbacks.NAME.data.KEY. Before 0.4.0 the field existed
but never ran, so every callback failed without reaching C#.

Breaking change: OnCustomCallback changes from Func<CustomCallback, CustomCallbackResult> to
Func<CustomCallback, Task<CustomCallbackResult>>. A synchronous handler no longer compiles. Migrate
with an async lambda or Task.FromResult(...). No working integration breaks, because the old handler
never ran.

New API: CustomCallbackResult.Success(Dictionary<string, object> data = null) and
CustomCallbackResult.Failure(Dictionary<string, object> data = null).

Behavior to state:
- CustomCallback.Variables holds the values the paywall sent, and is null if it sent none.
- Data values can be strings, numbers, booleans, lists and dictionaries.
- A blocking callback holds the paywall's tap action until the task completes, so give network
  requests a timeout.
- The paywall gets a failure if the handler throws, is cancelled or returns null, or if the placement
  has no OnCustomCallback.
- HandleCustomPaywallAction on the delegate is different: it tells the game a button was tapped and
  can't send anything back to the paywall.

Setup or prerequisites a customer must complete:
- Unity SDK 0.4.0 or later.
- Android: use the default in-window presentation. With
  SuperwallOptions.Paywalls.AndroidPresentation = AndroidPaywallPresentation.Activity, Unity is paused
  while the paywall is open, so each callback fails immediately and logs an error. This option is not
  in the Unity docs yet, so document it too.
- iOS: the generated Podfile now requires SuperwallKit >= 4.12.10, < 5.0, and an Append build rewrites
  the old ~> 4.0 line. A customer's own pin is left alone, so it must allow 4.12.10 or later.

Where it belongs (line numbers are from superwall/docs at e98c019):
- content/docs/unity/guides/custom-callbacks.mdx: new page, modelled on
  content/docs/ios/guides/advanced/custom-callbacks.mdx, with a migration note for the old signature.
- content/docs/unity/meta.json: add guides/custom-callbacks to the Guides section.
- content/docs/unity/sdk-reference/index.mdx: replace the OnCustomCallback signature at line 165,
  document CustomCallbackResult.Success/Failure, and link the new guide.
- content/docs/unity/quickstart/install.mdx: replace the pod requirement at line 38, and say in the
  Podfile note that follows that a customized Podfile needs SuperwallKit 4.12.10 or later.
- content/docs/unity/guides/using-superwall-delegate.mdx: after Custom Paywall Actions at line 83, say
  when to use custom callbacks instead and link the new guide.
- content/docs/unity/guides/advanced-configuration.mdx: document
  SuperwallOptions.Paywalls.AndroidPresentation and how it affects custom callbacks.
- content/docs/unity/changelog.mdx: add 0.4.0 with its breaking change, and fill the gap from 0.2.3
  using the package's CHANGELOG.md.
- content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements.mdx line 185 and
  content/docs/dashboard/guides/offer-codes-and-promo-codes.mdx line 69: add Unity SDK 0.4.0 to the
  version requirements.
- content/docs/dashboard/guides/using-referral-or-promo-codes-with-superwall.mdx line 23: link the
  Unity guide alongside the iOS one, or otherwise stop implying that callbacks are iOS only.

Source of truth — read these before writing:
- superwall/Superwall-Unity#20 diff: gh pr diff 20 --repo superwall/Superwall-Unity --patch
- Key files: README.md (Custom Callbacks section), CHANGELOG.md (0.4.0),
  Runtime/PaywallPresentationHandler.cs, Runtime/Models/RedemptionResult.cs,
  Editor/SuperwallPostBuildProcessor.cs

Constraints:
- Match the voice and structure of neighbouring pages under content/docs/**.
- Use the Fumadocs TypeTable for parameters and types; never <ParamTable>.
- If you add a page, add it to the folder's meta.json.
- Run `bun run build:cf` and `bun test` before opening the PR.
- Open a PR against superwall/docs referencing superwall/Superwall-Unity#20. Do not deploy.

Flagged by the docs-required skill. The docs-required label doesn't exist in this repo, so none was applied. If this is wrong, say why in a reply so the skill's calibration set can be corrected.

@anglinb
anglinb merged commit 2a91669 into main Nov 2, 2026
4 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.

1 participant