Do it yourself
$109 per platform, one-time
The WebViewGold license: a finished Xcode or Android Studio project. Paste your URL, set name and icon, switch on features, build and submit. Free updates forever.
See plans on the main pageClaude Code + push
Push is the native feature reviewers notice first and the one users actually keep. With WebViewGold, OneSignal runs inside the native shell; your web app only decides when to ask, which user a device belongs to and which screen a notification opens. Claude Code can write all of that in one session, and this guide gives you the prompt, the server code and the test plan.
From $109 one-time per platform, or let our team do it all from $698.
WebViewGold ships the OneSignal SDK inside both native projects. Once you switch it on, the app receives pushes even while it is closed. Your web app stays in charge of three decisions: when the system permission prompt appears, which user a device belongs to, and which URL a notification opens. Each of those is a URL scheme or an injected JavaScript variable, documented in the iOS push docs and the Android push docs.
Reviewers care for two reasons. Guideline 4.2 expects a web-based app to do more than a website, and a working push opt-in is usually the first native capability they see. Guideline 4.5.4 sets the limits: the app must work without push, notifications must not carry sensitive personal data, and promotional pushes need explicit opt-in in your UI plus a way to opt out. Android 13 and newer add a runtime permission (POST_NOTIFICATIONS), so the timing of your prompt matters on both platforms.
The flow you will end up with: after onboarding, the web app navigates to registerpush://; once the user allows, it reads onesignalplayerid and stores it on the user; your server sends a push to that subscription ID with a url in the additional data, and WebViewGold opens that URL when the notification is tapped. For the overall picture of native features, see the push notifications hub.
| Area | Claude Code | You |
|---|---|---|
| OneSignal dashboard | Nothing; it has no access to your dashboard | Create the app, add the APNs credentials (iOS) and the Firebase credentials (Android), copy the App ID and create a REST API key |
| WebViewGold projects | Can set kPushEnabled, oneSignalID, PUSH_ENABLED and the build.gradle ID if the projects live in the repository; you review the diff | Capabilities in the Apple Developer portal, the OneSignal extension bundle ID, signing and builds |
| Web app | Push helper, opt-in screen, settings row, register route, send helper, database migration, CLAUDE.md rules | Approve the plan, set the environment variables, deploy |
| Testing | A written test plan and, on request, a hidden debug page that shows the push status and subscription ID | A real iPhone and a real Android phone, TestFlight and internal testing |
Allow about an hour, most of it in the Apple and Firebase consoles.
Add iOS with your APNs credentials (OneSignal recommends a .p8 auth key; a push certificate also works) and Android with the Firebase service account JSON that OneSignal asks for. Copy the OneSignal App ID and create a REST API key for your server.
Set kPushEnabled to true, paste the App ID into oneSignalID and set askforpushpermissionatfirstrun to false so the prompt is yours to time. In the Apple Developer portal, enable Push Notifications and App Groups for the App ID. The OneSignal extension target needs your bundle ID plus .OneSignalNotificationServiceExtension.
Set PUSH_ENABLED to true, paste the App ID into ONE_SIGNAL_APP_ID in build.gradle (Module: app) and set requirePush to false. Replace ic_stat_onesignal_default.png with your own monochrome icon, otherwise users see a generic bell.
Set useragent_iphone and useragent_ipad (iOS) and USER_AGENT (Android) to a normal mobile user agent followed by WebViewGoldApp, so isNativeApp() can tell the app from Safari or Chrome. The CLAUDE.md template explains the convention.
Press Shift+Tab until Claude Code shows plan mode (or start it with claude --permission-mode plan), paste the prompt below and approve the plan only when it lists the files and the table you expect. Then let it implement, read the diff and deploy.
Archive the iOS app for TestFlight and upload the Android bundle to internal testing. If you would rather not install Xcode and Android Studio for this, the Cloud Builder builds both apps in the browser and uploads them for you.

Great way of converting your web app to a native app! Fast turn-around, and a great way to get your apps into the app stores without the need to develop a full native app!
Adjust the table name, the routes and the example notifications to your app. The prompt tells the agent where the authoritative scheme names live so it does not invent any.
Our web app also runs inside native iOS and Android apps built with WebViewGold. OneSignal push is switched on in the native projects. The web app has to ask for permission at the right moment, report the device subscription ID to our server, and the server sends targeted notifications. Before writing code, read the Push Notifications sections for iOS and Android in https://www.webviewgold.com/llms-full.txt and use only URL schemes and variables documented there. Start with a plan and wait for my approval. Implement: 1. src/lib/native.ts (reuse it if it exists): isNativeApp() checks navigator.userAgent for the token WebViewGoldApp; callNative(url) sets window.location.href only inside the app; readInjected(name, timeoutMs) polls window[name] every 100 ms and rejects after the timeout. 2. src/lib/push.ts: askForPush() navigates to registerpush://. getPushStatus() navigates to checkNativePushPermissions:// and resolves window.nativePushEnabled (true or false). syncPushId() navigates to getonesignalplayerid://, reads window.onesignalplayerid and POSTs it with the platform (ios or android, from the user agent) to /api/push/register. Never trigger two schemes in the same script run: on iOS several window.location.href calls in one script do not work, so sequence them with a delay of about one second. 3. An opt-in screen shown once after onboarding, never on the first screen, only when isNativeApp() is true: one sentence on what we send (order updates and replies), an Enable notifications button (askForPush, then syncPushId after 3 seconds) and a Not now link that hides the screen for 14 days via localStorage. 4. Settings: a Notifications row that shows the status from getPushStatus(). When it is off, show Open settings, which navigates to settingsapp://. Add a separate Product news toggle, off by default and stored on the user profile; only users who switched it on may receive promotional pushes (Apple guideline 4.5.4). 5. On every app start of a signed-in user inside the app, call syncPushId() once, because the subscription ID can change after a reinstall. 6. Server: POST /api/push/register (authenticated; upserts user_id, subscription_id, platform, last_seen_at into a push_devices table; add the migration) and a server-only helper sendPushToUser(userId, title, message, path) that calls the OneSignal REST API with include_subscription_ids and puts the full https URL of the target screen into data.url. Read ONESIGNAL_APP_ID and ONESIGNAL_REST_API_KEY from server environment variables only. 7. Call sendPushToUser where a new reply or an order status change is created. No personal or sensitive details in the notification text. 8. Add the push rules above to CLAUDE.md. Report the files you changed, the migration, the environment variables I must set and anything you could not verify.
Next.js App Router with TypeScript; ask the agent to adapt it to Express, a Supabase Edge Function or a Cloud Function if that is your stack. The data.url value is what WebViewGold opens when the notification is tapped; OneSignal's Launch URL field is not the one to use.
// app/api/push/register/route.ts (called by the web app inside the native shell)
import { NextResponse } from "next/server";
import { getUser, db } from "@/lib/server"; // your auth + database helpers
export async function POST(req: Request) {
const user = await getUser(req);
if (!user) return NextResponse.json({ error: "unauthorized" }, { status: 401 });
const { subscriptionId, platform } = await req.json();
if (typeof subscriptionId !== "string" || subscriptionId.length < 10) {
return NextResponse.json({ error: "invalid subscription id" }, { status: 400 });
}
await db.pushDevices.upsert({ userId: user.id, subscriptionId, platform, lastSeenAt: new Date() });
return NextResponse.json({ ok: true });
}
// lib/push-server.ts (server only, never import from client components)
export async function sendPushToUser(userId: string, title: string, message: string, path: string) {
const devices = await db.pushDevices.findMany({ userId });
if (devices.length === 0) return { sent: 0 };
const res = await fetch("https://api.onesignal.com/notifications?c=push", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Key " + process.env.ONESIGNAL_REST_API_KEY,
},
body: JSON.stringify({
app_id: process.env.ONESIGNAL_APP_ID,
include_subscription_ids: devices.map((d) => d.subscriptionId),
headings: { en: title },
contents: { en: message },
data: { url: "https://app.example.com" + path }, // opened by WebViewGold on tap
}),
});
if (!res.ok) throw new Error("OneSignal " + res.status + ": " + (await res.text()));
return { sent: devices.length };
}Simulators and desktop browsers cannot prove any of this. Use the TestFlight build on an iPhone and the internal testing build on an Android 13+ phone, both freshly installed.
askforpushpermissionatfirstrun and requirePush are false.push_devices and the device shows up in OneSignal under Audience → Subscriptions.url set to a deep page such as /orders/42. Tap it three times: with the app killed, in the background and in the foreground. Each tap opens that page in the app.settingsapp://. Re-enable there, return, and the status updates.window.location.href to several schemes in the same script does not work on iOS (Android handles it). Sequence calls with a delay, or use the documented multiapicall://getonesignalplayerid,getappversion for the supported data calls (MultiAPICall docs).url field from the additional data. Leave kPushOpenDeeplinkInBrowser (iOS) and OPEN_NOTIFICATION_URLS_IN_SYSTEM_BROWSER (Android) false if links should open inside the app.onesignalplayerid is injected asynchronously and may not exist until the SDK has registered. Poll with a timeout and retry on the next app start instead of failing silently.kPushEnhanceUrl or PUSH_ENHANCE_WEBVIEW_URL, only the first request gets ?onesignal_push_id=…; a client-side router loses it. Store it in a cookie or session right away, or use autoInjectVariable / AUTO_INJECT_VARIABLES.registerpush:// does nothing if no push service is enabled in the wrapper. Check the config before debugging your JavaScript.Yes. Set kFirebasePushEnabled in Config.swift with your GoogleService-Info.plist, and FIREBASE_PUSH_ENABLED in Config.java with your google-services.json. The web side is the same, except the ID comes from getfirebaseplayerid:// as firebaseplayerid.
No, and the app must not depend on it (guideline 4.5.4). It is still one of the strongest signals for guideline 4.2 that your app is more than a website.
Yes. Broadcasts from the OneSignal dashboard need no code, and local reminders scheduled with sendlocalpushmsg:// work without any push service. A server is only needed for per-user messages.
iOS shows the system dialog once. If the user declines because they do not know your app yet, only the system settings can change it. Asking after a moment that explains the value converts much better.
Do not rely on it. Inside the WebViewGold app, push is native through OneSignal, Firebase or Pushwoosh. Keep web push, if you use it, for browser visitors only.
Same result, different amount of work on your side. All three come with the approval guarantee and 24/7 human support.
$109 per platform, one-time
The WebViewGold license: a finished Xcode or Android Studio project. Paste your URL, set name and icon, switch on features, build and submit. Free updates forever.
See plans on the main page$349 per license, one-time add-on
No Mac, no Xcode, no Android Studio: configure in the browser, we build both apps in the cloud and hand them to App Store Connect and the Play Console.
How the Cloud Builder works Try the free demo$698 per platform $798
Send us your URL and icon. Our team configures, builds and delivers your app within 72 hours, white-label, with source projects. Both platforms for $1,158.
Full Service details Powered byWinter Sale prices valid until December 31, 2026.
App Store & Google Play approval guarantee – or your money back within 14 days
Rather hand it over? Full Service: we configure, build and deliver within 72 hours, from $698 per platform. Questions? Chat with us 24/7.
Refunds are granted under the conditions listed in our FAQ: a non-content rejection by Apple and/or Google, claimed within 14 days after purchase.