●GROWTH — Rork keeps growing with 743K monthly visits and an 85% growth rate●MAX — Rork Max generates native Swift apps for iPhone, iPad, Watch, TV, Vision Pro, and iMessage●MAX — It reaches AR/LiDAR scanning, Metal 3D games, Live Activities, HealthKit, and Core ML, beyond React Native's reach●STACK — Standard Rork builds iOS and Android together in React Native (Expo), so non-engineers can ship real apps●PRICE — Plans start free, paid tiers from $25/month, and Rork Max at $200/month●MARKET — Gartner projects 75% of new apps will be low-code or no-code by the end of 2026●GROWTH — Rork keeps growing with 743K monthly visits and an 85% growth rate●MAX — Rork Max generates native Swift apps for iPhone, iPad, Watch, TV, Vision Pro, and iMessage●MAX — It reaches AR/LiDAR scanning, Metal 3D games, Live Activities, HealthKit, and Core ML, beyond React Native's reach●STACK — Standard Rork builds iOS and Android together in React Native (Expo), so non-engineers can ship real apps●PRICE — Plans start free, paid tiers from $25/month, and Rork Max at $200/month●MARKET — Gartner projects 75% of new apps will be low-code or no-code by the end of 2026
Start a Live Activity Without Launching Your Rork Max App — Designing Around a push-to-start Token That Never Arrives
In a native Swift app generated by Rork Max, you want to start a Live Activity from your server without the user ever opening the app. But the push-to-start token is never observed and it fails silently. Here's the cause and an observation layer that reliably captures the token.
The moment a ride is confirmed, I wanted the ETA to appear on the lock screen even if the user never opened the app — a server-initiated Live Activity. That's what I set out to add to a Rork Max app with push-to-start. Since iOS 17.2, you can start a Live Activity from a remote notification alone, without ever bringing the app to the foreground. But even with the implementation in place, the start token that was supposed to reach my server stayed empty forever. No error, no warning — the token was simply never observed.
The cause was that the task running Activity.pushToStartTokenUpdates never actually captured a token. As an indie developer working on the lock screen, this "the code is in but the token silently never comes" state is the first thing that stops you. This walks through adding a launch-from-cold path to a Rork Max native app with the smallest diff: how to isolate the silent failure, and an observation layer you can use as-is.
push-to-start and update run on different tokens
The first thing to sort out: remote control of a Live Activity involves two tokens with different natures. Confuse them and you'll capture one while the other jams, landing you in "why won't it work."
The roles of the start token and the update token
The first is the push-to-start token. It's the key to "starting a Live Activity that doesn't yet exist, from a cold app," and you can observe exactly one per app when ActivityAuthorizationInfo is enabled. The second is each Activity's update token, used to update or end an individual Activity that has already started.
push-to-start is per-app; update is per-Activity. That granularity difference carries straight into the delivery side. The apns-topic you send to APNs differs from a normal push (bundleId): for both start and update it's {bundleId}.push-type.liveactivity, with apns-push-type: liveactivity. Leave that topic the same as a normal push and, even with a valid token, delivery is rejected silently.
First, confirm the token is even being observed — from logs
Before chasing the cause by guesswork, I confirm on-device from logs whether the token-observation task is actually emitting values. push-to-start arrives via an async sequence, so if the task never started or got released midway, it just stops silently.
import ActivityKitimport oslet laLog = Logger(subsystem: "net.rorklab.sample", category: "liveactivity")@MainActorfinal class LiveActivityBootstrap { private var startTokenTask: Task<Void, Never>? func begin() { // Without permission, the token never comes guard ActivityAuthorizationInfo().areActivitiesEnabled else { laLog.error("live activities disabled by user") return } observeStartTokens() } private func observeStartTokens() { startTokenTask = Task { for await tokenData in Activity<DeliveryAttributes>.pushToStartTokenUpdates { let token = tokenData.map { String(format: "%02x", $0) }.joined() laLog.info("push-to-start token: \(token.prefix(12))…") await self.registerStartToken(token) // send to server } } }}
Note that pushToStartTokenUpdates is static on the Activity type. DeliveryAttributes is your own ActivityAttributes-conforming type, and you observe the app-wide start token bound to it. If the token fragment never appears in the logs, the cause is almost always either the observation task's start timing or permission.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦You can isolate from logs where push-to-start — starting a Live Activity from the server without opening the app — falls silent: permission, Info.plist, or the token-observation task
✦You get a thin observation layer that runs pushToStartTokenUpdates from launch and reliably delivers the token to your server, ready to drop into Rork Max's generated code
✦You'll sort out the two distinct token types used for start vs. update, down to the APNs topic on the delivery side that makes it work on a real device
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Run token observation as a launch-time, permanent task
The most common silent failure is starting the observation task when the screen appears. The whole point of push-to-start is not making the user open the app, so waiting for them to bring up a screen defeats the purpose — you have to capture the token right after launch and register it with the server.
extension LiveActivityBootstrap { // Call this right after app launch (App.init / .task in SwiftUI) func registerStartToken(_ token: String) async { var request = URLRequest(url: URL(string: "https://api.rorklab.example/latoken")!) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = try? JSONEncoder().encode(["startToken": token]) do { _ = try await URLSession.shared.data(for: request) laLog.info("start token registered") } catch { laLog.error("register failed: \(error.localizedDescription)") } }}
What to hold onto here: the push-to-start token can rotate on reinstall or after a long stretch of not opening the app. Don't end the observation task after one shot — keep the for await loop running permanently and overwrite the server side on every update. That avoids the "it worked yesterday, then stopped silently" failure from token expiry. I recommend keeping this observation as a permanent task separate from notification permission or account state.
The payload when the server sends the start push
Once you have the token, the server sends a liveactivity-type push to APNs. Only on start does event become start, and you must pass the initial state via attributes-type and attributes. Its structure differs from a normal update payload, so implementing only update and deferring start becomes an easy-to-miss trap.
The attributes-type string must exactly match the name of your app's ActivityAttributes-conforming type. One character off and delivery arrives but no Activity is created — another silent failure. Before going to production I generate this type name from a single constant shared by the app and server templates, so hand-typed drift can't happen.
The parts that need your own hands in Rork Max's output
Rork Max generates the ActivityKit UI and the content-state structure well from natural language. But making push-to-start hold on a real device requires configuration outside the code. Three things I verify every time.
First, set NSSupportsLiveActivities to YES in Info.plist. Without it, Live Activities don't run at all. Second, if you use push-to-start, branch for iOS 17.2 and up — pushToStartTokenUpdates was added in 17.2, so fall back to a normal launch with if #available on lower versions. Third, the delivery-side topic: switch the apns-topic and apns-push-type above to the liveactivity variants.
The order for isolating "the token silently never comes"
When I'm stuck on a real device, I check in this order. First, is areActivitiesEnabledtrue? If the user turned Live Activities off in Settings, the token never comes. Next, does the push-to-start token appear in the logs right after launch? If not, suspect where the observation task starts. If it passes this far and the token reaches the server but no Activity starts, the cause narrows to the delivery stage — a mismatch in apns-topic or attributes-type.
Fixing this isolation order up front lets you name which layer is at fault quickly, even for a feature like Live Activities that tangles UI, permission, and delivery infrastructure. For your next step, wire the LiveActivityBootstrap above into a launch-time .task, then fully terminate the app on a real device and fire a single start push from the server. Whether the Activity springs up on the lock screen — or where the logs stall — pins down the rest of the cause.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.