RORK LABJP
ACQUISITION — Rork makes its first acquisition, buying Paperline, a macOS app that generates native Swift apps with AIFUNDING — The $15M seed led by Left Lane Capital backs Rork's push to redefine how mobile apps are built and monetizedGROWTH — Rork Max reportedly hit $1.5M ARR within three days of launch and doubled annual revenue in two weeksENGINE — Rork Max runs on Claude Code and Claude Opus 4.6, the first web Swift builder aiming to replace XcodeSPLIT — Standard Rork uses React Native (Expo); Rork Max generates native Swift across the whole Apple ecosystemPRICING — Start free; paid plans begin at $25/month, with Rork Max at $200/monthACQUISITION — Rork makes its first acquisition, buying Paperline, a macOS app that generates native Swift apps with AIFUNDING — The $15M seed led by Left Lane Capital backs Rork's push to redefine how mobile apps are built and monetizedGROWTH — Rork Max reportedly hit $1.5M ARR within three days of launch and doubled annual revenue in two weeksENGINE — Rork Max runs on Claude Code and Claude Opus 4.6, the first web Swift builder aiming to replace XcodeSPLIT — Standard Rork uses React Native (Expo); Rork Max generates native Swift across the whole Apple ecosystemPRICING — Start free; paid plans begin at $25/month, with Rork Max at $200/month
Articles/Dev Tools
Dev Tools/2026-05-01Advanced

Implementing CallKit + PushKit in Rork: Native Call UI and VoIP Push for Voice & Video Apps

A practical, production-grade walkthrough for wiring CallKit and PushKit into a Rork-built voice or video calling app — covering token lifecycle, audio sessions, App Store review risks, and the WebRTC handoff.

Rork447CallKitPushKitVoIPPush Notifications8WebRTC3iOS83React Native181Calling Apps

Premium Article

"My Rork-built calling app doesn't ring when the screen is locked." I get this question often, and almost every time the root cause is the same: the app is relying on a regular APNs alert. To make a real iOS VoIP call land — full-screen incoming UI on the lock screen, audio playing the moment the user taps Answer — you need a different, parallel API surface: CallKit plus PushKit.

I've shipped this stack myself in a Rork-based WebRTC calling app, and the first few days were entirely spent figuring out why incoming calls wouldn't ring on a real device. Apple's docs are scattered, and there's even less material when you're bridging from React Native. This article is the survival guide I wish I'd had — laid out so that you can avoid the same pitfalls I hit on the way to production.

The patterns below cover both Rork (React Native) and Rork Max (native SwiftUI). For React Native, we'll use react-native-callkeep plus react-native-voip-push-notification as the bridge. For Rork Max, we'll touch PushKit and CallKit directly through PushRegistry and CXProvider. Most of the underlying behaviors are identical — what changes is how much glue code you write yourself.

Why a Regular Push Just Won't Cut It

Before we touch any code, it's worth slowing down to understand why Apple gave us a separate API at all. Internalizing this saves you a lot of debugging later, because most of the strange constraints you'll bump into are downstream consequences of this design choice.

A standard APNs alert delivers a banner that the user has to tap before your app comes alive. Even with content-available: 1 background pushes, iOS prioritizes battery life and may delay or coalesce delivery for tens of minutes. By the time your app finally executes, the caller has already given up. PushKit, by contrast, ships a payload with apns-push-type: voip, and the moment iOS receives it, it wakes your process from a cold start in the background and gives you a chance to call CallKit's reportNewIncomingCall. CallKit then displays the lock-screen-full-bleed system UI that users associate with "real" phone calls.

There's a strict catch from iOS 13 onward: if you receive a VoIP push and don't report an incoming call to CallKit within 30 seconds, iOS will kill your app. Repeated violations make the OS quietly stop delivering VoIP pushes to that device entirely. The penalty is brutal precisely because Apple wants to disincentivize developers using VoIP push as a generic background-execution loophole. So the rule isn't optional — every PushKit handler must end at CallKit, no exceptions, on every code path including error branches.

This naturally brings up a tempting question: can you reuse VoIP push for other "must run in background" tasks, like clearing a chat queue or finishing an LLM response? Apple has been firm: VoIP push is for real-time human-to-human voice and video, period. Several large apps — including some you've heard of — have been told to refactor in review, and at least one had a temporary App Store removal as part of the negotiation. Use BGTaskScheduler or silent push for anything else, and reserve VoIP push for actual ringing phones. I'll come back to this in the App Store review section, because it's the single most common reason a Rork-based calling app gets rejected.

A useful mental model: think of VoIP push as a privilege Apple grants on the implicit promise that you'll use it for human-to-human calls only. The OS gates it heavily because it's the most expensive privilege in the entire push system in terms of battery life and background CPU. If your design starts to drift from that promise, that's a signal to pick a different mechanism.

The Layers You're Wiring Together

A production VoIP app ends up looking like this. When you build it on Rork, what makes it tricky is that you're hopping between JavaScript and native iOS code — and you need a clear mental model of which layer owns which job. Confusing the layers (e.g. trying to do CallKit reporting from JavaScript) is the most common architectural mistake I see in code reviews.

  • Signaling server: tells "user A wants to call user B" (Cloudflare Workers + Durable Objects, Supabase Realtime, custom WebSocket — your choice). It's also where you'd implement features like ringing-state expiration and auto-cancel.
  • VoIP push backend: hits APNs with apns-push-type: voip. Often the same service as your signaling server, but logically distinct.
  • PushKit (native iOS): receives the VoIP push and is responsible for handing the payload to CallKit within Apple's deadline.
  • CallKit (native iOS): shows the system call UI, manages call state, owns the audio session lifecycle.
  • WebRTC (shared): actual audio/video media transport, usually via react-native-webrtc or a vendor SDK like Daily / LiveKit / Twilio.
  • JS layer (Rork): history, in-app UI screens, business logic, analytics.

For React Native, OSS handles the bridge: react-native-callkeep wraps CXProvider, and react-native-voip-push-notification wraps PKPushRegistry. Both libraries are mature and used in production by some of the largest RN-based calling apps. For Rork Max, you'll be in Swift the whole way, which gives you finer control and avoids one extra JS bridge round trip per event. I'll show both flavors below so you can pick whichever matches your project.

A subtle point worth calling out: CallKit operations are blocking from iOS's perspective. When you call reportNewIncomingCall, iOS expects the rest of your handler to be lightweight. Heavy work — DB reads, contact lookups, network calls — should happen after CallKit has accepted the report. Mixing this up causes intermittent "the call rang but I can't answer it" bugs that are nearly impossible to reproduce in the simulator.

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
If you've been stuck because your VoIP app doesn't show a full-screen incoming call UI on the lock screen, you'll walk away with a working PushKit + CallKit pipeline you can ship today
You'll learn the entire production stack — VoIP token registration and revocation, audio session coordination with WebRTC, and answer/hangup flow on both React Native and SwiftUI
You'll be able to judge whether your app's use of VoIP push is App Store compliant, and what to swap to if Apple pushes back during review
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.

or
Unlock all articles with Membership →
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.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-06-22
Your Daily Reminder Stops Firing After a Couple of Weeks — iOS's Invisible 64-Notification Cap
When a daily reminder built with Rork (Expo) goes silent after a while, the cause is usually iOS's 64 pending-notification limit. Design a repeating calendar trigger for fixed messages and a rolling reschedule for daily-changing content, with working code that survives DST and multiple reminders.
Dev Tools2026-05-28
Tracking Down BGTaskScheduler.submit Error Code=1 (Unavailable) in Rork iOS Apps
A field-tested checklist for diagnosing BGTaskScheduler.submit failing with Error Code=1 (Unavailable) in iOS apps built with Rork, walking through the six causes that account for nearly every case.
Dev Tools2026-05-18
Fixing iOS-Only Linking.canOpenURL False Returns in Rork — The LSApplicationQueriesSchemes Trap
Production iOS builds silently route every user to the mobile web instead of opening TikTok or X? This is almost always a missing LSApplicationQueriesSchemes entry. Here is the full fix path for Rork.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →