App Store Server Notifications

RSS for tag

Monitor subscription events in real time with server notifications from the App Store using App Store Server Notifications.

Posts under App Store Server Notifications tag

83 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

Clarification on offerIdentifier Behavior in TransactionPayload and Upgrade Scenarios
Hello everyone, I have some questions regarding the behavior of the offerIdentifier property in the TransactionPayload from App Store Server Notifications. When a user redeems an Offer Code, the offerIdentifier field is populated with the respective identifier. However, I am unsure how this field behaves in different scenarios, and I would appreciate any insights or clarification: Does the offerIdentifier persist throughout the subscription lifecycle (from the initial purchase to expiration)? Does it become null once the Offer Code benefits expire? Is it only present at the time of purchase and omitted in subsequent notifications? Additionally, I would like to understand the behavior of the offerIdentifier in the following scenario: A user purchases a lower-tier subscription using an Offer Code. Later, they upgrade to a higher-tier plan, causing the Offer Code benefits to effectively expire. What happens to the offerIdentifier in the transaction for the upgrade? Will it still appear in transactions after the upgrade, or will it be null? I couldn't find explicit details about these situations in the official documentation, so I hope someone here might have experience or knowledge to share. Thank you in advance for your help!
0
0
66
4d
IAP refund decision changed
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:)
0
0
49
5d
Example requests for Apple Server Notifications V2
Dear Apple Support, We are currently in the process of migrating from Apple Server Notifications v1 to v2. As you know, once we switch from v1 to v2 in App Store Connect, this change is irreversible. Our team needs to ensure that our backend environment, developed in Python with Django and using the app-store-server-library==1.5.0, is fully prepared to handle all possible notification types before making that final switch. While we can receive a basic test notification from Apple, it doesn’t provide the range of data or scenarios (e.g., INITIAL_BUY, RENEWAL, GRACE_PERIOD, etc.) that our code must be able to process, store in our database, and respond to accordingly. Without comprehensive, example-rich raw notification data, we are left guessing the structure and full scope of incoming v2 notifications. Could you please provide us with a set of raw, fully representative notification payloads for every notification type supported in v2? With these examples, we can simulate the entire lifecycle of subscription events, verify our logic, and confidently complete our migration. Thank you very much for your assistance.
1
0
66
5d
Receiving an inconsistent notification of type DID_CHANGE_RENEWAL_STATUS
I encountered a scenario involving a subscription and need to determine if it's a problem or an expected outcome. Here are the details: My service received a notification from Apple of type DID_CHANGE_RENEWAL_STATUS with subtype AUTO_RENEW_DISABLED. The status field received on the payload was equal to 1 - Active. (2024-12-19T15:34:53.801) My service again received a DID_CHANGE_RENEWAL_STATUS with subtype AUTO_RENEW_DISABLED. But the status field received was 2 - Expired. (2024-12-19T23:34:57.527) My service received an EXPIRED with subtype VOLUNTARY notification. (2024-12-19T23:35:01.669) Is the event 2 an inconsistent event? Since we are receiving a notification that means the auto renew was disabled when the subscription was already expired.
0
0
131
2w
Email from app_notification@apple.com (“DPLA”) violation
Hello everyone, I recently received an email from Apple titled "Notification of Apple Developer Program License Agreement (“DPLA”) violation:" stating that I may have violated Section 11.2 (Termination) of the agreement. The specific clause mentioned relates to engaging in or encouraging misleading, fraudulent, improper, or dishonest acts, such as manipulating App Store rankings or reviews. I’m unsure what triggered this notification as I have always adhered to Apple’s guidelines. Recently, I ran a promotional campaign for my app, where it was made temporarily free, leading to a significant spike in downloads. I suspect this may have raised a flag, but everything was done transparently and without intent to violate any rules. I’ve reached out to Apple Support for clarification, but I wanted to ask here if anyone has experienced something similar or could provide insights into resolving this. I value my membership in the Apple Developer Program and want to ensure everything is compliant. Any advice would be greatly appreciated!
0
1
232
4w
Official document for CONSUMPTION_REQUEST - What kind of data we are receiving?
This documentation describes what kind of data we should be sending to Apple server, once we are receiving CONSUMPTION_REQUEST https://developer.apple.com/documentation/appstoreserverapi/consumptionrequest But, it doesn't describe what kind of data we are receiving, when we are receiving CONSUMPTION_REQUEST? May I know, is such a document available? Thank you.
0
0
228
Dec ’24
App Store Server Notifications and API Client - Toggling Sandbox vs Production Environment
The documentation mentions the following: Verify your receipt first with the production URL; then verify with the sandbox URL if you receive a 21007 status code. This approach ensures you don’t have to switch between URLs while your app is in testing, in review by App Review, or live in the App Store. This way, you can use one server environment to handle both Sandbox and Production environments. It is necessary to pass App Review. However, I'm not manually hitting these URLs - I'm using Apple's libraries. Specifically, the environment is used in SignedDataVerifier and AppStoreServerAPIClient. (I can't link to these because, for some reason, the domain apple.github.io is not allowed. The documentation for these is only found there. You can find it quickly by searching these terms and the domain.) Here is how SignedDataVerifier is being used: const verifier = new SignedDataVerifier( appleRootCertificates, APPLE_ENABLE_ONLINE_CHECKS, APPLE_ENVIRONMENT, APPLE_BUNDLE_ID, APPLE_APP_ID ) const verifiedNotification: ResponseBodyV2DecodedPayload = await verifier.verifyAndDecodeNotification(signedPayload) if (!verifiedNotification) { // Failure return } Here is how AppStoreServerAPIClient is being used: const appStoreServerAPIClient = new AppStoreServerAPIClient( SIGNING_KEY, APPLE_IAP_KEY_ID, APPLE_IAP_ISSUER_ID, APPLE_BUNDLE_ID, APPLE_ENVIRONMENT ) const statusResponse: StatusResponse = await appStoreServerAPIClient.getAllSubscriptionStatuses(originalTransactionId, [Status.ACTIVE]) In the source code for SignedDataVerifier.verifyAndDecodeNotification, I can see that it throws a VerificationException(VerificationStatus.INVALID_ENVIRONMENT) error . So for SignedDataVerifier is it as simple as wrapping my code in a try/catch and checking that the error's status code is 21007? I'm unsure about this because if you scroll to the bottom of the linked source code file, you can see the enumeration VerificationStatus, but it's unclear if this member has a value of 21007. The source code for AppStoreServerAPIClient only says that it throws an APIException if a response could not be processed, so I'm not too sure about how to handle this one.
1
0
255
Dec ’24
Receive notifications for unreported tokens after few minutes
i m integrate App Store Server Notifications for External Purchase Server API The documentation says: For external purchase tokens that are unreported after 10 days, Apple sends a server notification to your server https://developer.apple.com/documentation/externalpurchaseserverapi#Receive-notifications-for-unreported-tokens I have a doubt. After I'm generating an external purchase token (sandbox), apple notify me 4 times in next 2-3h, even they say, they will notify me after 10 days for unreported tokens. Mention: Response for notification call was 200 token_create: 13:18 notification_1: 13:58 notification_2: 14:38 notification_3: 15:08 notificaiton_4: 15:38 "notificationType": "EXTERNAL_PURCHASE_TOKEN", "subtype": "UNREPORTED", I have no clue what's happening
1
0
194
Dec ’24
Does Firebase Push Notification Service Need New Certificates Due to APNs Update
Hi, With the upcoming changes to the Apple Push Notification service (APNs) server certificates — including the SHA-2 Root: USERTrust RSA Certification Authority certificate update — I wanted to clarify if we need to take any action with Firebase Cloud Messaging (FCM). Since we’re using FCM to send push notifications to iOS devices, does Firebase also need to update its server certificates in response to these changes, or will Firebase handle the updates automatically? We understand that Apple recommends updating our Trust Store to include the new certificates for APNs, but we’re unsure if any action is needed on our end for FCM specifically. Thanks in advance for the clarification!
4
2
4.3k
Dec ’24
How to Decode In-App Purchase Base64-Encoded Receipt to Extract Transaction ID Without Using the Deprecated verifyReceipt Endpoint?
I need to decode a Base64-encoded receipt from an In-App Purchase and extract the transaction ID from it. However, I want to avoid using the verifyReceipt endpoint since it is deprecated. Can anyone guide me on how to achieve this manually? Are there any specific libraries, steps, or code samples I should follow to parse the receipt data and retrieve the transaction ID? Any help or insights would be greatly appreciated!
0
0
236
Dec ’24
Rollback to App Store Server Notifications V1
Is it possible to switch back to V1 if we find some issues? in sandbox and production. I've found conflicting answers, here is the list of answers and when were they posted: yes (not sure if only in sandbox mode), dec 2022 -> https://developer.apple.com/forums/thread/722115 no, aug 2024 -> https://forums.developer.apple.com/forums/thread/761272 yes, nov 2024 https://developer.apple.com/documentation/technotes/tn3180-reverting-app-store-server-notifications-v1 What is the right one? Can we get v2 notifications without enabling it? Just by using this api https://developer.apple.com/documentation/appstoreserverapi/get_notification_history Thanks for your time
1
0
203
Dec ’24
Error calling Test Notification endpoint
I'm trying to call the Test Notification endpoint: https://developer.apple.com/documentation/appstoreserverapi/request_a_test_notification?language=objc I'm creating a JWT bearer token using a script I run on the console with these 2 commands: npm install jsonwebtoken fs and node generate_jwt.js Keys are taken from App Store Connect -> User and Access -> Integrations -> Keys -> In-App Purchase. generate_jwt.js script: const jwt = require('jsonwebtoken'); const fs = require('fs'); const keyId = 'MY_KEY_ID'; const issuerId = 'MY_ISSUER_ID'; const privateKey = `-----BEGIN PRIVATE KEY----- MY_SECRET_KEY -----END PRIVATE KEY-----`; // Define JWT headers and claims const token = jwt.sign({}, privateKey, { algorithm: 'ES256', expiresIn: '1h', issuer: issuerId, header: { alg: 'ES256', kid: keyId, }, audience: 'appstoreconnect-v1' }); console.log('JWT Token:', token); When I run a POST https://api.storekit.itunes.apple.com/inApps/v1/notifications/test request on postman with Bearer token authentication, I get a 401 Unauthorized error.
0
0
204
Nov ’24
Storekit2 not providing a stable experience in sandbox mode
Hello, About a month ago me and my team allocated a sprint to migrate the App Store Server Notifications service and the StoreKit library from v1 to v2. Once the implementation was completed, we started testing using the sandbox environment (which was supposed to provide greater stability and maturity in v2). The testing session went well, we managed to test various scenarios without noticing any issue (successful payment, failed payment, renewal etc). This week, before doing the production deployment, a final testing session was scheduled. Out of nowhere, we started to encounter all kinds of strange behaviours such as: StoreKit2 error (in Xcode) Received error that does not have a corresponding StoreKit Error: Error Domain=ASDErrorDomain Code=825 "No transactions in response" UserInfo={NSDebugDescription=No transactions in response} Purchase did not return a transaction: Error Domain=ASDErrorDomain Code=825 "No transactions in response" UserInfo={NSDebugDescription=No transactions in response} In some cases the transaction id resulting from the payment was retuned "" or nil, causing our server to raise an exception. Newly created sandbox account that was raising an error that says "there is already a subscription made on this apple id". Delays in the payment processing Today, without doing any code changes, we managed to test all the scenarios without encountering any issue. Due to the oscillating experience we had in the sandbox environment, we are considering postponing the release and maintaining StoreKit 1 for now, but even in this case, the fact that App Store Connect does not offer the possibility of rolling back the Notification Service to v1, causes inconvenience. Has anyone encountered something similar? Is migrating to v2 a good choice?
1
2
536
Oct ’24
Unexpected App Store Server Notification offerIdentifier
My server receives App Store Server Notification v2 notifications. Recently, it has been receiving notifications for offer codes under offers that have been deactivated. The custom code value may be the same in the active offer as the deactivated offer. When a user redeems the custom offer code for a deactivated offer, the notification payload's offerIdentifier value now contains the UUID associated with that offer in the URL in App Store Connect (like https://appstoreconnect.apple.com/apps/my-app-id/distribution/subscriptions/my-subscription-id/pricing/offer-codes/offer-uuid) The notification payload contains: {"offerIdentifier": "offer-uuid"} instead of {"offerIdentifier": "<My offer identifier from App Store Connect>"} Is it possible for a deactivated offer to be redeemed? If not, is this a known issue? We need the actual offer identifier to understand which offers users are redeeming. Is this replacement with a UUID a known issue?
2
0
343
Oct ’24
Can't receive in app purchase notification in production. But it's fine on TestFlight.
Hello everyone, We have an app that implements in-app purchases, and we're using the notification settings outlined in this Apple Developer guide. Everything is functioning correctly in TestFlight, and our server successfully receives notifications from Apple. However, after releasing the app to production, we're no longer receiving these notifications, while the TestFlight version continues to work as expected. Has anyone encountered a similar issue or have any suggestions on how to resolve this? Thank you for your help!
1
1
233
Oct ’24
keep getting 401 Unauthorized error when fetching Apple's public keys.
I keep getting 401 Unauthorized error when fetching Apple's public keys. In [14]: print(f"Error fetching public keys: {response.status_code} {response.text}") Error fetching public keys: 401 Unauthenticated I've verified that the Key ID, Issuer ID, and private key file are all correct, with the private key having admin access. The server time is correctly set to UTC. Given this, I can't identify what might be causing the issue. Any insights? def generate_apple_developer_token(): # Load the private key in PEM format with open(PRIVATE_KEY_FILE, 'rb') as key_file: private_key = serialization.load_pem_private_key( key_file.read(), password=None, backend=default_backend() ) # JWT header headers = { "alg": "ES256", "kid": KEY_ID, "typ": "JWT" } # JWT payload payload = { "iss": ISSUER_ID, "iat": int(datetime.utcnow().timestamp()), "exp": int((datetime.utcnow() + timedelta(minutes=10)).timestamp()), "aud": "appstoreconnect-v1", } # Encode the header and payload as base64 header_base64 = base64.urlsafe_b64encode(json.dumps(headers).encode()).decode().rstrip("=") payload_base64 = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=") # Concatenate header and payload message = f"{header_base64}.{payload_base64}".encode() # Sign the message using ECDSA with SHA256 signature = private_key.sign( message, ec.ECDSA(hashes.SHA256()) ) # Convert the DER-encoded signature to raw format (r and s concatenated) der_to_raw_ecdsa_format = lambda der: der[4:36] + der[-32:] # Convert the signature to raw format (64 bytes) signature_64 = der_to_raw_ecdsa_format(signature) # Base64 URL-encode the signature signature_base64 = base64.urlsafe_b64encode(signature_64).decode().rstrip("=") # Concatenate header, payload, and signature to form the JWT jwt_token = f"{header_base64}.{payload_base64}.{signature_base64}" return jwt_token def get_apple_public_keys(): try: # Generate a fresh JWT developer_token = generate_apple_developer_token() # Set up headers with the authorization token headers = { "Authorization": f"Bearer {developer_token}" } # Fetch the public keys from Apple response = requests.get('https://api.storekit.itunes.apple.com/in-app-purchase/publicKeys', headers=headers) # Log the response if it's not successful if response.status_code != 200: print(f"Error fetching public keys: {response.status_code} {response.text}") response.raise_for_status() # Raises an exception for 4xx/5xx errors # Parse and return the public keys response_data = response.json() keys = response_data.get('keys') if not keys: print("No 'keys' found in the response from Apple.") return [] return keys except requests.exceptions.RequestException as e: print(f"Error fetching Apple's public keys: {e}") return []
1
0
392
Oct ’24