Custom Callbacks
Run your own code when a paywall asks for it, then send back success or failure so the paywall can pick its next step. Web SDK 0.3.4+.
Beta
The Web SDK is in beta and its API may change between releases.
A Custom Callback action in the paywall editor asks your page to run some code and answer with success or failure. The paywall then runs its On Success or On Failure actions. Use it to check a code the user typed or look something up in your backend.
This works the same way as onCustomCallback in the iOS and Android SDKs. You answer callbacks per presentation, on the handler you pass to register(), not on the delegate.
Requirements
- Web SDK 0.3.4 or later (
@superwall/paywalls-js, and@superwall/paywalls-reactfor React). - The Web SDK must present the paywall. A paywall opened on its own, outside the SDK, has nothing to answer the callback, so it always gets
failure. - The paywall must use the default iframe presenter or a custom presenter. Paywalls you render yourself with the
paywalloption oruseCustomPaywalldon't receive callbacks.
Add a callback
- In the paywall editor, select the element that should trigger the callback.
- Under Tap Behavior, click + Add Action and choose Custom Callback.
- Enter a Name, such as
validateCode. - Choose a Behavior. See Blocking and non-blocking.
- To send values to your page, click + Add Variable.
- Add follow-up actions under On Success and On Failure.
- Publish the paywall.
- In your page, pass
handler.onCustomCallbacktoregister():
await sw.register({
placement: "campaign_trigger",
handler: {
onCustomCallback: async ({ name, variables }) => {
if (name !== "validateCode") return { status: "failure" };
const ok = await api.validate(variables?.["params.code"]);
return { status: ok ? "success" : "failure", data: { ok } };
},
},
});See Tap behaviors for the rest of the editor's actions.
What your handler receives
onCustomCallback receives a CustomCallback:
Prop
Type
What to return
Return a CustomCallbackResult, or a promise of one:
Prop
Type
For example, a callback named validateCode that returns { status: "success", data: { ok: true } } sets callbacks.validateCode.data.ok in the paywall. The paywall also sets callbacks.validateCode.result to the status.
The SDK always answers the paywall. It sends failure when:
- You pass neither
onCustomCallbacknor a matching entry incustomCallbacks. - Your handler throws or its promise rejects.
- Your handler returns something without a
statusof"success"or"failure".
A throw or a bad return is logged through the SDK's logger. If the paywall closes before your handler finishes, the answer is dropped.
Typed callbacks
For type-checked handlers, declare your callbacks on the CustomCallbacks interface and answer them by name with handler.customCallbacks. Each handler gets that callback's input as its variables, and a success result's data is checked against output.
declare module "@superwall/paywalls-js" {
interface CustomCallbacks {
validateCode: { input: { "params.code": string }; output: { ok: boolean } };
}
}
await sw.register({
placement: "campaign_trigger",
handler: {
customCallbacks: {
validateCode: async (vars) => ({
status: "success",
data: { ok: await api.validate(vars["params.code"]) },
}),
},
},
});The SDK checks customCallbacks first. Any callback name that isn't in the map goes to onCustomCallback, so you can pass both. Typed handlers come with a few catches:
- Write
inputandoutputas inline object types or type aliases. Aninterfacedoesn't fitRecord<string, JsonValue>, so its handler is typed asnever. - The types are not checked at runtime. Callback names and variables come from the paywall editor and can drift from your declaration.
- A typed handler receives
variablesas its first argument and{ name }as its second.
React
Pass onCustomCallback or customCallbacks to usePlacement:
const { register } = usePlacement({
onCustomCallback: async ({ name, variables }) => {
if (name !== "validateCode") return { status: "failure" };
const ok = await api.validate(variables?.["params.code"]);
return { status: ok ? "success" : "failure" };
},
});usePlacement always calls the latest onCustomCallback you passed, so it can use current state. A throw still reaches the SDK and becomes failure.
<SuperwallPaywall> accepts onCustomCallback and customCallbacks props without a type error but ignores them in 0.3.4. Use usePlacement or call register() yourself.
Blocking and non-blocking
The Behavior you choose in the editor controls only whether the paywall waits. Your handler runs and the SDK answers either way.
- Blocking: the paywall waits for your answer before it runs the rest of the action chain. Use it when the next step depends on the result, such as validating a code.
- Non-blocking: the paywall continues right away. On Success or On Failure still runs when your answer arrives.
The SDK has no timeout. A blocking callback waits as long as your handler takes, so keep it fast.
Custom presenters
If you pass your own presenter to register(), the SDK hands it the callback runner as ctx.onCustomCallback on the PresentationContext. It runs customCallbacks and onCustomCallback for you and never rejects. A handler that throws resolves failure.
ctx.onCustomCallback is set only when the caller passed onCustomCallback or customCallbacks. When it's absent, answer failure, as the default presenter does.
Limitations
- Only paywalls the Web SDK presents receive callbacks. A standalone paywall link always gets a failure.
- Paywalls rendered through a custom
paywallrenderer oruseCustomPaywalldon't receive callbacks. <SuperwallPaywall>ignores callback props in 0.3.4.- There's no timeout. A blocking callback holds the paywall until your handler answers.
Related
How is this guide?