Skip to main content

Embedded Fields — Apple Pay domain setup

Before Apple Pay buttons appear in your checkout, Apple needs to verify that the domain serving the page is one you've registered with our payments network. This is a one-time setup per domain — once the file is hosted and verified, the Apple Pay button renders automatically for every eligible buyer on that domain.

You do all of this from the Wallet Domains dashboard in your Vonpay account: add a domain, download its verification file, host the file on your server, and click Verify. No support request needed — the whole ceremony is self-serve and takes about five minutes per domain.

Google Pay does NOT need this step. Google Pay domains verify automatically — there's no file to host. The full Apple-Pay-domain-association ceremony below applies only to Apple Pay.


Why Apple requires this

Apple Pay's security model treats every checkout page as a potential phishing surface. When a buyer's device asks Apple to authorize a payment for acmehats.com, Apple double-checks that whoever requested this Apple Pay session actually owns acmehats.com. The double-check works by:

  1. The payments-processor (us via our underlying network) tells Apple: "this merchant has registered acmehats.com."
  2. Apple's verifier fetches https://acmehats.com/.well-known/apple-developer-merchantid-domain-association and reads its contents.
  3. If the file's content matches what the processor registered, Apple flips the domain to verified — Apple Pay buttons start working on that domain.

If Apple ever fails to fetch the file (we serve a 404, the TLS handshake fails, the file's content changes), Apple flips the domain back to unverified and the button stops appearing on the buyer's device. There's no email, no alert — buyers just stop seeing Apple Pay.

You don't need an Apple Developer account — Vonpay handles the Apple Pay registration with Apple on your behalf.

Google Pay's model is different. Google Pay needs no domain-association file — its domains verify automatically when you add them in the Wallet Domains dashboard. If you're integrating Google Pay only, you can skip this whole page.


When you need to set up a domain

Run the setup for every distinct domain where buyers will see the Embedded Fields card field. Examples:

  • checkout.acmehats.com — your live checkout subdomain
  • pay.acmehats.com — alternate buyer-facing path
  • staging.acmehats.com — your staging environment (separate Apple Pay registration; not the live one)
  • mirror-test.vonpay.com — Vora's hosted test sample (if you're using it instead of standing up your own)

Subdomains count as separate domains. Wildcards aren't supported by Apple Pay — each subdomain registers independently.

If you serve checkout through a third-party CDN (Cloudflare, Fastly, etc.) — Apple Pay always verifies against the domain in your buyer's browser address bar, not the CDN's domain.


Step-by-step setup

1. Download the verification file

In your Vonpay dashboard, open Settings → Wallet Domains.

  • New domain? Click Add domain, enter the hostname (e.g. checkout.acmehats.com — hostname only, no https:// and no path), choose Apple Pay, then click Add domain. The domain's detail page opens automatically.
  • Already listed? Click the domain row to open its detail page.

On the detail page, click Download verification file. Your browser saves a single binary file named exactly:

apple-developer-merchantid-domain-association

No extension. The file content is unique to your account-and-domain pair — registering the same domain under a different account produces a different file.

Each domain row shows its current status — Verified, Pending, Failed, or Revoked — and flags any domain whose annual re-check is approaching as Expiring soon.

2. Host the file on your domain

The file must be served at:

https://{your-domain}/.well-known/apple-developer-merchantid-domain-association

Path is exact and case-sensitive. Apple's verifier doesn't follow redirects to a different path or filename; doesn't accept content-encoding gzip; doesn't accept a 200 with HTML. Plain raw bytes at exactly this URL, served over HTTPS with a valid TLS cert.

Content-Type: any (application/octet-stream, text/plain, even application/json all work). Apple ignores the header and reads the bytes directly.

Framework-specific recipes

Next.js

Drop the file into public/.well-known/apple-developer-merchantid-domain-association. Next.js serves the public/ directory at the site root automatically; no config change.

Vite / Vue / Svelte

Drop the file into public/.well-known/apple-developer-merchantid-domain-association. Vite serves public/ at the site root in both dev and production builds.

Vercel

Hosting any of the framework patterns above on Vercel works out of the box. For static sites without a framework, you can also use a vercel.json rewrite:

{
"rewrites": [
{
"source": "/.well-known/apple-developer-merchantid-domain-association",
"destination": "/_static/apple-developer-merchantid-domain-association"
}
]
}

Cloudflare Pages

Drop the file at the project root path _static/.well-known/apple-developer-merchantid-domain-association (or wherever your build outputs). Cloudflare's static handler serves it directly — no edge function needed.

Cloudflare Workers

Add a route for the well-known path that returns the file content from KV or an env.ASSETS.fetch() lookup:

export default {
async fetch(req: Request, env: Env) {
if (new URL(req.url).pathname === "/.well-known/apple-developer-merchantid-domain-association") {
return new Response(APPLE_PAY_DOMAIN_FILE, {
headers: { "content-type": "application/octet-stream" },
});
}
// ...rest of your worker
},
};

Nginx

location = /.well-known/apple-developer-merchantid-domain-association {
alias /var/www/.well-known/apple-developer-merchantid-domain-association;
default_type application/octet-stream;
}

Apache

Drop the file into your document root at .well-known/apple-developer-merchantid-domain-association. Apache serves dotfile directories by default; if you've disabled that, add:

<Directory "/var/www/html/.well-known">
Require all granted
</Directory>

Express / Node

app.get("/.well-known/apple-developer-merchantid-domain-association", (req, res) => {
res.type("application/octet-stream").send(APPLE_PAY_DOMAIN_FILE);
});

Static site / CDN-only hosting

Most static-site hosts (Netlify, Surge, Render, Render-Cloudflare-R2) serve public/.well-known/ or equivalent without config. If yours doesn't, check the host's docs for "well-known files" or "dotfile directories."

3. Confirm the file is reachable

Before asking Vora to verify the domain with Apple, check that the file is live:

curl -v https://{your-domain}/.well-known/apple-developer-merchantid-domain-association

Expect:

  • HTTP 200
  • Non-empty body (the file is a few KB)
  • Valid TLS cert (not self-signed, not expired)
  • No redirects

If you see 301 / 302, 404, 403, or TLS errors, fix those first — Apple's verifier won't follow redirects or accept errors.

4. Click Verify in the dashboard

Back on the domain's detail page in Settings → Wallet Domains, click Verify. Vonpay triggers the verification call with Apple, and the page polls for the result on its own — the status badge updates automatically, usually within 30 seconds. No need to refresh or reload.

You'll land on one of:

  • ✅ Verified — Apple Pay buttons start working on the buyer's next page load. No restart needed; the SDK picks up the verification state on the next session retrieve.
  • ❌ Failed — the detail page shows Apple's reason. Common ones:
    • file_not_found — the URL returned 404 / 5xx. Re-verify reachability with the curl above.
    • content_mismatch — file content doesn't match what we registered. Re-download the file from the dashboard and re-host.
    • tls_error — Apple couldn't trust your cert chain. Likely a missing intermediate cert or expired cert.

If verification is still running when the dashboard stops checking, it shows a "still in progress" note and stops polling. Reopen the domain in Settings → Wallet Domains, or click Verify again, to pick up the result once Apple has responded.

5. Keep the file in place

Apple re-verifies on a rolling annual cycle. If the file goes missing (CI nuked the public/ directory, CDN cache purged, DNS moved to a host that doesn't serve dotfile paths), Apple flips the domain back to unverified silently — buttons stop appearing.

Vora's daily renewal worker watches for this and re-runs the verification call with Apple when the file disappears. But Apple can only verify what you serve — if the file is gone, the renewal call still fails. Treat the file as load-bearing infrastructure: pin its source in version control, include it in your deploy pipeline, don't let cleanup scripts touch it.


What can go wrong

SymptomLikely causeFix
curl returns 404File not deployed, or path mismatch (extra extension, missing dot, wrong directory)Verify exact path on disk; redeploy
curl returns 301 / 302Web server redirects all paths to https://, www., or trailing-slash versionCarve out an exemption for /.well-known/
curl returns 200 but body is your homepage HTMLSPA catch-all route is intercepting the pathAdd /.well-known/* to the framework's exclude list (Next.js middleware.ts matcher, Vite catch-all config, etc.)
Verification returns content_mismatch after a redeployCI compressed the file with gzip, or transformed it as an assetAdd /.well-known/* to your build's no-transform list
Domain flips back to unverified after working for monthsApple's annual re-check failed because the file is no longer reachableRe-host the file; Vora's renewal worker re-runs the verification automatically once the URL works
Apple Pay button still doesn't appear after verificationBrowser caching of the device-eligibility checkTest in a private Safari window; the Apple Pay availability cache clears on session end

Testing your setup

Once Apple flips the domain to verified:

  1. Use a real Apple device. Safari on macOS (with Touch ID + a paired iPhone with a card in Wallet) or iOS Safari with a card in Wallet. Chrome on Mac, Firefox, and any non-Apple device cannot render the Apple Pay button — that's an Apple platform restriction, not a VORA limit.
  2. Visit your checkout page at the registered domain.
  3. Initialize a session through the normal Embedded Fields integration — see Quickstart.
  4. Mount the card field. The Apple Pay button appears inside the card mount when the buyer is eligible — see Elements reference → Apple Pay & Google Pay.
  5. Tap the button → Apple Pay sheet opens → authenticate → elements.submit() resolves. Branch on the 3-way result union — never read result.token unconditionally:
    • result.error — submit failed before any charge.
    • result.token — a buyer was on the session, so a reusable vp_pmt_* was vaulted (with wallet: "apple_pay"); already charged on the charge-and-save flow, charge server-side on tokenize-only.
    • result.charged — a guest with no buyer on the session: a one-time charge completed and no token is returned (still carries wallet: "apple_pay").

If the button doesn't appear, check the browser console for one of:

  • frame_wallet_domain_unverified — verification didn't complete. Open the domain in Settings → Wallet Domains and click Verify again.
  • frame_wallet_unavailable — the buyer's device doesn't have a card in Apple Wallet. Add one and retry.

See Errors → Wallet errors for the full event surface.


What's next