Apple Pay domain verification fails when Apple cannot access the file at https://yourdomain.com/.well-known/apple-developer-merchantid-domain-association. The fix is to download the verification file from your Paystack Dashboard, place it at that exact path on your server, and ensure it is served without redirects, with the correct content type, and is accessible from the public internet. Common causes of failure are 301 redirects (trailing slash, www/non-www), incorrect MIME type, the file being blocked by your hosting platform, or the file not being deployed to production.
Before Apple Pay can be offered as a payment option on your website through Paystack, Apple needs to confirm that you control the domain. The verification process works like this:
/.well-known/apple-developer-merchantid-domain-association.https://yourdomain.com/.well-known/apple-developer-merchantid-domain-association.The file itself is a text file containing a long token string. It has no file extension. The .well-known directory is a standard convention (RFC 8615) for hosting metadata files that services can discover automatically.
When verification fails, the Paystack Dashboard shows the domain as "unverified" or "pending." Apple Pay will not appear as a payment option for customers on that domain. The error is silent on the customer side: they simply do not see the Apple Pay button.
Cause 1: Redirect instead of direct response. This is the most frequent culprit. Your server redirects the request before serving the file. Typical redirects that break verification:
http:// to https:// redirect (Apple requests HTTPS, so this is only an issue if your HTTPS itself redirects)example.com to www.example.com (or vice versa)/.well-known/apple-developer-merchantid-domain-association to /.well-known/apple-developer-merchantid-domain-association/Cause 2: File not found (404). The file is not deployed to the correct path. This happens when the file is in your local development environment but was not included in the production build, or when your build process ignores dotfiles and directories starting with a period (like .well-known).
Cause 3: Wrong content type. Some servers auto-detect the content type and serve the file as application/octet-stream or another type. Apple expects the file to be served with a content type that returns the file body as text. text/plain or application/octet-stream both work, but ensure no middleware is transforming the response body.
Cause 4: Hosting platform blocks dotfiles. Some platforms block access to directories starting with a dot for security reasons. The .well-known directory needs an explicit exception.
Cause 5: CDN or caching layer interference. A CDN in front of your origin server might cache a 404 for the path, or might transform the response in a way Apple does not expect. Ensure the CDN passes through requests to /.well-known/ without modification.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
On Vercel, place the verification file in your public directory. Files in public are served statically at the root path.
# Create the directory and place the file
mkdir -p public/.well-known
cp apple-developer-merchantid-domain-association public/.well-known/
If Vercel is stripping the .well-known directory during build, add a configuration to your vercel.json:
// vercel.json
{
"rewrites": [
{
"source": "/.well-known/apple-developer-merchantid-domain-association",
"destination": "/api/apple-pay-verification"
}
]
}
Then create an API route that serves the file content:
// pages/api/apple-pay-verification.ts (Pages Router)
// or app/api/apple-pay-verification/route.ts (App Router)
import { NextResponse } from 'next/server';
const VERIFICATION_CONTENT = 'paste-your-verification-token-here';
export async function GET() {
return new NextResponse(VERIFICATION_CONTENT, {
headers: { 'Content-Type': 'text/plain' },
});
}
The static file approach (public/.well-known/) is simpler and should be your first attempt. Use the API route approach only if the static file is not being served correctly.
After deploying, verify with curl:
curl -I https://yourdomain.com/.well-known/apple-developer-merchantid-domain-association
You should see a 200 status with no redirects in the response chain.
On Netlify, place the verification file in your publish directory (usually public, dist, or build depending on your framework).
# For a React/Vue/Svelte app built with Vite
mkdir -p public/.well-known
cp apple-developer-merchantid-domain-association public/.well-known/
Netlify sometimes has issues serving files without extensions from dotfile directories. If the file is not being served, add a redirect rule in your netlify.toml or _redirects file:
# netlify.toml
[[redirects]]
from = "/.well-known/apple-developer-merchantid-domain-association"
to = "/.well-known/apple-developer-merchantid-domain-association"
status = 200
force = true
The force = true flag ensures this rule takes priority over any other redirect rules (like a SPA catch-all redirect). Without it, a rule like /* /index.html 200 for single-page apps might intercept the request and serve your HTML page instead of the verification file.
If you have a _redirects file, add this line before your SPA catch-all:
/.well-known/apple-developer-merchantid-domain-association /.well-known/apple-developer-merchantid-domain-association 200!
The exclamation mark (!) is Netlify's syntax for forced redirects, equivalent to force = true in the TOML format.
On nginx, the .well-known directory needs an explicit location block because nginx may block dotfile access by default.
# nginx.conf or your site configuration
server {
# ... your existing configuration ...
# Serve Apple Pay verification file
location = /.well-known/apple-developer-merchantid-domain-association {
alias /var/www/html/.well-known/apple-developer-merchantid-domain-association;
default_type text/plain;
}
# If you have a blanket block on dotfiles, add an exception
location ~ /\.well-known {
allow all;
}
# Common dotfile block that might interfere
# Make sure this does NOT match .well-known
location ~ /\.(?!well-known) {
deny all;
}
}
The key details:
location = (exact match) rather than a prefix match. This is more efficient and avoids unintended matches.default_type text/plain because nginx cannot auto-detect the type of a file without an extension.deny all for dotfiles, modify it to exclude .well-known. The regex /\.(?!well-known) uses a negative lookahead to block all dotfiles except .well-known.After updating the configuration:
# Test configuration syntax
sudo nginx -t
# Reload nginx
sudo systemctl reload nginx
# Verify the file is accessible
curl -v https://yourdomain.com/.well-known/apple-developer-merchantid-domain-association
Apache. Place the file in your document root under .well-known/. If Apache blocks dotfiles, add an exception in your .htaccess:
# .htaccess
RewriteEngine On
# Allow .well-known directory
RewriteRule ^.well-known/ - [L]
Express.js. Serve the file directly from a route:
const fs = require('fs');
const path = require('path');
app.get('/.well-known/apple-developer-merchantid-domain-association', (req, res) => {
const filePath = path.join(__dirname, '.well-known', 'apple-developer-merchantid-domain-association');
res.type('text/plain').sendFile(filePath);
});
Django. Add a URL pattern that serves the file:
# urls.py
from django.http import HttpResponse
from django.urls import path
def apple_pay_verification(request):
with open('.well-known/apple-developer-merchantid-domain-association') as f:
return HttpResponse(f.read(), content_type='text/plain')
urlpatterns = [
path('.well-known/apple-developer-merchantid-domain-association', apple_pay_verification),
# ... other urls
]
Laravel. Return the file content from a route:
// routes/web.php
Route::get('.well-known/apple-developer-merchantid-domain-association', function () {
return response(
file_get_contents(public_path('.well-known/apple-developer-merchantid-domain-association')),
200,
['Content-Type' => 'text/plain']
);
});
Before telling Paystack to verify your domain, confirm the file is accessible. Run these checks from your local machine (or any machine outside your network):
# Check 1: Verify HTTP status is 200 (not 301, 302, or 404)
curl -o /dev/null -s -w "%{http_code}" https://yourdomain.com/.well-known/apple-developer-merchantid-domain-association
# Expected output: 200
# Check 2: Verify no redirects occurred
curl -L -v https://yourdomain.com/.well-known/apple-developer-merchantid-domain-association 2>&1 | grep "< HTTP"
# Expected: single "HTTP/2 200" line. Multiple HTTP status lines mean redirects.
# Check 3: Verify the content is correct (not your SPA index.html)
curl https://yourdomain.com/.well-known/apple-developer-merchantid-domain-association
# Expected: the verification token string, NOT HTML content
The third check catches a subtle bug: if your single-page application's catch-all route is serving index.html for the verification path, curl will return a 200 status (looks correct) but the content will be HTML instead of the verification token. Apple will see the wrong content and reject verification.
Only trigger the domain verification from Paystack Dashboard after all three checks pass.
After domain verification succeeds in the Paystack Dashboard, confirm Apple Pay works end-to-end:
If the Apple Pay button does not appear after verification, check that: you are testing on an Apple device with Safari, the device has Apple Pay set up with at least one card, your Paystack account has Apple Pay enabled (not just domain-verified), and your checkout code includes the card payment channel (Apple Pay is presented as a card payment option).
Domain verification is per-environment. If you verified your production domain, your staging domain is still unverified. Verify each domain separately.
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course