Actions
Close the paywall, restore purchases, open links, request OS permissions, and call back into your app — everything a paywall asks its host to do.
A paywall runs inside your app, and some things only the host can do: dismiss the paywall, open a link, prompt for a permission, run your app's code. All of it goes through useActions():
import { useActions } from "superwall/hooks";
const { close, restore, openUrl, requestPermission, requestCallback } = useActions();The actions
| Action | Use it for |
|---|---|
close() | Closing the paywall — the X button. Closing is not navigation. |
restore() | Restore purchases. Fire-and-forget: success arrives as a transaction_complete event or a dismissed paywall — there is no return value to await. See Purchases. |
openUrl(url) | Terms, privacy, any link. Always this, never <a href>. |
openExternalUrl(url) | Open in the system browser instead of in-app. |
openDeepLink(link) | Deep link into the app. |
customPlacement(name, params?) | Fire a Superwall placement — which can present another paywall. |
requestPermission(type) | OS permission prompt. Resolves "granted" | "denied" | "unsupported". |
requestCallback(name, options?) | Run your app's code and await its answer. Resolves { status: "success" | "failure", data? }. |
requestStoreReview("in-app" | "external") | Store review prompt. |
Links go through openUrl, never an <a href>. Inside a webview, an anchor either does nothing or navigates the paywall away from itself — openUrl hands the URL to the host so it opens the way the platform expects.
Closing works the same way: the paywall lives on a navigation stack of its own pages, but leaving the paywall isn't a navigation — it's close(). See Pages & navigation.
Permissions
const status = await requestPermission("notification");
// "granted" | "denied" | "unsupported"Permission types: notification, camera, microphone, location, background_location, contacts, read_images, read_video (Android only), tracking.
Callbacks — ask your app a question
A callback runs code in your app and hands the answer back to the paywall — anything the paywall cannot know on its own: does this account exist, is this referral code valid, what did the user pick during signup.
const result = await requestCallback<{ exists: boolean }>("checkAccount");
if (result.status === "success" && result.data?.exists) {
router.push("welcome-back");
}Type the answer with a claim, as above — the generic is your statement of what the app returns.
Permission vs callback
A permission asks the OS; a callback asks your app. Both resolve from code the paywall does not control, which shapes how you use them:
- Show something while they run. The OS prompt or your app's code takes as long as it takes.
- Treat a denial as an ordinary outcome, not an error. A user who declines notifications is still a user — design the path that continues without.
In development
In superwall dev, actions don't reach a real host — they're logged in the studio's event log, and permission, callback, and purchase requests prompt you to pick the outcome. That makes both branches of every flow testable before a device ever sees it. See The studio.
The permissions example shows requestPermission and requestCallback side by side, with a denial treated as an outcome rather than an error. See Examples.
How is this guide?
Variables & Personalization
React to user attributes, device state, and placement parameters — and write paywalls the dashboard can experiment on without a rebuild.
Lifecycle & Events
What a paywall knows and when — the preload rule that shapes every entry animation, and the SDK events you can react to.