We are writing to report a recurring stability issue with the Apple Pay sandbox environment. We are using the official sandbox test cards provided on the Apple Developer website for our testing:
https://developer.apple.com/apple-pay/sandbox-testing/
We are experiencing frequent, intermittent failures when attempting to add these sandbox cards to the Wallet for testing purposes. The issue typically occurs a couple of times per day. When the failure occurs, the card provisioning process fails unexpectedly.
The issue is not limited to a single card; we have observed this behavior across all available card networks. In some instances, all cards (Visa, Mastercard, Discover, Amex) fail to provision simultaneously. At other times, the issue appears to be isolated to specific networks while others work correctly.
Crucially, the issue appears to be temporary. After some time passes (ranging from minutes to an hour), we are able to add the exact same card successfully without making any changes to our test environment or configuration.
We have diligently checked our setup to rule out configuration errors on our end. This includes verifying:
The device is set to a supported region.
We are signed in with a valid sandbox tester Apple ID.
All other prerequisites for sandbox testing are met.
The fact that the process works correctly at other times strongly suggests that this is a server-side stability issue within the Apple Pay sandbox environment rather than a persistent misconfiguration on our part.
To help with your investigation, we have attached an image that demonstrates a failed attempt to add a card.
Could you please investigate the stability of the sandbox card provisioning service? Please let us know if this is a known issue or if there is any further information we can provide.
Thank you for your time and assistance.
Apple Pay on the Web
RSS for tagApple Pay on the Web allows you to accept Apple Pay on your website using JavaScript-based APIs.
Posts under Apple Pay on the Web tag
107 Posts
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Hello,
I am currently testing an Adyen integration with Sylius and need to verify Apple Pay with Cartes Bancaires in the sandbox environment. Could you please advise how Cartes Bancaires can be tested in Apple Pay Sandbox (e.g. cards details)?
Thank you in advance for your guidance.
Best regards,
Grzegorz
MacOS: 12 ( Monterrey )
Safari: 17.6
Demo Site: https://applepaydemo.apple.com/
At the bottom where the Apple Pay button should appear, I see a warning something like "This browser doesn't support Apple Pay, please use safari" along with a link to requirements for apple pay.
All the requirements are fulfilled, OS and Safari's version are above the minimum required.
Link was opened in Safari.
And the other thing is if I open the same site in Chrome, I can see the apple pay button and when I click on it a QR appears which is the expected behaviour.
How to resolve this?
Hey,
I am trying to implement the apple pay process pay backend service,
I have checked everything and somehow it fails. I only have 1 certificate for merchant and 1 for the apple pay process, I have the private keys and try to run this following code that fails -
import crypto from 'crypto';
import fs from 'fs';
import forge from 'node-forge';
const MERCHANT_ID_FIELD_OID = '1.2.840.113635.100.6.32';
function decryptedToken()
{
const token = "";
const ephemeralPublicKey = "";
const encryptedData = "";
//===================================
// Import certs
//===================================
const epk = Buffer.from(ephemeralPublicKey, 'base64');
const merchantCert = fs.readFileSync('merchant_full.pem', 'utf8')
const paymentProcessorCert = fs.readFileSync("apple_pay_private.pem");
//===================================
let symmetricKey = '';
try {
symmetricKey = restoreSymmetricKey(epk, merchantCert, paymentProcessorCert);
} catch (err) {
throw new Error(`Restore symmetric key failed: ${err.message}`);
}
try {
//-----------------------------------
// Use the symmetric key to decrypt the value of the data key
//-----------------------------------
const decrypted = JSON.parse(decryptCiphertextFunc(symmetricKey, encryptedData));
console.log("Decrypted Token:", decrypted);
// const preppedToken = prepTabaPayToken(token, decrypted)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Send decrypted token back to frontend
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// res.send(preppedToken);
} catch (err) {
throw new Error(`Decrypt cipher data failed: ${err.message}`);
}
}
// extractMerchantID -
const extractMerchantID = (merchantCert) => {
//===================================
// Extract merchant identification from public key certificate
//===================================
try {
const info = forge.pki.certificateFromPem(merchantCert);
const result = info['extensions'].filter(d => d.id === MERCHANT_ID_FIELD_OID);
//-----------------------------------
// Return
//-----------------------------------
return result[0].value.toString().substring(2);
} catch (err) {
throw new Error(Unable to extract merchant ID from certificate: ${err});
}
}
// generateSharedSecret -
const generateSharedSecret = (merchantPrivateKey, ephemeralPublicKey) => {
//===================================
// Use private key from payment processing certificate and the ephemeral public key to generate
// the shared secret using Elliptic Curve Diffie*Hellman (ECDH)
//===================================
const privateKey = crypto.createPrivateKey({
key: merchantPrivateKey,
format: "pem",
type: "sec1", // because it's "EC PRIVATE KEY"
});
const publicKey = crypto.createPublicKey({
key: ephemeralPublicKey,
format: 'der',
type: 'spki'
});
//-----------------------------------
// Return
//-----------------------------------
return crypto.diffieHellman({privateKey,publicKey: publicKey,});
//-----------------------------------
}
// getSymmetricKey -
const getSymmetricKey = (merchantId, sharedSecret) => {
//===================================
// Get KDF_Info as defined from Apple Pay documentation
//===================================
const KDF_ALGORITHM = '\x0didaes256GCM';
const KDF_PARTY_V = Buffer.from(merchantId, 'hex').toString('binary');
const KDF_PARTY_U = 'Apple';
const KDF_INFO = KDF_ALGORITHM + KDF_PARTY_U + KDF_PARTY_V;
//-----------------------------------
// Create hash
//-----------------------------------
const hash = crypto.createHash('sha256');
hash.update(Buffer.from('000000', 'hex'));
hash.update(Buffer.from('01', 'hex'));
hash.update(Buffer.from(sharedSecret, 'hex'));
hash.update(KDF_INFO, 'binary');
//-----------------------------------
// Return
//-----------------------------------
return hash.digest('hex');
//-----------------------------------
}
// restoreSymmetricKey -
const restoreSymmetricKey = (ephemeralPublicKey, merchantCert, paymentProcessorCert) => {
//===================================
// 3.a Use the payment processor private key and the ephemeral public key, to generate the shared secret
//===================================
const sharedSecret = generateSharedSecret(paymentProcessorCert, ephemeralPublicKey);
//-----------------------------------
// 3.b Use the merchant identifier of the public key certificate and the shared secret, to derive the symmetric key
//-----------------------------------
const merchantId = extractMerchantID(merchantCert);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Return
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
console.log("Merchant ID:", merchantId);
console.log("Shared Secret (hex):", sharedSecret);
return getSymmetricKey(merchantId, sharedSecret);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
// decryptCiphertextFunc -
const decryptCiphertextFunc = (symmetricKey, encryptedData) => {
console.log("🔑 Decrypting Ciphertext with Symmetric Key:", symmetricKey);
//===================================
// Get symmetric key and initialization vector
//===================================
const buf = Buffer.from(encryptedData, 'base64');
const SYMMETRIC_KEY = Buffer.from(symmetricKey, 'hex');
const IV = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); // Initialization vector of 16 null bytes
const CIPHERTEXT = buf.slice(0, -16);
//-----------------------------------
// Create and return a Decipher object that uses the given algorithm and password (key)
//-----------------------------------
const decipher = crypto.createDecipheriv("aes-256-gcm", SYMMETRIC_KEY, IV);
const tag = buf.slice(-16, buf.length);
decipher.setAuthTag(tag);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Load encrypted token into Decipher object
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let decrypted = decipher.update(CIPHERTEXT);
console.log("🔑 Decrypted Data");
decrypted += decipher.final();
//:::::::::::::::::::::::::::::::::::
// Return
//:::::::::::::::::::::::::::::::::::
return decrypted;
//:::::::::::::::::::::::::::::::::::
}
decryptedToken();
Hi everyone,
We’ve recently run into an issue with Apple Pay on the web and would appreciate some clarification.
Background:
Previously, we integrated Apple Pay without using the Apple Pay JS SDK.
We relied on ApplePaySession.canMakePayments() to check availability, and it worked fine.
After Apple announced support for browsers beyond Safari, we switched to the Apple Pay JS SDK.
According to Apple’s documentation, we should now use applePayCapabilities() for capability checks in third-party browsers.
Our current behavior:
We implemented applePayCapabilities().
Initially, it was returning either paymentCredentialStatusUnknown or paymentCredentialsUnavailable.
Based on those values, we displayed the Apple Pay button.
The problem:
About a week ago, on the same device/browser, applePayCapabilities() started returning applePayUnsupported.
Setup: MacBook Pro 13-inch (M1, 2020), Google Chrome Version 136.0.7103.93.
The Apple documentation says: “Don’t show an Apple Pay button or offer Apple Pay” when the result is applePayUnsupported.
However, at the same time, canMakePayments() is returning true.
This creates a direct conflict between the two recommendations:
canMakePayments() → true ⇒ show the button.
applePayCapabilities() → applePayUnsupported ⇒ don’t show the button.
Question:
What’s the correct approach here?
Should we prioritize applePayCapabilities() and hide the button, or is it acceptable to continue relying only on canMakePayments() as the source of truth for showing Apple Pay?
Any insights from others who’ve run into this contradiction would be very helpful.
Thanks in advance!
Hello,
We are experiencing a consistent delay when initiating Apple Pay sessions using the https://apple-pay-gateway.apple.com/paymentservices/startSession endpoint. Below is a detailed overview of our setup and the issue.
Setup
Our web service is hosted in AWS and there is a proxy server between our web service and Apple servers.
We are passing the correct domain in the initiativeContext field of the startSession request.
The .well-known/apple-developer-merchantid-domain-association file is hosted on a different domain, which is also correctly configured and associated with our merchant ID in the Apple Developer portal.
Observed Behavior
When the same request is made from a local development environment, Apple responds immediately (under 1 second).
When the request is made from our AWS-hosted service, Apple responds with a valid session, but only after a consistent ~15-second delay.
The content and response are otherwise identical — only the timing differs.
We would appreciate any insights or suggestions from others who have faced similar behavior or from the Apple Pay team.
Thank you in advance!
Hi,
We’re implementing Apple Pay on the Web for a multi-tenant platform via a PSP. The PSP operates multiple HSM/clusters and gave us multiple CSRs, asking us to register all.
Our understanding: a Merchant ID can hold several PPCs over time, but only one is active at once.
Questions
Is there any supported way to keep more than one PPC active simultaneously for the same Merchant ID?
If not, what does Apple recommend for web-only, multi-tenant setups: a single MID with PSP-side decryption & sub-merchant separation, or separate MIDs per brand/region?
Any official guidance on PPC rotation and handling many domains for Apple Pay on the Web?
links to official docs or prior Apple responses would be appreciated.
We are experiencing intermittent 403 Forbidden errors during Apple Pay on web merchant validation in our production and sandbox environment.
Has anyone else started seeing 403 Forbidden errors recently (since mid-2025)?
Why would merchant validation be sometimes successful and sometimes fail with 403?
Could this be related to new Apple Pay gateway changes or stricter validation rules?
Any additional debug steps or permanent solutions we should try?
Thank you.
We are an acquirer/payment provider offering Apple Pay. Our merchants use our hosted checkout to accept payments. After a user pays with Apple Pay on our checkout, the Wallet transaction record shows our checkout domain as the payee. We would like it to display the merchant’s brand/name so users can recognize or contact the merchant.
Is there any parameter or configuration that controls what Wallet shows as the payee? For example, can this be set via a specific field/parameter, or is it strictly derived from the Merchant ID’s display name (or other Apple Pay configuration)? What is the correct approach for a PSP/acquirer to have the merchant’s brand shown in Wallet transaction record?
Additional detail: The field in question is the merchant/payee name shown in the Apple Wallet receipt—directly under the transaction amount at the top of the receipt, and again beneath the “Total” line.
{
"epochTimestamp": 1755169981033,
"expiresAt": 1755173581033,
"merchantSessionIdentifier": "SSH4ADF1D97A60B47FC8537037BE9892237_FF777A9CB5E9EDAB38A01E4EDF71CB5572F19153853DAC70ADC5AA3E75877CB4",
"nonce": "b6f1e016",
"merchantIdentifier": "7C52E6BFA112124092008236BE1EE49791E4E82E9082AD9AC98D55B03A088120",
"domainName": "1960-ikffk.checkout.trypeppr.com",
"displayName": "peppr",
"signature": "308006092a864886f70d010702a0803080020101310d300b0609608648016503040201308006092a864886f70d0107010000a080308203ee30820394a00302010202080e7210e510586e34300a06082a8648ce3d040302307a312e302c06035504030c254170706c65204170706c69636174696f6e20496e746567726174696f6e204341202d20473331263024060355040b0c1d4170706c652043657274696669636174696f6e20417574686f7269747931133011060355040a0c0a4170706c6520496e632e310b3009060355040613025553301e170d3231303131303032313632395a170d3236303130393032313632395a306b3131302f06035504030c286563632d736d702d62726f6b65722d7369676e5f5543342d50524f445f4b727970746f6e5f45434331143012060355040b0c0b694f532053797374656d7331133011060355040a0c0a4170706c6520496e632e310b30090603550406130255533059301306072a8648ce3d020106082a8648ce3d0301070342000466e0ea0e787dcb3f66bc533189da2bda08ed9574e421117aa1af2cc310f6a8b19ca3e77ed00fa84e8df2ac8688e529866e76ebad89eda5b7c336e0f0d8a7d05da38202113082020d300c0603551d130101ff04023000301f0603551d2304183016801423f249c44f93e4ef27e6c4f6286c3fa2bbfd2e4b304506082b0601050507010104393037303506082b060105050730018629687474703a2f2f6f6373702e6170706c652e636f6d2f6f63737030342d6170706c65616963613330323082011d0603551d2004820114308201103082010c06092a864886f7636405013081fe3081c306082b060105050702023081b60c81b352656c69616e6365206f6e207468697320636572746966696361746520627920616e7920706172747920617373756d657320616363657074616e6365206f6620746865207468656e206170706c696361626c65207374616e64617264207465726d7320616e6420636f6e646974696f6e73206f66207573652c20636572746966696361746520706f6c69637920616e642063657274696669636174696f6e2070726163746963652073746174656d656e74732e303606082b06010505070201162a687474703a2f2f7777772e6170706c652e636f6d2f6365727469666963617465617574686f726974792f30340603551d1f042d302b3029a027a0258623687474703a2f2f63726c2e6170706c652e636f6d2f6170706c6561696361332e63726c301d0603551d0e0416041457c735942abd9ea2feccd3cbe7ede0a37c8cc5fa300e0603551d0f0101ff040403020780300f06092a864886f76364061d04020500300a06082a8648ce3d0403020348003045022100f2fa622622128cd1e1642084bc4117ccdede7289690e864cfb88abb43e04338e022065f85a90b82711d1fd762e0b59c45496e9e683c265c8279998e37872feae46ec308202ee30820275a0030201020208496d2fbf3a98da97300a06082a8648ce3d0403023067311b301906035504030c124170706c6520526f6f74204341202d20473331263024060355040b0c1d4170706c652043657274696669636174696f6e20417574686f7269747931133011060355040a0c0a4170706c6520496e632e310b3009060355040613025553301e170d3134303530363233343633305a170d3239303530363233343633305a307a312e302c06035504030c254170706c65204170706c69636174696f6e20496e746567726174696f6e204341202d20473331263024060355040b0c1d4170706c652043657274696669636174696f6e20417574686f7269747931133011060355040a0c0a4170706c6520496e632e310b30090603550406130255533059301306072a8648ce3d020106082a8648ce3d03010703420004f017118419d76485d51a5e25810776e880a2efde7bae4de08dfc4b93e13356d5665b35ae22d097760d224e7bba08fd7617ce88cb76bb6670bec8e82984ff5445a381f73081f4304606082b06010505070101043a3038303606082b06010505073001862a687474703a2f2f6f6373702e6170706c652e636f6d2f6f63737030342d6170706c65726f6f7463616733301d0603551d0e0416041423f249c44f93e4ef27e6c4f6286c3fa2bbfd2e4b300f0603551d130101ff040530030101ff301f0603551d23041830168014bbb0dea15833889aa48a99debebdebafdacb24ab30370603551d1f0430302e302ca02aa0288626687474703a2f2f63726c2e6170706c652e636f6d2f6170706c65726f6f74636167332e63726c300e0603551d0f0101ff0404030201063010060a2a864886f7636406020e04020500300a06082a8648ce3d040302036700306402303acf7283511699b186fb35c356ca62bff417edd90f754da28ebef19c815e42b789f898f79b599f98d5410d8f9de9c2fe0230322dd54421b0a305776c5df3383b9067fd177c2c216d964fc6726982126f54f87a7d1b99cb9b0989216106990f09921d00003182018930820185020101308186307a312e302c06035504030c254170706c65204170706c69636174696f6e20496e746567726174696f6e204341202d20473331263024060355040b0c1d4170706c652043657274696669636174696f6e20417574686f7269747931133011060355040a0c0a4170706c6520496e632e310b300906035504061302555302080e7210e510586e34300b0609608648016503040201a08193301806092a864886f70d010903310b06092a864886f70d010701301c06092a864886f70d010905310f170d3235303831343131313330315a302806092a864886f70d010934311b3019300b0609608648016503040201a10a06082a8648ce3d040302302f06092a864886f70d010904312204209378ff57580c3205e9ea38d985a2e9ca2db7f06db29b7560f585561a23894402300a06082a8648ce3d04030204483046022100fad47e840779070d097ef91cd4bfa5381d77426071cb38c1cdc77ff9460ba1470221009215c246893bff0983052caaae610a16117237e73ab36d859008e7b234670eaa000000000000",
"operationalAnalyticsIdentifier": "peppr:7C52E6BFA112124092008236BE1EE49791E4E82E9082AD9AC98D55B03A088120",
"retries": 0,
"pspId": "7C52E6BFA112124092008236BE1EE49791E4E82E9082AD9AC98D55B03A088120"
}
This is generated in the onvalidatemerchant event handler, and passed into session.completeMerchantValidation.
Using a sandbox account with linked cards, the next thing that happens is a "payment not completed" message in the ApplePay popup on the page, and the oncancel event is hit
Inspecting the event, I don't see anything that hints at the issue. There is a sessionError object, but its code is "unknown" and the info object is empty.
We would appreciate it if you could provide the detailed flow of recurring payments and the unsubscribe process using the Apple Pay JS API, specifically in cases where the PSP decrypts the Apple Pay token before sending it to the payment gateway.
Thank you very much for your kind assistance.
We would like to confirm the unsubscribe flow related to recurring payment processing.
When a user unsubscribes, does your system send any notification to us?
If no notification is provided, we will not be able to detect the unsubscribe event and will continue to send recurring payment requests to the gateway periodically. Would this cause any issues?
We would appreciate it if you could share the specific unsubscribe flow with us.
Thank you in advance for your support.
Hello,
We are experiencing an issue with Apple Pay integration in our application. We are using WKWebView to handle various payment methods, but we are unable to complete payments via Apple Pay.
Upon debugging the WKWebView, we received the following error message: "400 No required SSL certificate was sent" when attempting to process the payment.
Currently, we are using a Let's Encrypt SSL certificate. Could you please confirm whether this certificate is suitable for Apple Pay, or if we should be using a different SSL certificate?
Topic:
App & System Services
SubTopic:
Apple Pay
Tags:
Apple Pay on the Web
Apple Pay
Tap to Pay on iPhone
To perform the integration, it must be done under the same domain that has been validated. Is it not possible to do it in a local environment?
Could that be the reason why I can't display the button or complete the validation with the API?
Hi, I’ve been trying to integrate Apple Pay, but for some reason, the payment button is not showing up.
The project is built with Laravel 11 and Vue. I imported the script as follows:
<script crossorigin crossorigin
src="https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js"
></script>
Then I added the following the steps:
<style>
apple-pay-button {{
--apple-pay-button-width: --apple-pay-button-width: 150px;;
--apple-pay-button-height: --apple-pay-button-height: 30px;;
--apple-pay-button-border-radius: --apple-pay-button-border-radius: 3px;;
--apple-pay-button-padding: --apple-pay-button-padding: 0px 0px;;
--apple-pay-button-box-sizing: border-box;
}
</style>
<apple-pay-button buttonstyle="black" type="plain" locale="en-US"></apple-pay-button>
I followed all the steps from the official Apple Pay demo:
https://applepaydemo.apple.com/
I also configured the Content Security Policy (CSP) to allow all necessary resources. However, when I test my integration, the button doesn’t appear. I’ve checked the console, but there are no errors.
At the same time, I have my certificate imported into the Keychain, and I’ve completed the entire process of creating both the certificate and the private key. However, when I try to validate the session using the certificate and key with Apple’s API, I get an error:
400 The SSL certificate error
https://apple-pay-gateway-cert.apple.com/paymentservices/
We have verified our domain but if the file is removed from the deployed site after verification will this impact using ApplePaySession.applePayCapabilities in real time?
We use that method from the JS api in our React app to determine whether or not to show the apple pay button.
When that function is called in the browser, do the apple servers ping the https://our.domain.com/.well-known/apple-developer-merchantid-domain-association URL at that time?
Or do they check for it periodically?
The reason for asking is that with our many environments we wonder if we can verify each environment's domain by adding the file once. The file will be wiped out by our CICD process as it goes up the environment stack through our development workflow.
Or do we need to maintain that file for each environment and add something to our build process?
Hello,
On my website, I have a button to make a payment via Apple Pay. When I click on it, the Touch ID window opens correctly. However, when I place my finger on the Touch ID, I get a payment error.
This issue only occurs in production mode. In sandbox mode, everything works perfectly.
Here is a log file :
log.txt
Thank you in advance for your help.
I am trying to play around on the Apple Pay demo page (https://applepaydemo.apple.com) and I am getting the following error response.
PaymentRequest AbortError: The operation was aborted.
I am using the Payment Request API
Hello -- We're preparing to roll out Apple Pay on website in the next week but encountered some issues during testing.
While we successfully processed transactions using a VISA card, we ran into errors when testing with other card brands. Has anyone come across this issue before?
I have a question regarding the file apple-developer-merchantid-domain-association.txt.
I understand that this file is used during API access for Apple Pay Web payments. However, is it necessary for our company to access this file during the payment process?
Also, this domain validation file is expected to be placed in the publicly accessible “.well-known” folder on our web server. Is it acceptable for this file to remain readable by third parties on the Internet, including Apple’s servers, without posing any security risks?
Since this file is generated during domain registration on the Apple Developer site and is unique to our domain, we believe there should be no security concerns even if accessed by third parties. However, are there any specific security requirements for this domain validation file?
Please note that the domain validation has already been successfully completed.
We appreciate your time and look forward to your guidance.
Best regards,