implementation 2026-04-18 13 min read the implementation desk

Implementation guide: Apple Pay domain verification at scale

3-minute scan
  • 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-known path (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-17

    The 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 -v to confirm no 301/302 on the file path.
    • Subdomains: The association file on www.brand.com does NOT cover brand.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/plain explicitly and purge the edge on each change.
    • Wordfence/Sucuri: Both occasionally block the .well-known path 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.

    Found this useful? Share it X LinkedIn Reddit HN Email

    FAQ

    How long does Apple take to verify after we register?
    Usually under 30 seconds. If it takes more than 5 minutes, check that the file is fetchable from the open internet (not behind a VPN or basic auth).
    Can we use one association file across all brands?
    Only if all brands are under the same Apple merchant ID on the same parent gateway account. If you have multiple parent accounts, each has its own file.
    Does Apple Pay need re-verification every year like SSL?
    No, there is no formal expiry. But TLS cert rotations, WAF changes, and CDN changes can silently break verification, which is why the daily health check matters.
    What about Apple Pay on in-app purchases?
    Different flow — uses StoreKit, not web domain verification. This guide covers web checkout only.
    How many domains can one merchant ID cover?
    Hundreds. There is no practical limit for portfolio operators.

    Running multiple brands?
    multiflow was built for this.

    The Operator Briefing

    Twice-monthly. No fluff.

    Processor shutdowns, reserve-hold playbooks, reconciliation lessons, and the merchant-account decisions that save operators six-figure years. Delivered to your inbox — never spam.

    No spam. Unsubscribe in one click.

    We use essential cookies · Privacy