Embedded Fields — Apple Pay domain setup
Before Apple Pay buttons appear in your checkout, Apple verifies that the domain serving the page is one registered with our payments network — a one-time setup per domain, self-serve from Settings → Wallet Domains in your Vonpay dashboard: add the domain, download its verification file, host the file, click Verify. About five minutes per domain; no Apple Developer account needed — Vonpay holds the Apple Pay registration on your behalf. Google Pay needs none of this — its domains verify automatically when you add them.
Apple's verifier fetches https://{your-domain}/.well-known/apple-developer-merchantid-domain-association and compares its bytes with what we registered. If that fetch ever fails — a 404, a TLS problem, changed content — Apple flips the domain back to unverified with no email or alert: buyers simply stop seeing the button.
When you need to set up a domain
Every distinct hostname where buyers will see the card field — checkout.acmehats.com, pay.acmehats.com, staging.acmehats.com (a separate registration from live). Subdomains are separate domains and Apple Pay supports no wildcards. Behind a CDN, Apple verifies the domain in the buyer's address bar, not the CDN's.
Step-by-step setup
1. Download the verification file
In Settings → Wallet Domains, click Add domain, enter the hostname (no https://, no path), choose Apple Pay, then Add domain — or click an existing domain row. On the detail page click Download verification file; your browser saves a single binary file named exactly apple-developer-merchantid-domain-association, no extension, unique to your account-and-domain pair.
Each domain row shows Verified, Pending, Failed or Revoked, and flags a domain whose annual re-check is approaching as Expiring soon.
2. Host the file on your domain
Serve it at:
https://{your-domain}/.well-known/apple-developer-merchantid-domain-association
The path is exact and case-sensitive. Apple's verifier does not follow redirects, does not accept gzip content-encoding, and does not accept a 200 with HTML — plain raw bytes at exactly this URL over HTTPS with a valid certificate. Any Content-Type works; Apple reads the bytes.
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
curl -v https://{your-domain}/.well-known/apple-developer-merchantid-domain-association
Expect HTTP 200, a non-empty body (a few KB), a valid certificate, and no redirects. Fix any 301 / 302, 404, 403 or TLS error first.
4. Click Verify in the dashboard
On the domain's detail page click Verify. Vonpay triggers the verification call with Apple and the page polls for the result, usually within 30 seconds:
- Verified — Apple Pay buttons start working on the buyer's next page load; the SDK picks up the state on the next session retrieve.
- Failed — the page shows why, in one of four ways: we couldn't confirm your domain (re-host the file at the exact path and click Verify again — re-run the curl above first), a temporary issue interrupted verification (try again in a moment), or this wallet isn't enabled / set up on your account yet (contact support).
If the page stops polling while verification is still running, reopen the domain or click Verify again to pick up the result.
5. Keep the file in place
Apple re-verifies on a rolling annual cycle, and a missing file (a cleanup script, a purged CDN cache, a host that does not serve dotfile paths) flips the domain back to unverified silently. Our daily renewal worker re-runs the verification when the file disappears, but it can only verify what you serve — pin the file in version control and in your deploy pipeline.
What can go wrong
| Symptom | Likely cause | Fix |
|---|---|---|
curl returns 404 | File not deployed, or path mismatch (extra extension, missing dot, wrong directory) | Verify exact path on disk; redeploy |
curl returns 301 / 302 | Web server redirects all paths to https://, www., or trailing-slash version | Carve out an exemption for /.well-known/ |
curl returns 200 but body is your homepage HTML | SPA catch-all route is intercepting the path | Add /.well-known/* to the framework's exclude list (Next.js middleware.ts matcher, Vite catch-all config, etc.) |
| Verification fails again after a redeploy that had worked | CI compressed the file with gzip, or transformed it as an asset | Add /.well-known/* to your build's no-transform list |
| Domain flips back to unverified after working for months | Apple's annual re-check failed because the file is no longer reachable | Re-host the file; Vora's renewal worker re-runs the verification automatically once the URL works |
| Apple Pay button still doesn't appear after verification | Browser caching of the device-eligibility check | Test in a private Safari window; the Apple Pay availability cache clears on session end |
Testing your setup
Once the domain is Verified, use a real Apple device — Safari on macOS (Touch ID, or a paired iPhone with a card in Wallet) or iOS Safari with a card in Wallet; no other browser can render the button. Visit your checkout at the registered domain, create a session through the normal integration (Quickstart) and mount the card field: the Apple Pay button appears inside the card mount when the buyer is eligible (eligibility model).
Tap the button, authenticate in the sheet, and elements.submit() resolves with the same two arms as a card in embed mode — charged: true for a guest, or token for a buyer on the session. On a charge-and-save session the tap has already moved the money: a returned token is the vaulted card, not a handle to charge this payment again. Confirm settlement via the webhook before fulfilling.
If the button does not appear, the embed emits no error — it simply does not render. Re-check the domain's status in Settings → Wallet Domains (click Verify again if it is not Verified) and the eligibility gates: Safari on an Apple device with a card in Wallet. Standalone wallet buttons in elements mode report the frame_wallet_* codes instead — Errors → Wallet errors.
What's next
- Elements reference → Apple Pay & Google Pay — the runtime behavior and eligibility model
- Accept Apple Pay & Google Pay — standalone wallet buttons in
elementsmode - Quickstart — end-to-end Embedded Fields integration