The Certification Authority (CA) for Apple Push Notification service (APNs) is changing. APNs will update the server certificates in sandbox on January 20, 2025, and in production on February 24, 2025. All developers using APNs will need to update their application’s Trust Store to include the new server certificate: SHA-2 Root : USERTrust RSA Certification Authority certificate.
To ensure a smooth transition and avoid push notification delivery failures, please make sure that both old and new server certificates are included in the Trust Store before the cut-off date for each of your application servers that connect to sandbox and production.
At this time, you don’t need to update the APNs SSL provider certificates issued to you by Apple.
Notifications
RSS for tagLearn about the technical aspects of notification delivery on device, including notification types, priorities, and notification center management.
Post
Replies
Boosts
Views
Activity
I tried below at 2:00 PM on 21/01/2025(JST).
Apple Push Notification service server certificate update
I followed above,
a new server certificate: "SHA-2 Root : USERTrust RSA Certification Authority certificate" was added to my push server, but a certificate error occurred and push notifications could not be sent.
So I refered this article,Instead of connecting via DNS name resolution at api.development.push.apple.com,
I fixed api.development.push.apple.com to "17.188.143.34" in /etc/hosts,
I could push notifications with the new server certificate.
(I got this IP(17.188.143.34) from this airtcle)
From this, I suspect that Apple had not yet updated the APNs certificate (CA) for the Sandbox environment as of 2:00 PM on January 21, 2025 (JST).
Was the update published as scheduled?
Problem
We have successfully set up push notifications using Apple APN service, that is push notifications work when using a token generated using the JSON Web Token Generator in the Push Notification console. However, we get an "InvalidProviderToken" error when creating using our own token using the following code.
The Key and TeamID is definitely correct (obviously, censored in the below code). When pasting our token in the JSON Web Token Validator in the Push Notification console we get the error „Invalid signing key“. We merely pasted our secret key in our setNewTokenIfNeeded code, separated on four lines using the “““ style.
Does anyone know why this error happens? Given that it works when we upload our .p8 file to the JSON Web Token Generator and we simply paste the text of this file (excluding the lines with "-----BEGIN/END PRIVATE KEY-----") I guess our secret key is correct?
Code to generate token
fileprivate var currentToken: String?
fileprivate var currentTokenCreateTime: Date?
fileprivate func setNewTokenIfNeeded() {
// Ensure, token is at least 20 minutes but at most 60 minutes old
if let currentTokenCreateTime = currentTokenCreateTime {
let ageOfTokenInSeconds = abs(Int(currentTokenCreateTime.timeIntervalSinceNow))
NSLog("Age of token: \(Int(ageOfTokenInSeconds / 60)) minutes.")
if ageOfTokenInSeconds <= 20 * 60 { return }
}
// Generate new token
NSLog("Renewing token.")
let secret = """
ABCABCABCABCABCABCABCABCABCABCABCABC+ABCABC+ABCABCABC+ABCABCAB/+
ABCABCABCABCABCABCABCABCABCABCABCABC+ABCABC+ABCABCABC+ABCABCAB/+
ABCABCABCABCABCABCABCABCABCABCABCABC+ABCABC+ABCABCABC+ABCABCAB/+
ABCABCAB
"""
let privateKey = SymmetricKey(data: Data(secret.utf8))
let headerJSONData = try! JSONEncoder().encode(Header())
let headerBase64String = headerJSONData.urlSafeBase64EncodedString()
let payloadJSONData = try! JSONEncoder().encode(Payload())
let payloadBase64String = payloadJSONData.urlSafeBase64EncodedString()
let toSign = Data((headerBase64String + "." + payloadBase64String).utf8)
let signature = HMAC<SHA256>.authenticationCode(for: toSign, using: privateKey)
let signatureBase64String = Data(signature).urlSafeBase64EncodedString()
let token = [headerBase64String, payloadBase64String, signatureBase64String].joined(separator: ".")
currentToken = token
currentTokenCreateTime = Date()
}
fileprivate struct Header: Encodable {
let alg = "ES256"
let kid: String = "ABCABCABC" // Key (censored here)
}
fileprivate struct Payload: Encodable {
let iss: String = "ABCABCABC" // Team-ID (censored here)
let iat: Int = Int(Date().timeIntervalSince1970)
}
extension Data {
func urlSafeBase64EncodedString() -> String {
return base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
}
}
Code to send the push notification
func SendPushNotification(category: ConversationCategory,
conversationID: UUID,
title: String,
subTitle: String?,
body: String,
devicesToSendTo: [String]) {
// Für alle Felder s. https://developer.apple.com/documentation/usernotifications/generating-a-remote-notification
let payload = [
"aps": [
"alert": [
"title": title,
"subtitle" : subTitle ?? "",
"body": body
],
"category" : category.rawValue,
"mutable-content": 1
],
"conversationID": conversationID.uuidString
] as [String : Any]
// Ggf. Token setzen
setNewTokenIfNeeded()
guard let currentToken = currentToken else {
NSLog("Token not initialized.")
return
}
NSLog(currentToken)
// Notification an alle angegebenen Devices schicken
let bundleID = "com.TEAMID.APPNAME"
for curDeviceID in devicesToSendTo {
NSLog("Sending push notification to device with ID \(curDeviceID).")
let apnServerURL = "https://api.sandbox.push.apple.com:443/3/device/\(curDeviceID)"
var request = URLRequest(url: URL(string: apnServerURL)!)
request.httpMethod = "POST"
request.allHTTPHeaderFields = [
"authorization": "bearer " + currentToken,
"apns-id": UUID().uuidString,
"apns-topic": bundleID,
"apns-priority": "10",
"apns-expiration": "0"
]
request.httpBody = try! JSONSerialization.data(withJSONObject: payload, options: .prettyPrinted)
URLSession(configuration: .ephemeral).dataTask(with: request) { data, response, error in
if let error = error {
NSLog(error.localizedDescription)
}
if let data = data {
NSLog(String(data: data, encoding: .utf8)!)
}
}.resume()
}
}
On a similar note, some people seem to encounter this error when using the prettyPrinted option for the JSON serialization (i.e., in request.httpBody = try! JSONSerialization.data(withJSONObject: payload, options: .prettyPrinted). Could this be the culprit, given our secret key contains „/„ and „+“?
Many thanks!
i got some problem for the LiveAcitvity when i start it with notification.
The LiveActivity can not show,but it can work when i update or end a LiveActitvity;
And so,i think my configeration is right like the code;
thanks in advance
Background:
① We initiate push notification requests by generating tokens using the p8 certificate.
② The lowest version of the server we use is Ubuntu 16.04, and the image is Alpine Linux 3.15.
③ Currently, the root certificate USERTrust_RSA_Certification_Authority.pem is default in the system and has the same MD5 value as the provided download file. The time for both is 2019.
My questions:
① Which certificate should we download and add to the server's trust store, Root Certificates?
② Does the system we are using default include this certificate?
③ What operations are needed for this server certificate replacement?
Hello, it seems like starting from iOS 18.0, it is possible to entirely skip the notifications permission request alert by swiping up from the bottom of the screen. It doesn't work this way with any other kinds of system alerts, nor in iOS 17.4 (tested it in the Simulator though).
So, is it a bug? Or is it intended? Either way, I haven't found any information regarding that.
The problem with that is when you skip the alert, notifications are missing from the app preferences.
We integrated App Store Server notification, to get notified about CONSUMPTION_REQUESTS and REFUND notifications.
In the data, we noticed same transactionId have multiple REFUND decisions, usually REFUND_DECLINED and then REFUND. Why is that? Did user contact customer support ?
For the second (or later) REFUND decision, CONSUMPTION_REQUEST notifications are usually not sent, but thats not always the case. Sometimes, REFUND decision are the same. Sometimes, we get even 3 or more REFUND related notifications for same transactionId, e.g:
2024-12-02: REFUND_DECLINED
2024-12-05: REFUND_DECLINED
2024-12-12: REFUND
Do user request refund again ? Do they contact customer support ? But I can not explain why sometimes status it REFUND at first, but then later REFUND_DECLINED.
Thank you already in advance:)
Currently, our company server is using the push service by calling APNS through the p8 certificate.
In this process, our server does not have a CA certificate or SSL certificate in use.
Only the p8 certificate is installed and only performs the role of calling APNS.
If it is used like this, is there no need to update the CA certificate separately?
Or do I have to apply a new SSL certificate and add the CA to it?
Can someone help me plz?
On December 6, 2024, I received the following email.
Does this mean that there is something that needs to be done on the app side or on the Firebase side?
Currently, in our project, we are using Firebase to set up push notifications.
If anyone knows how to deal with this or has taken any action, could you tell me what specific steps you took?
Action Required: Apple Push Notification Service Server Certificate Update
As we announced in October,
the Certification Authority (CA) for Apple Push Notification service (APNs) is changing.
APNs will update the server certificates in sandbox on January 20, 2025,
and in production on February 24, 2025. To continue using APNs without interruption,
you’ll need to update your application’s Trust Store to include the new server certificate: SHA-2 Root : USERTrust RSA Certification Authority certificate.
To ensure a smooth transition and avoid push notification delivery failures,
please make sure that both old and new server certificates are included in the Trust Store before the cut-off date for each of your application servers that connect to sandbox and production.
At this time, you don’t need to update the APNs SSL provider certificates issued to you by Apple.
Hi,
My customer has acquired the assets of another company. We therefore need to transfer this company's app to my customer's account.
This app primarily functions using WebRTC video, which relies on APN PUSH notifications. I have reviewed the information about transferring an app from one developer account to another, particularly here: https://developer.apple.com/help/app-store-connect/transfer-an-app/overview-of-app-transfer. I understand that we need to generate a new SSL certificate that will be integrated into the app once it has been transferred.
Since users will not all update the app immediately, we will have two server certificates running in parallel for some time.
Is it allowed to send two notifications to the same iPhone, knowing that one of the two will systematically fail?
If not, what is the best practice for successfully migrating VOIP notifications?
Thank you in advance,
F. BABIN
After my membership was renewed (auto-payment was rejected, and my account was manually paid), my app stopped receiving notifications I thought was an invalidation by membership but after some days, my app kept not receiving notifications.
I just recreated a new key for my app, (I hope it solves the problem)
My question is, do I need to make another change to my account to reenable services?
Hi!
I am encountering an issue when attempting to send a test notification to update a live activity. The request is failing with the following error:
{
"code": 400,
"message": "bad-request",
"reason": "The device token doesn't match the specified topic.",
"requestUuid": "3ed3fc0c-9c57-4d67-8ae8-cbabe0579b10"
}
I have verified that all device tokens and app identifiers are correct, but the error persists. Could you please assist in identifying the root cause of this issue?
I am trying to add voip call functionality to my app.
It works as expected while the app is in the foreground. But in the background it does not.
I have registered the app as requiring background voip permissions.
My implementation doesn't fit into one of these posts, so here is a gist:
https://gist.github.com/BrentMifsud/4be43c022c1279f04ecb56250a86b3f1
We have just been granted access to the com.apple.developer.usernotifications.filtering entitlement, and are following the documented steps for handled E2EE VOIP notifications listed here: https://developer.apple.com/documentation/callkit/sending-end-to-end-encrypted-voip-calls
1 - A user initiates a VoIP call on their app. Their app then sends an encrypted VoIP call request to your server.
We do exactly this, Alice calls Bob from her app, sending a notification to our servers.
2 - Your server sends the encrypted data to the receiver’s device using a regular remote notification. Be sure to set the apns-push-type header field to alert.
We do exactly this, our server send on a notification to APNS with the apns-push-type header set to alert, destined for Bob.
3 - On the receiver’s device, the notification service extension processes the incoming notification and decrypts it. If it’s an incoming VoIP call, the extension calls reportNewIncomingVoIPPushPayload(_:completion:) to initiate the call. It then silences the push notification (see com.apple.developer.usernotifications.filtering).
I try to do exactly this. The notification is received by the NSE on Bob's device, which decrypts it and then notices it is a VOIP call from Alice. It prepares a dictionaryPayload with the decrypted data and then calls reportNewIncomingVoIPPushPayload(_:) async throws. This throws an NSXPCConnectionInterrupted error, which when logged shows as below:
Error Domain=NSCocoaErrorDomain Code=4097 "connection to service named com.apple.callkit.notificationserviceextension.voip" UserInfo={NSDebugDescription=connection to service named com.apple.callkit.notificationserviceextension.voip}
The only difference I can see to the documentation is that I am working in an asynchronous context so am using the asynchronous version of the method, but I don't imagine this should cause an issue?
I then supress the notification as documented and this works correctly.
Does anyone have any ideas why I am getting this error when calling reportNewIncomingVoIPPushPayload(_:) async throws?
Hello,
I have a question regarding the replacement of the APNs authentication key (.p8) in a Firebase setup for push notifications.
Currently, my app uses an APNs authentication key from the original Apple Developer account. However, we are in the process of transferring app ownership to a new Apple Developer account, which will require generating a new APNs authentication key and updating it in Firebase.
My concerns are:
Impact on Existing Device Tokens:
If we replace the existing .p8 key with a new one generated from the new developer account, will the existing APNs device tokens remain valid, or will they need to be reissued?
Push Notification Delivery:
Will Firebase still be able to send push notifications to devices that were registered with the previous APNs authentication key after the key is replaced?
Steps for a Smooth Transition:
Are there any best practices or additional steps we need to follow to ensure uninterrupted delivery of push notifications during and after the key replacement?
Any insights or guidance from the Apple Developer team or community would be greatly appreciated.
I downloaded the .crt file to take the following steps. Could you please give me a guide on how to actually apply the file to the server?
Upload only files to a specific path on the Linux server.
keytool -import -alias cacert -file ca.crt -keystore client.truststore.jks
Is it possible to just apply it with the above command?
Please confirm.
thank you
You are probably aware of the upcoming root certificate change for any servers you might have that you use to send push notifications by connection to APNs.
If you are not, here is the announcement.
We have been getting some questions about this, and understand not everyone is familiar with their server setup.
First, we would like to clarify that this is only a change to your server's certificate trust store. You do not need to update anything else, like your APNs push certificates, the build certificates and provisioning profiles for your team/app, and so on. All you need to do is to install the mentioned new root certificate to your push server's trust store.
If you are using a 3rd party push provider, it is them who will need to handle their servers. But you may want to double check with them nevertheless.
If you are managing your own push servers that connect to APNs directly, then it is your responsibility to download and install the root certificate mentioned in the above link on your server(s).
Unfortunately we cannot provide specific instructions on how to install this root certificate on every kind of server out there. Each server operating system/push server software will have different ways these root certificates are installed, which is out of scope of our support abilities.
If you are not sure how to do this, I would recommend you seek help for this from your server-side developers or server admins.
Or, if you don't have access to such resources, you can ask the support channels for your system the question: How do I install a root certificate?
We have setup a test server at 17.188.143.34:443 that you can use to try and send pushes to test whether your new root certificate is correctly installed.
An alternative way to test this would be, from a terminal prompt:
openssl s_client -connect 17.188.143.34:443 -servername api.sandbox.push.apple.com -verifyCAfile USERTrustRSACertificationAuthority.crt -showcerts
Change the parameter to the -verifyCAfile argument to point to your trust store, and it should allow you to validate
Sample return results would be:
Connecting to 17.188.143.34
CONNECTED(00000003)
depth=2 C=US, ST=New Jersey, L=Jersey City, O=The USERTRUST Network, CN=USERTrust RSA Certification Authority
verify return:1
depth=1 CN=Apple Public Server RSA CA 11 - G1, O=Apple Inc., ST=California, C=US
verify return:1
depth=0 C=US, ST=California, O=Apple Inc., CN=api.sandbox.push.apple.com
verify return:1
Argun Tekant /
DTS Engineer /
Core Technologies
I have an app available for download in the Apple App Store. The app sends local notifications, which are scheduled at the user's request once the app launches. I've recently learned that when new versions of my app are deployed and automatically update on the user's device, previously scheduled local notifications are deleted. Given my app design, the user can re-launch the app in order to re-schedule the local notifications. This is a bit of a problem, though, because part of my app's value is in reminding the user - so after requesting a local notification, the user expects to receive a local notification and then launch the app, not the other way around.
Given this, I've been exploring solutions so my app continues to function as expected (including delivering local notifications, even if the app hasn't yet been launched) after an app update. I've explored .backgroundTasks(), but they too are apparently deleted with an app update and require the app to be re-launched first to work as expected. Another solution might be to use push notifications instead of local notifications, but that seems like a very involved solution if I'm just looking to make sure that local notifications persist after an app update. I can't be the only person to have this dilemma - am I overlooking a simple solution?
I see the log in my server and they told that PTT notification sent successful but bellow function not call from my app. Could you give me any suggest?
func incomingPushResult(channelManager: PTChannelManager, channelUUID: UUID, pushPayload: [String: Any]) -> PTPushResult
We currently are checking to see if we need to make modifications to account for the new Certification Authority (CA) for APNs that will be changing soon. Since we currently use the newer token-based connection to APNs and not the certificate-based connection, would we be unaffected by this change?
I'm trying to rewrite a Swift code to Swift 6 language mode and am stuck with this problem. How do I safely pass the bestAttemptContent and contentHandler to the Task? This is from the UNNotificationServiceExtension subclass.
final class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
var customNotificationTask: Task<Void, Error>?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
guard let bestAttemptContent = bestAttemptContent else {
invokeContentHandler(with: request.content)
return
}
do {
let notificationModel = try PushNotificationUserInfo(data: request.content.userInfo)
guard let templatedImageUrl = notificationModel.templatedImageUrlString,
let imageUrl = imageUrl(from: templatedImageUrl) else {
invokeContentHandler(with: bestAttemptContent)
return
}
setupCustomNotificationTask(
imageUrl: imageUrl,
bestAttemptContent: bestAttemptContent,
contentHandler: contentHandler
)
} catch {
invokeContentHandler(with: bestAttemptContent)
}
}
// More code
private func downloadImageTask(
imageUrl: URL,
bestAttemptContent: UNMutableNotificationContent,
contentHandler: @escaping (UNNotificationContent) -> Void
) {
self.customNotificationTask = Task {
let (location, _) = try await URLSession.shared.download(from: imageUrl)
let desiredLocation = URL(fileURLWithPath: "\(location.path)\(imageUrl.lastPathComponent)")
try FileManager.default.moveItem(at: location, to: desiredLocation)
let attachment = try UNNotificationAttachment(identifier: imageUrl.absoluteString, url: desiredLocation, options: nil)
bestAttemptContent.attachments = [attachment]
contentHandler(bestAttemptContent)
}
}
}
I tried using the MainActor.run {}, but it just moved the error to that run function.
The UNNotificationRequest is not sendable, and I don't think I can make it so.
Wrap the setupCustomNotification in a Task will move the errors to the didReceive method.
It seems like the consuming keyword will help here, but it leads to a compilation error, even with the latest Xcode (16.2).
Any pointers?