Diagnose it in five minutes
Start with the fetch itself. Browsers follow redirects and pass bot challenges, so "the URL works when I open it" is not evidence. Use curl and read what a machine sees:
curl -si https://yourdomain.com/.well-known/apple-developer-merchantid-domain-association
You want an HTTP 200 with no Location header. Any 301, 302, or 308 fails verification outright, because Apple's fetcher does not follow redirects. Then read the body and check the other hostnames:
# The body must be the file, not your app's HTML
curl -s https://yourdomain.com/.well-known/apple-developer-merchantid-domain-association | head -c 60
# Repeat both checks for every hostname that shows the button
curl -si https://www.yourdomain.com/.well-known/apple-developer-merchantid-domain-association
The body should be an opaque blob. Stripe's file, for example, is about 9 KB of hex-encoded text beginning 7B227073704964. If you see your own HTML, a catch-all rewrite is answering instead of the file. Finally, confirm the TLS chain a non-browser client sees:
openssl s_client -connect yourdomain.com:443 \
-servername yourdomain.com < /dev/null
Then match what you found:
The five causes
Which flow you are in
The fetch is identical everywhere; what differs is who generates the file and where you click register. Diagnose with the checks above, then apply the fix in your flow's console.
- Apple Developer portal, direct. You run your own merchant ID and merchant validation. Download the per-domain file from the portal, serve it at the well-known path, and click verify within 7 days. This is the flow where file expiry and the one-file-per-domain rules bite hardest.
- Stripe. You never touch the Apple portal; Stripe owns merchant validation. Download Stripe's static association file, serve it at the well-known path, then register the domain in the Dashboard or with POST /v1/payment_method_domains. The validate endpoint re-checks after you fix something, and apple_pay.status_details.error_message carries the failure reason.
- WooCommerce Stripe plugin. The plugin registers the domain through Stripe's API and serves the association file itself through a rewrite. Do not upload the file by hand. When this flow fails, the usual culprits are permalink or rewrite configuration and security plugins intercepting the well-known path.
- Square, Adyen, Braintree, PayPal. Same model, different console: each has you serve a domain association file and register the domain in their dashboard or API. Every cause on this page applies unchanged; only the registration step moves.
A trap specific to Stripe: test mode does not verify domains. Registering with a test mode key returns apple_pay: active immediately, even for a domain that does not exist. Your staging environment will happily show the button while the same configuration fails in live mode, so treat live registration as the first real test of your file.
Stripe Connect: registered on the wrong account
Apple Pay domain registration lives on the Stripe account that runs the charge. With direct charges, that is the connected account, not the platform. Register only on the platform and everything looks verified in the platform dashboard while the payment page logs:
You have not registered or verified the domain, so apple_pay is not enabled in the Express Checkout Element.
The fix is to register the domain on the connected account by passing the Stripe-Account header:
curl https://api.stripe.com/v1/payment_method_domains \
-u "sk_live_...:" \
-H "Stripe-Account: acct_..." \
-d domain_name=checkout.yourdomain.com
Destination charges and separate charges and transfers register on the platform instead, with no Stripe-Account header. Either way, register every hostname that renders a wallet button.
If verification succeeds and the button still does not render, stop re-verifying the domain; the remaining causes live in the payment page and the browser, not the registration.
- Secure context. Wallets require HTTPS. Plain HTTP disables them, including
http://localhost, so local development needs a TLS proxy or tunnel before any wallet button will paint. - Iframe permissions. A payment form inside an iframe needs the embedding page to delegate the payment capability, for Stripe.js:
Permissions-Policy: payment=(self "https://js.stripe.com"). - A wallet-capable browser. The Apple Pay button renders in Safari on a device with a card provisioned in Wallet. Other browsers on the same machine generally show their own wallet or nothing.
- Automation browsers never qualify. Headless Chrome, Playwright, Puppeteer, and CI screenshot environments have no wallet, so the express slot stays empty there no matter how correct the setup is. Test on a real device before concluding anything.
- Account and region. The wallet must be enabled for the processor account, and the card networks involved must be supported for the transaction's country and currency.
Questions, answered
How long does Apple Pay domain verification take?
Verification is a single fetch of the well-known file, not a review queue. Success normally reflects within seconds to a few minutes. A verification that sits pending for hours is a fetch that keeps failing; diagnose it as a failure rather than waiting.
Do www and the apex domain need separate verification?
Yes. Every hostname that shows an Apple Pay button is its own registration: example.com, www.example.com, and shop.example.com each need to be registered and verified, and in the Apple portal flow each needs its own file.
Why did Apple Pay stop working when nothing was deployed?
Apple re-verifies registered domains automatically, starting 30 days before your TLS certificate expires, with retries 15 and 7 days out. If the association file was deleted after the first success, or the renewed certificate changed the chain of trust, the re-check fails and Apple Pay drops off without any deploy on your side.
Can I test domain verification in Stripe test mode?
No. Registering a domain with a test mode key marks Apple Pay active immediately, even for a domain that does not exist. Test mode tells you nothing about your file, your TLS, or your CDN rules; the real fetch happens when you register the domain in live mode.
Apple Pay on Flint
Flint hosted checkout and payment links run on Flint's domain, so domain verification is Flint's job: Apple Pay and Google Pay work there with none of the setup on this page. For embedded checkout on your own domain, register the checkout hostname once with POST /v1/payment-method-domains; the response reports per-wallet readiness, and the validate endpoint re-checks after you fix the domain using the causes above. See the embedded payments guide and the API reference.
Sources