Team-scoped keys introduce the ability to restrict your token authentication keys to either development or production environments. Topic-specific keys in addition to environment isolation allow you to associate each key with a specific Bundle ID streamlining key management.
For detailed instructions on accessing these features, read our updated documentation on establishing a token-based connection to APNs.
Delve into the world of built-in app and system services available to developers. Discuss leveraging these services to enhance your app's functionality and user experience.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Hello, experts!
I'm working on a VOIP application that handles audio calls and integrates with CallKit. The problem occurs when attempting to redial a previously made audio call from the system's call history. When I try to handle the NSUserActivity in the application(_:continue:restorationHandler:) method, it intercepts the INStartAudioCallIntent instead of the expected INStartCallIntent.
Background
Deprecation Warnings: I'm encountering deprecation warnings when using INStartAudioCallIntent and INStartVideoCallIntent:
'INStartAudioCallIntent' was deprecated in iOS 13.0: INStartAudioCallIntent is deprecated. Please adopt INStartCallIntent instead.
'INStartVideoCallIntent' was deprecated in iOS 13.0: INStartVideoCallIntent is deprecated. Please adopt INStartCallIntent instead.
As a result, I need to migrate to INStartCallIntent instead, but the issue is that when trying to redial a call from the system’s call history, INStartAudioCallIntent is still being triggered.
Working with Deprecated Intents: If I use INStartAudioCallIntent or INStartVideoCallIntent, everything works as expected, but I want to adopt INStartCallIntent to align with the current iOS recommendations.
Configuration:
CXProvider Configuration: The CXProvider is configured as follows:
let configuration = CXProviderConfiguration()
configuration.supportsVideo = true
configuration.maximumCallsPerCallGroup = 1
configuration.maximumCallGroups = 1
configuration.supportedHandleTypes = [.generic]
configuration.iconTemplateImageData = UIImage(asset: .callKitLogo)?.pngData()
let provider = CXProvider(configuration: configuration)
Outgoing Call Handle: When making an outgoing call, the CXHandle is created like this:
let handle = CXHandle(type: .generic, value: callId)
Info.plist Configuration: In the info.plist, the following key is defined:
<key>NSUserActivityTypes</key>
<array>
<string>INStartCallIntent</string>
</array>
Problem:
When trying to redial the audio call from the system's call history, the NSUserActivity received in the application(_:continue:restorationHandler:) method is an instance of INStartAudioCallIntent instead of INStartCallIntent. This happens even though INStartCallIntent is listed in NSUserActivityTypes in the info.plist and I want to migrate to the newer intent as recommended in iOS 13+.
Device:
iPhone 13 mini
iOS version 17.6.1
Topic:
App & System Services
SubTopic:
Automation & Scripting
Tags:
Foundation
CallKit
Intents
App Intents
Hello,
I am unable to figure out how I tell the FamilyActivityPicker whether it should show apps installed on my personal device (to be used with AuthorizationCenter.shared.requestAuthorization(for: .individual)) or apps installed on my child’s device (authenticated their phone via AuthorizationCenter.shared.requestAuthorization(for: .child)).
Is there any parameter or SwiftUI modifier I need to apply?
Otherwise, how does the user or the app know which token belongs to them and which token belongs to their child’s device?
Radar: FB17020977
Thanks a lot for your help!
If I run an app with a message filter extension, it's triggered for all the prepaid unknown numbers and its not triggered for all the unknown postpaid numbers. Any idea, how to trigger for postpaid unknown numbers?.
I tested it on the app I work with and others I use and the notification message is not appearing when using sleep mode
Iphone: 13 mini
IOS: 18.4.1
i have codes looks like:
import UIKit
import LiveCommunicationKit
@available(iOS 17.4, *)
class LiveCallKit: NSObject, ConversationManagerDelegate {
@available(iOS 17.4, *)
func conversationManager(_ manager: ConversationManager, conversationChanged conversation: Conversation) {
}
@available(iOS 17.4, *)
func conversationManagerDidBegin(_ manager: ConversationManager) {
}
@available(iOS 17.4, *)
func conversationManagerDidReset(_ manager: ConversationManager) {
}
@available(iOS 17.4, *)
func conversationManager(_ manager: ConversationManager, perform action: ConversationAction) {
}
@available(iOS 17.4, *)
func conversationManager(_ manager: ConversationManager, timedOutPerforming action: ConversationAction) {
}
@available(iOS 17.4, *)
func conversationManager(_ manager: ConversationManager, didActivate audioSession: AVAudioSession) {
}
@available(iOS 17.4, *)
func conversationManager(_ manager: ConversationManager, didDeactivate audioSession: AVAudioSession) {
}
@objc public enum InterfaceKind : Int, Sendable, Codable, Hashable {
/// 拒绝/挂断
case reject
/// 接听.
case answer
}
var sessoin: ConversationManager
var callId: UUID
var completionHandler: ((_ actionType: InterfaceKind,_ payload: [AnyHashable : Any]) -> Void)?
var payload: [AnyHashable : Any]?
@objc init(icon: UIImage!) {
let data:Data = icon.pngData()!;
let cfg: ConversationManager.Configuration = ConversationManager.Configuration(ringtoneName: "ring.mp3",
iconTemplateImageData: data,
maximumConversationGroups: 1,
maximumConversationsPerConversationGroup: 1,
includesConversationInRecents: false,
supportsVideo: false,
supportedHandleTypes: Set([Handle.Kind.generic]))
self.sessoin = ConversationManager(configuration: cfg)
self.callId = UUID()
super.init()
self.sessoin.delegate = self
}
@objc func toIncoming(_ payload: [AnyHashable : Any], displayName: String,actBlock: @escaping(_ actionType: InterfaceKind,_ payload: [AnyHashable : Any])->Void) async {
self.completionHandler = actBlock
do {
self.payload = payload
self.callId = UUID()
var update = Conversation.Update(members: [Handle(type: .generic, value: displayName, displayName: displayName)])
let actNumber = Handle(type: .generic, value: displayName, displayName: displayName)
update.activeRemoteMembers = Set([actNumber])
update.localMember = Handle(type: .generic, value: displayName, displayName: displayName);
update.capabilities = [ .playingTones ];
try await self.sessoin.reportNewIncomingConversation(uuid: self.callId, update: update)
try await Task.sleep(nanoseconds: 2000000000);
} catch {
}
}
}
i want to listen the button event,but i can't find the solutions!please give me a code demo
I'm using NERelayManager to set Relay configuration which all works perfectly fine.
I then do a curl with the included domain and while I see QUIC connection succeeds with relay server and H3 request goes to the server, the connection gets abruptly closed by the client with "Software caused connection abort".
Console has this information:
default 09:43:04.459517-0700 curl nw_flow_connected [C1.1.1 192.168.4.197:4433 in_progress socket-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, ipv6, dns, uses wifi)] Transport protocol connected (quic)
default 09:43:04.459901-0700 curl [C1.1.1 192.168.4.197:4433 in_progress socket-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, ipv6, dns, uses wifi)] event: flow:finish_transport @0.131s
default 09:43:04.460745-0700 curl nw_flow_connected [C1.1.1 192.168.4.197:4433 in_progress socket-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, ipv6, dns, uses wifi)] Joined protocol connected (http3)
default 09:43:04.461049-0700 curl [C1.1.1 192.168.4.197:4433 in_progress socket-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, ipv6, dns, uses wifi)] event: flow:finish_transport @0.133s
default 09:43:04.465115-0700 curl [C2 E47A3A0C-7275-4F6B-AEDF-59077ABAE34B 192.168.4.197:4433 quic, multipath service: 1, tls, definite, attribution: developer] cancel
default 09:43:04.465238-0700 curl [C2 E47A3A0C-7275-4F6B-AEDF-59077ABAE34B 192.168.4.197:4433 quic, multipath service: 1, tls, definite, attribution: developer] cancelled
[C2 FCB1CFD1-4BF9-4E37-810E-81265D141087 192.168.4.139:53898<->192.168.4.197:4433]
Connected Path: satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, ipv6, dns, uses wifi
Duration: 0.121s, QUIC @0.000s took 0.000s, TLS 1.3 took 0.111s
bytes in/out: 2880/4322, packets in/out: 4/8, rtt: 0.074s, retransmitted bytes: 0, out-of-order bytes: 0
ecn packets sent/acked/marked/lost: 3/1/0/0
default 09:43:04.465975-0700 curl nw_flow_disconnected [C2 192.168.4.197:4433 cancelled multipath-socket-flow ((null))] Output protocol disconnected
default 09:43:04.469189-0700 curl nw_endpoint_proxy_receive_report [C1.1 IPv4#124bdc4d:80 in_progress proxy (satisfied (Path is satisfied), interface: en0[802.11], ipv4, ipv6, dns, proxy, uses wifi)] Privacy proxy failed with error 53 ([C1.1.1] masque Proxy: http://192.168.4.197:4433)
default 09:43:04.469289-0700 curl [C1.1.1 192.168.4.197:4433 failed socket-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, ipv6, dns, uses wifi)] event: flow:failed_connect @0.141s, error Software caused connection abort
Relay server otherwise works fine with our QUIC MASQUE clients but not with built-in macOS MASQUE client. Anything I'm missing?
The stuff I've found by searching has confused me, so hopefully someone can help simplify it for me?
I have an app (I use it for logging which books I've given away), and I could either add a bunch of things to the app, or I could have another app (possibly a CLI tool) to generate some reports I'd like.
Hi all,
I am using SwiftData and cloudkit and I am having an extremely persistent bug.
I am building an education section on a app that's populated with lessons via a local JSON file. I don't need this lesson data to sync to cloudkit as the lessons are static, just need them imported into swiftdata so I've tried to use the modelcontainer like this:
static func createSharedModelContainer() -> ModelContainer {
// --- Define Model Groups ---
let localOnlyModels: [any PersistentModel.Type] = [
Lesson.self, MiniLesson.self,
Quiz.self, Question.self
]
let cloudKitSyncModels: [any PersistentModel.Type] = [
User.self, DailyTip.self, UserSubscription.self,
UserEducationProgress.self // User progress syncs
]
However, what happens is that I still get Lesson and MiniLesson record types on cloudkit and for some reason as well, whenever I update the data models or delete and reinstall the app on simulator, the lessons duplicate (what seems to happen is that a set of lessons comes from the JSON file as it should), and then 1-2 seconds later, an older set of lessons gets synced from cloudkit.
I can delete the old set of lessons if I just delete the lessons and mini lessons record types, but if I update the data model again, this error reccurrs.
Sorry, I don't know if I managed to explain this well but essentially I just want to stop the lessons and minilessons from being uploaded to cloudkit as I think this will fix the problem. Am I doing something wrong with the code?
It seems like find my mac is able to fetch the device location even if the device is in locked state. Why can’t other apps do the same.
For important background information, read Extra-ordinary Networking before reading this.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
On Host Names
I commonly see questions like How do I get the device’s host name? This question doesn’t make sense without more context. Apple systems have a variety of things that you might consider to be the host name:
The user-assigned device name — This is a user-visible value, for example, Guy Smiley. People set this in Settings > General > About > Name.
The local host name — This is a DNS name used by Bonjour, for example, guy-smiley.local. By default this is algorithmically derived from the user-assigned device name. On macOS, people can override this in Settings > General > Sharing > Local hostname.
The reverse DNS name associated with the various IP addresses assigned to the device’s various network interfaces
That last one is pretty much useless. You can’t get a single host name because there isn’t a single IP address. For more on that, see Don’t Try to Get the Device’s IP Address.
The other two have well-defined answers, although those answers vary by platform. I’ll talk more about that below.
Before getting to that, however, let’s look at the big picture.
Big Picture
The use cases for the user-assigned device name are pretty clear. I rarely see folks confused about that.
Another use case for this stuff is that you’ve started a server and you want to tell the user how to connect to it. I discuss this in detail in Showing Connection Information in an iOS Server.
However, most folks who run into problems like this do so because they’re suffering from one of the following misconceptions:
The device has a DNS name.
Its DNS name is unique.
Its DNS name doesn’t change.
Its DNS name is in some way useful for networking.
Some of these may be true in some specific circumstances, but none of them are true in all circumstances.
These issues are not unique to Apple platforms — if you look at the Posix spec for gethostname, it says nothing about DNS! — but folks tend to notice these problems more on Apple platforms because Apple devices are often deployed to highly dynamic network environments.
So, before you start using the APIs discussed in this post, think carefully about your assumptions.
And if you actually do want to work with DNS, there are two cases to consider:
If you’re looking for the local host name, use the APIs discussed above.
In other cases, it’s likely that the APIs in this post will not be helpful and you’d be better off focusing on DNS APIs [1].
[1] The API I recommend for this is DNS-SD. See the DNS section in TN3151 Choosing the right networking API.
macOS
To get the user-assigned device name, call the SCDynamicStoreCopyComputerName(_:_:) function. For example:
let userAssignedDeviceName = SCDynamicStoreCopyComputerName(nil, nil) as String?
To get the local host name, call the SCDynamicStoreCopyLocalHostName(_:) function. For example:
let localHostName = SCDynamicStoreCopyLocalHostName(nil) as String?
IMPORTANT This returns just the name label. To form a local host name, append .local..
Both routines return an optional result; code defensively!
If you’re displaying these values to the user, use the System Configuration framework dynamic store notification mechanism to keep your UI up to date.
iOS and Friends
On iOS, iPadOS, tvOS, and visionOS, get the user-assigned device name from the name property on UIDevice.
IMPORTANT Access to this is now restricted. For more on that, see the documentation for the com.apple.developer.device-information.user-assigned-device-name entitlement.
There is no direct mechanism to get the local host name.
Other APIs
There are a wide variety of other APIs that purport to return the host name. These include:
gethostname
The name property on NSHost [1]
The hostName property on NSProcessInfo (ProcessInfo in Swift)
These are problematic for a number of reasons:
They have a complex implementation that makes it hard to predict what value you’ll get back.
They might end up trying to infer the host name from the network environment.
The existing behaviour is hard to change due to compatibility concerns.
Some of them are marked as to-be-deprecated.
IMPORTANT The second issue is particularly problematic, because it involves synchronous DNS requests [2]. That’s slow in general. Worse yet, if the network environment is restricted in some way, these calls can be very slow, taking about 30 seconds to time out.
Given these problems, it’s generally best to avoid calling these routines at all.
[1] It also has a names property, which is a little closer to reality but still not particularly useful.
[2] Actually, that’s not true for gethostname. Rather, that call just returns whatever was last set by sethostname. This is always fast. The System Configuration framework infrastructure calls sethostname to update the host name as the system state changes.
Hello,
We are trying to implement Actionable Notifications on iOS via Remote Notifications.
According to Apple’s official documentation (Declaring Your Actionable Notification Types),
it is recommended to register notification categories at launch time.
However, in our use case, the number of buttons and their actions in the Actionable Notification are determined at the time of the Remote Notification request.
This means that we cannot predefine the categories at app launch but need to dynamically configure them based on the payload of the Remote Notification.
Our Approach
We are considering setting aps.mutable-content = 1 and using Notification Service Extension to modify the categoryIdentifier dynamically.
Below is the JSON payload we plan to use for Remote Notifications:
{
"aps": {
"alert": {
"title": "New Message Received!",
"body": "Check out the details."
},
"category": "DYNAMIC_CATEGORY",
"mutable-content": 1
},
"categoryData": {
"id": "DYNAMIC_CATEGORY",
"actions": [
{
"id": "REPLY_ACTION",
"title": "Reply",
"options": ["foreground"]
},
{
"id": "DELETE_ACTION",
"title": "Delete",
"options": ["destructive"]
}
]
}
}
Questions:
Can we dynamically configure Actionable Notifications based on the Remote Notification payload?
If we set categoryIdentifier in Notification Service Extension’s didReceive(_:withContentHandler:), will users still see the correct action buttons even if the app is terminated?
What is the recommended approach to dynamically configure Actionable Notifications at the time of receiving the Remote Notification, rather than at app launch?
I’m trying to build a CRUD app using SwiftData, @Query model and multidatepicker.
The data from a multidatepicker is stored or persists in SwiftData as Set = [].
My current dilemma is how to use SwiftData and @Query model Predicate to find all records on the current date.
I can’t find any SwiftData documentation or examples @Query using Set = [].
My CRUD app should retrieve all records for the current date. Unfortunately, I don’t know the correct @Query model syntax for Set = [].
I have implemented a Live Activity that includes two buttons. Currently, both buttons utilize deep links to open the main application, where I then detect the URL to perform the corresponding action.
My primary question is:
Is it possible to update a button's title and/or color within a Live Activity without requiring the main application to open?
Topic:
App & System Services
SubTopic:
Widgets & Live Activities
Hi Guys,
I am having an issue verifying a card when it is pending verification in the Apple Watch Wallet App and the iPhone Wallet.
When the user verifies the card in the wallets, they are redirected to verification in my APP. However, the problem is that I don't know which application is calling, whether it is the Apple Watch or the iPhone, because the URL sends me the same serialNumber from the PKPASS. It is impossible to know if the user wants to verify and activate the card on the watch or the iPhone.
Because I only receive the following information in the URL:
myapp://app-url?
passTypeldentifier=paymentpass.com.apple&action =verify&serialNumber=*****
The serialNumber is the same from the iPhone Wallet and the Watch Wallet.
func application(_ app: UIApplication, open url: URL, options:
[UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool
{
let source = options[.sourceApplication]
I try to retrieve the source, but it comes back null. It would be the only way to know the originating App.
Can someone help me solve this problem?
Topic:
App & System Services
SubTopic:
Apple Pay
Guys has anyone here used the PlayVideoIntent protocol while implementing app intents?
If yes can you please walk me though what purpose it solves and what features and functionality I can unlock with it?
Link to apple's documentation -> https://developer.apple.com/documentation/appintents/playvideointent
Topic:
App & System Services
SubTopic:
Automation & Scripting
Tags:
Video
App Intents
Apple Intelligence
We are integrating the Microsoft Web Chat Control inside a WKWebView in our iOS application. The microphone button (used for speech input) works as expected when the app is active. However, we are facing an issue when the app is sent to the background and then brought back to the foreground.
Issue Details:
When the app returns from background to foreground:
🔹 The mic button becomes unresponsive (taps are not recognized).
🔹 No permission prompt or speech functionality is triggered.
🔹 Other elements of the Web Chat control continue to work fine.
This issue seems isolated to iOS and WKWebView usage.
We have verified that microphone permissions are granted and there are no system-level blocks.
Environment:
Platform: iOS
Web Container: WKWebView
Microsoft Web Chat Version: Devanshu Kinariwala add version here
iOS Version: iOS 18.3.1
Devices: iPhone 13, iPhone 14 Pro
error in browser console
[Error] A MediaStreamTrack ended due to a capture failure (x3)
[Error] WebSocket connection to 'wss://directline.botframework.com/v3/directline/conversations/JQ1k0phVogeJ30ZQddBvAQ-in/stream?watermark=-&t=eyJhbGciOiJSUzI1NiIsImtpZCI6ImlmOEs0aFg4R1hXVnZkS3pwdFRFWFJveURTUSIsIng1dCI6ImlmOEs0aFg4R1hXVnZkS3pwdFRFWFJveURTUSIsInR5cCI6IkpXVCJ9.eyJib3QiOiJhaWFhcy1xYS1jb252YWktYm90Iiwic2l0ZSI6InRWcW14cDBQZU9vIiwiY29udiI6IkpRMWswcGhWb2dlSjMwWlFkZEJ2QVEtaW4iLCJuYmYiOjE3NDI5NzE1MTgsImV4cCI6MTc0Mjk3MTU3OCwiaXNzIjoiaHR0cHM6Ly9kaXJlY3RsaW5lLmJvdGZyYW1ld29yay5jb20vIiwiYXVkIjoiaHR0cHM6Ly9kaXJlY3RsaW5lLmJvdGZyYW1ld29yay5jb20vIn0.Mx3MMVP3t9Ex36UW-YARskZLny0iORxc6-B0ewvNp0S-ivUjvOS43kZc0J5HoOgYRkoGaKemo00_JSkzryAbKKoSwqMjahf0VotqTZsJjoIgtyNJFfAYyGVriBHMV_6FfH_YEezDMD5puY6R89eM-atQOw-CfoClwrxn8jgVL5Kn19WdDZvmQwFIArklA7as8bboKcWv4PveEKptM9xCokttaGzv-S5pdbNETMoJzIhLcJDHmEVJ6oJ0TFs5XS7RGMSQlM_gs95TySzVjVL7XV6qEOt_A10lRzmx0PxPIUw_nqllEIbWFy5H7AfsxbKRtM1nLe4lRm1KS7_xw9dSlw' failed: The operation couldn’t be completed. Software caused connection abort
[Error] WebSocket connection to 'wss://eastus2.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?language=en-US&format=detailed&Ocp-Apim-Subscription-Key=4JuKqIMwLMhgAxfORVDEYfiuTL6Hrbnj3isAeGfs7aks4AOltun6JQQJ99AKACHYHv6XJ3w3AAAYACOGJFYj&X-ConnectionId=A93B097C62F14C55B30A851798609F73' failed: The operation couldn’t be completed. Software caused connection abort
[Error] WebSocket connection to 'wss://eastus2.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?language=en-US&format=detailed&Ocp-Apim-Subscription-Key=4JuKqIMwLMhgAxfORVDEYfiuTL6Hrbnj3isAeGfs7aks4AOltun6JQQJ99AKACHYHv6XJ3w3AAAYACOGJFYj&X-ConnectionId=E99DF3A6CE734E0294A5FB5296D725CC' failed: The operation couldn’t be completed. Software caused connection abort
[Error] WebSocket connection to 'wss://eastus2.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?language=en-US&format=detailed&Ocp-Apim-Subscription-Key=4JuKqIMwLMhgAxfORVDEYfiuTL6Hrbnj3isAeGfs7aks4AOltun6JQQJ99AKACHYHv6XJ3w3AAAYACOGJFYj&X-ConnectionId=8B3370005E7A4946BEA174E804F64FF7' failed: The operation couldn’t be completed. Software caused connection abort
Swift: // Method to check microphone permission
func checkMicrophonePermission(completion: @escaping (Bool) -> Void) {
let authorizationStatus = AVCaptureDevice.authorizationStatus(for: .audio)
switch authorizationStatus {
case .authorized:
// Permission granted
completion(true)
case .notDetermined:
// Request permission
AVCaptureDevice.requestAccess(for: .audio) { granted in
DispatchQueue.main.async {
completion(granted)
}
}
case .denied, .restricted:
// Permission denied or restricted
completion(false)
@unknown default:
// Handle future cases
completion(false)
}
}
Topic:
App & System Services
SubTopic:
Hardware
在watchOS11.5下,Apple watch无法加载天气。无论是否连接自己的iPhone均无法加载
Howdy,
I'm trying to figure out how to replicate the following behavior for our app:
The system is able to ascertain that the Mac equivalent of some iOS app is installed locally, and it prevents notifications from being mirrored. However, I am unable to determine how this association is inferred. When I check our iOS app under this prefpane, the switch remains enabled and toggleable—we'd like to act like Slack here.
My initial assumption is that an app group containing both the Mac and iOS apps can be used to create the association; however, I would like to confirm that this is indeed the case before doing so. I'm not terribly confident about this.
Details:
The bundle identifiers of both apps do not match. This also applies to Slack; its iOS app is com.tinyspeck.chatlyio while its Mac app is com.tinyspeck.slackmacgap.
In our case, the iOS app's identifier is like com.company.app while the Mac app's identifier is com.company.app.desktop.
Both apps are signed with certificates that have matching team identifiers. The com.apple.developer.team-identifier entitlement is present on the Mac app.
The Mac app shares a keychain access group with the iOS app.
The Mac app is not sandboxed.
The Mac app is an Electron app.
The Mac app does not use APNs. It sends notifications "locally".
I currently only have the iOS app installed on my iPhone via TestFlight, if that matters.
Notification mirroring does work, but we'd like to forcibly disable this by associating the apps together.
To my knowledge, the iOS app makes use of both a UNNotificationServiceExtension and a UNNotificationContentExtension.
The iOS app currently doesn't have an assigned category (at least in Xcode). The Mac app is currently miscategorized as a developer tool (LSApplicationCategoryType = "public.app-category.developer-tools";), but that should be fixed.
(Redacted) bundle information for the Mac app:
CFBundleDisplayName = App;
CFBundleExecutable = "App Desktop";
CFBundleName = App;
Note that our CFBundleExecutable differs from the bundle's display name/name because we're currently migrating our users to a new version of the app that they'd likely want to live alongside the new one. The filename of the bundle itself is, similarly, App Desktop.app.
For the iOS app, to my knowledge, the CFBundleName and CFBundleDisplayName are App.
We are currently integrating In-App Purchases for our app and have configured App Store Server Notifications (v2) in the Sandbox environment.
During testing, we observed the following issue:
When a transaction is cancelled, declined, or pending (e.g., Ask to Buy flows or authorization pending),
No App Store Server Notification is sent to our webhook endpoint.
We only receive webhook events where the status is "purchased".
This becomes a critical problem for us because our backend must accurately track transaction states including failed and pending purchases, especially for wallet top-up use cases.
Additionally, we tried mocking failed transactions (via Xcode local environment and turning off In-App Purchases from Developer Settings) to simulate a technical failure scenario.
Even in these cases, no webhook notification was received when the purchase failed server-side.
Is it expected behavior in Sandbox that only successful transactions ("purchased") trigger webhooks?
Are failed or pending transactions suppressed in Sandbox intentionally?
Will webhook behavior be different in Production (i.e., will we receive webhook notifications for failures there)?
Is there any extra configuration or entitlement needed to fully test failure scenarios via webhooks in Sandbox?
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
In-App Purchase
App Store Server Notifications
Is CallKit still not available in certain countries? like China? If it is, is there a way to get a list of countries?