Implementation guide: Apple Pay domain verification at scale
- Apple Pay domain verification is a once-per-domain file upload plus an API registration call. The failure mode on portfolios is not the initial registration — it is silent de-registration when domains drift (TLS expires, WAF blocks the path, file gets deleted).
- Automate both sides: file placement via Infrastructure-as-Code, registration via API, and verification via a daily health check that alerts before a domain silently fails.
- For 30 domains, plan 6–8 hours of initial wiring, 15 minutes per new domain after that, and a weekly monitoring review.
On this page
One domain is easy: upload a file, click a button, done. Thirty domains with monthly new-brand launches plus cert renewals plus CDN changes plus occasional WAF rule updates is a scale problem. Every operator we have talked to who runs 10+ domains has had at least one silent Apple Pay outage where a brand's Apple Pay button stopped appearing and nobody noticed for 2–6 weeks. This guide shows the automation pattern that eliminates silent outage.
1. What Apple Pay verification actually checks
Every time an Apple device renders an Apple Pay button on your checkout, the device fetches https://yourdomain.com/.well-known/apple-developer-merchantid-domain-association and compares the file contents against a hash stored at Apple. If any of these conditions fail, the button silently does not render:
- File is missing (404) or wrong content-type.
- TLS certificate is invalid or self-signed.
- HTTP redirects the
.well-knownpath (Apple does not follow redirects). - WAF returns 403/429 to Apple's IP range.
- File contents have drifted from the registered hash.
None of these produce a user-visible error. The button just does not appear.
2. Domain inventory
Build a single source of truth for every domain that serves checkout. One row per domain:
domains.yml:
- brand: peptides-a
domain: peptidesa.com
platform: woocommerce
file_source: acquirer_account_1
registered_at: 2026-01-15
last_verified: 2026-04-17
- brand: peptides-b
domain: peptidesb.com
platform: shopify_headless
file_source: acquirer_account_1
registered_at: 2026-02-03
last_verified: 2026-04-17The file_source column matters. Each Apple merchant ID generates its own domain association file. If you have multiple parent accounts, each account has a distinct file, and a brand registered against account 1 cannot serve the file from account 2.
3. File placement automation
For WordPress: a must-use plugin that serves the file from a central location. This lets you update the file across 30 WordPress sites with one code push.
// wp-content/mu-plugins/apple-pay-domain.php
add_action('init', function() {
if (strpos($_SERVER['REQUEST_URI'], '/.well-known/apple-developer-merchantid-domain-association') !== false) {
$file = WP_CONTENT_DIR . '/apple-pay/' . get_current_blog_id() . '.txt';
if (!file_exists($file)) $file = WP_CONTENT_DIR . '/apple-pay/default.txt';
header('Content-Type: text/plain');
header('Cache-Control: public, max-age=300');
readfile($file);
exit;
}
});For Next.js / Remix headless: place the file in public/.well-known/apple-developer-merchantid-domain-association. It ships with every deploy.
For static-site platforms: use a _headers file (Netlify) or custom route (Vercel) to force text/plain content-type.
4. Bulk registration via API
Stripe's API (and equivalent parent-gateway APIs) exposes Apple Pay domain registration as a POST endpoint. A bulk-register script:
// bulk-register.js
const domains = require('./domains.yml');
for (const d of domains) {
try {
const result = await gateway.applePayDomains.create({ domain_name: d.domain });
console.log(`Registered ${d.domain}: ${result.id}`);
} catch (e) {
console.error(`Failed ${d.domain}: ${e.message}`);
}
}Run this once per domain batch. The API call triggers Apple's verification crawl within seconds; a success response means the file was fetched and matched.
5. The daily health check
This is the part most teams skip, and it is the single most valuable automation in the portfolio. Once per day, hit every domain's file URL and compare the response against the expected hash:
// health-check.js (runs daily via cron)
for (const d of domains) {
const res = await fetch(`https://${d.domain}/.well-known/apple-developer-merchantid-domain-association`);
const body = await res.text();
const hash = crypto.createHash('sha256').update(body).digest('hex');
const expected = EXPECTED_HASH[d.file_source];
if (hash !== expected) {
await alert.send(`Apple Pay domain drift: ${d.domain} — got hash ${hash.slice(0,8)}, expected ${expected.slice(0,8)}`);
}
if (!res.ok) {
await alert.send(`Apple Pay file unreachable: ${d.domain} — HTTP ${res.status}`);
}
}Route the alert to Slack or PagerDuty. The first time you run this you will find one or two domains already broken — that is normal, fix them and move on.
6. CI/CD gating
Add a pre-deploy check to every brand's deploy pipeline: if the Apple Pay file is not present at the expected path, fail the deploy. This catches the developer-deletes-the-file class of outage.
# .github/workflows/deploy.yml
- name: Verify Apple Pay file
run: |
if ! curl -sf "https://staging.${BRAND}.com/.well-known/apple-developer-merchantid-domain-association" > /tmp/file; then
echo "Apple Pay file missing on staging" && exit 1
fi
expected_hash=$(cat ./apple-pay-expected.txt | sha256sum | cut -d" " -f1)
actual_hash=$(sha256sum /tmp/file | cut -d" " -f1)
[ "$expected_hash" = "$actual_hash" ] || { echo "Hash mismatch" && exit 1; }7. Renewal and re-registration
Apple does not explicitly expire domain registrations, but the TLS certificate on the domain does. When Let's Encrypt renews at day 60, the cert changes, and some WAFs briefly block Apple's crawler during the cert handshake. Build a weekly re-verification job that calls gateway.applePayDomains.list() and logs which domains are still "verified" per the gateway's cached status. Anything that drops out, re-register.
8. Cross-platform file distribution
For portfolios mixing WordPress, Shopify, and Next.js, maintain the file in one git repo and push it to each platform via the platform's native deploy path. Never let the file live in only one place — if the git repo is the source of truth, every platform reads from the same hash.
9. Gotchas
- Cloudflare "Always Use HTTPS": If this is misconfigured it can interfere with Apple's verification. Test with
curl -vto confirm no 301/302 on the file path. - Subdomains: The association file on
www.brand.comdoes NOT coverbrand.com. Register both apex and www separately. - CDN edge cache: Some CDNs cache the file incorrectly with the wrong content-type. Set
Content-Type: text/plainexplicitly and purge the edge on each change. - Wordfence/Sucuri: Both occasionally block the
.well-knownpath as "suspicious". Whitelist the path in both. - Multiple Apple merchant IDs: If your parent account has multiple Apple merchant IDs, you get multiple distinct association files. Track which file each domain serves; serving the wrong file silently fails.
10. What success looks like
A 30-domain portfolio with: one git repo of Apple Pay files, one daily health check that reports green, one weekly re-registration job, and one CI gate per deploy. Total ops time: 10 minutes per week of reviewing the health-check log, plus 15 minutes whenever a new brand launches. No more silent outages.
Want us to stand this up for you? Start with the 12-question intake — we will return a domain inventory audit within 48 hours showing which of your current domains are at risk. Or check pricing and how the parent account handles Apple Pay across brands.