Apple Pay Merchant Validation
Merchant validation is a critical step where Apple verifies the merchant's identity after the user initiates payment. This process ensures secure communication between the merchant's domain and Apple's payment infrastructure.
This document focuses on: handling the onvalidatemerchant
event on the frontend and completing merchant authentication on the backend.
Quick navigation:
- Payment Initiation
- Payment Authorization
- Complete Payment Flow
- Session Initialization
- Account Setup
- Troubleshooting
Process Overview
Merchant validation involves four core steps:
- Receive Validation Request: Frontend receives
validationURL
from Apple - Backend Authentication: Use merchant certificates or Onerway proxy to authenticate with Apple
- Obtain Merchant Session: Successfully receive
merchantSession
object after validation - Complete Validation: Frontend calls
completeMerchantValidation()
to continue payment flow
Validation Process Flow
Merchant Validation Flow
Implementation Guide
Frontend Implementation
Handle merchant validation events on the frontend:
/**
* Handle Apple Pay merchant validation event
* @param {ApplePaySession} session - Active Apple Pay session
* @param {ApplePayValidateMerchantEvent} event - Validation event from Apple
* @param {Object} config - Merchant configuration
*/
async function handleMerchantValidation(session, event, config) {
// Build the validation request for your backend (only essential parameters)
const validationRequest = {
domainName: window.location.hostname, // Must match verified domain
validationURL: event.validationURL // From Apple onvalidatemerchant
};
try {
const response = await fetch('/api/apple-pay/validate-merchant', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(validationRequest)
});
const data = await response.json();
if (data.merchantSession) {
session.completeMerchantValidation(data.merchantSession);
} else {
throw new Error(data.error || 'Validation failed');
}
} catch (error) {
console.error('Merchant validation failed:', error);
session.abort();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Warning
After obtaining the session object, please call completeMerchantValidation
method to complete merchant validation.
Backend Implementation
Complete merchant validation through Onerway proxy.
Request
Parameter | Type | Length | Required | Signed | Description |
---|---|---|---|---|---|
appId | String | 20 | Yes | Yes | Merchant application ID assigned by Onerway for website identification. |
merchantNo | String | 20 | Yes | Yes | Merchant number assigned by |
requestId | String | 64 | Yes | Yes | Unique request identifier for tracking and deduplication. |
sign | String | / | Yes | No | Digital signature string for request verification and security. Please refer to Signature for signature generation method. |
verifyUrl | String | 256 | Yes | Yes | Apple Pay merchant validation URL (from |
website | String | 128 | Yes | Yes | Merchant domain name registered with Apple Pay (maps to Apple Pay |
Response
merchantSession
is inside data
when using raw gateway response:
Name | Type | Description |
---|---|---|
respCode | String | Response code from |
respMsg | String | Response message from |
data | Object | Response data. Refer to object data |
data
Name | Type | Description |
---|---|---|
└data | String | Apple Pay merchant session data as JSON string. This data should be passed directly to the Apple Pay session completion handler. |
API Usage Example
{
"appId": "1727880846378401792",
"merchantNo": "800209",
"requestId": "ZYRDGXQSDEUMFFXVWEU",
"sign": "c46ea8b12bcc3392a6b8a58207a71f634442a8ea650d7cdb97494e1ec5f413c9",
"verifyUrl": "https://apple-pay-gateway-cert.apple.com/paymentservices/startSession",
"website": "rundown-beret.biz"
}
2
3
4
5
6
7
8
{
"respCode": "20000",
"respMsg": "Success",
"data": "{\"epochTimestamp\":1752027594903,\"expiresAt\":1752031194903,\"merchantSessionIdentifier\":\"SSHC70E8352BAB64DA1ABD5534864FB1D7D_916523AAED1343F5BC5815E12BEE9250AFFDC1A17C46B0DE5A943F0F94927C24\",\"nonce\":\"493ee68c\",\"merchantIdentifier\":\"A719B735608A23CC132E86CF3C67E22C446673B495D53A28FD2FA671B5B86C87\",\"domainName\":\"rundown-beret.biz\",\"displayName\":\"rundown-beret.biz\",\"signature\":\"308006092a864886f70d010702a0803080020101310d300b0609608648016503040201308006092a864886f70d0107010000a080308203e43082038ba003020102020859d8a1bcaaf4e3cd300a06082a8648ce3d040302307a312e302c06035504030c254170706c65204170706c69636174696f6e20496e746567726174696f6e204341202d20473331263024060355040b0c1d4170706c652043657274696669636174696f6e20417574686f7269747931133011060355040a0c0a4170706c6520496e632e310b3009060355040613025553301e170d3231303432303139333730305a170d3236303431393139333635395a30623128302606035504030c1f6563632d736d702d62726f6b65722d7369676e5f5543342d53414e44424f5831143012060355040b0c0b694f532053797374656d7331133011060355040a0c0a4170706c6520496e632e310b30090603550406130255533059301306072a8648ce3d020106082a8648ce3d030107034200048230fdabc39cf75e202c50d99b4512e637e2a901dd6cb3e0b1cd4b526798f8cf4ebde81a25a8c21e4c33ddce8e2a96c2f6afa1930345c4e87a4426ce951b1295a38202113082020d300c0603551d130101ff04023000301f0603551d2304183016801423f249c44f93e4ef27e6c4f6286c3fa2bbfd2e4b304506082b0601050507010104393037303506082b060105050730018629687474703a2f2f6f6373702e6170706c652e636f6d2f6f63737030342d6170706c65616963613330323082011d0603551d2004820114308201103082010c06092a864886f7636405013081fe3081c306082b060105050702023081b60c81b352656c69616e6365206f6e207468697320636572746966696361746520627920616e7920706172747920617373756d657320616363657074616e6365206f6620746865207468656e206170706c696361626c65207374616e64617264207465726d7320616e6420636f6e646974696f6e73206f66207573652c20636572746966696361746520706f6c69637920616e642063657274696669636174696f6e2070726163746963652073746174656d656e74732e303606082b06010505070201162a687474703a2f2f7777772e6170706c652e636f6d2f6365727469666963617465617574686f726974792f30340603551d1f042d302b3029a027a0258623687474703a2f2f63726c2e6170706c652e636f6d2f6170706c6561696361332e63726c301d0603551d0e041604140224300b9aeeed463197a4a65a299e4271821c45300e0603551d0f0101ff040403020780300f06092a864886f76364061d04020500300a06082a8648ce3d0403020347003044022074a1b324db4249430dd3274c5074c4808d9a1f480e3a85c5c1362566325fbca3022069369053abf50b5a52f9f6004dc58aad6c50a7d608683790e0a73ad01e4ad981308202ee30820275a0030201020208496d2fbf3a98da97300a06082a8648ce3d0403023067311b301906035504030c124170706c6520526f6f74204341202d20473331263024060355040b0c1d4170706c652043657274696669636174696f6e20417574686f7269747931133011060355040a0c0a4170706c6520496e632e310b3009060355040613025553301e170d3134303530363233343633305a170d3239303530363233343633305a307a312e302c06035504030c254170706c65204170706c69636174696f6e20496e746567726174696f6e204341202d20473331263024060355040b0c1d4170706c652043657274696669636174696f6e20417574686f7269747931133011060355040a0c0a4170706c6520496e632e310b3009060355040613025553301e170d3134303530363233343633305a170d3239303530363233343633305a307a312e302c06035504030c254170706c65204170706c69636174696f6e20496e746567726174696f6e204341202d20473331263024060355040b0c1d4170706c652043657274696669636174696f6e20417574686f7269747931133011060355040a0c0a4170706c6520496e632e310b30090603550406130255533059301306072a8648ce3d020106082a8648ce3d03010703420004f017118419d76485d51a5e25810776e880a2efde7bae4de08dfc4b93e13356d5665b35ae22d097760d224e7bba08fd7617ce88cb76bb6670bec8e82984ff5445a381f73081f4304606082b06010505070101043a3038303606082b06010505073001862a687474703a2f2f6f6373702e6170706c652e636f6d2f6f63737030342d6170706c65726f6f7463616733301d0603551d0e0416041423f249c44f93e4ef27e6c4f6286c3fa2bbfd2e4b300f0603551d130101ff040530030101ff301f0603551d23041830168014bbb0dea15833889aa48a99debebdebafdacb24ab30370603551d1f0430302e302ca02aa0288626687474703a2f2f63726c2e6170706c652e636f6d2f6170706c65726f6f74636167332e63726c300e0603551d0f0101ff0404030201063010060a2a864886f7636406020e04020500300a06082a8648ce3d040302036700306402303acf7283511699b186fb35c356ca62bff417edd90f754da28ebef19c815e42b789f898f79b599f98d5410d8f9de9c2fe0230322dd54421b0a305776c5df3383b9067fd177c2c216d964fc6726982126f54f87a7d1b99cb9b0989216106990f09921d00003182018830820184020101308186307a312e302c06035504030c254170706c65204170706c69636174696f6e20496e746567726174696f6e204341202d20473331263024060355040b0c1d4170706c652043657274696669636174696f6e20417574686f7269747931133011060355040a0c0a4170706c6520496e632e310b3009060355040613025553020859d8a1bcaaf4e3cd300b0609608648016503040201a08193301806092a864886f70d010903310b06092a864886f70d010701301c06092a864886f70d010905310f170d3235303730393032313935345a302806092a864886f70d010934311b3019300b0609608648016503040201a10a06082a8648ce3d040302302f06092a864886f70d010904312204206eea5fe324e35a0f4a87d568594a2334f3ad5e52ea2d1ad9b6de048605c83fda300a06082a8648ce3d04030204473045022100eff116376d607a5c43681ffe5ade91eafd5640723b8ff00a0a8f2b934f42d18e022059139c887992a14f6fa5fc61b3f3ba4ed454d1a295a6952d9068e561eb027f70000000000000\",\"operationalAnalyticsIdentifier\":\"rundown-beret.biz:A719B735608A23CC132E86CF3C67E22C446673B495D53A28FD2FA671B5B86C87\",\"retries\":0,\"pspId\":\"A719B735608A23CC132E86CF3C67E22C446673B495D53A28FD2FA671B5B86C87\"}"
}
2
3
4
5
Code Example
@PostMapping("/api/apple-pay/validate-merchant")
public ResponseEntity<Map<String, Object>> validateMerchant(@RequestBody Map<String, String> request) {
try {
String validationURL = request.get("validationURL");
String domainName = request.get("domainName");
// Build Onerway request parameters (only essential business parameters)
Map<String, String> body = new HashMap<>();
body.put("verifyUrl", validationURL);
body.put("website", domainName);
// Call Onerway Apple Pay Web Session API
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, String>> entity = new HttpEntity<>(body, headers);
ResponseEntity<Map> response = restTemplate.postForEntity(
ONERWAY_BASE_URL + "/txn/apiCheckApplePay",
entity,
Map.class
);
Map<String, Object> result = response.getBody();
if ("20000".equals(result.get("respCode"))) {
// Parse merchantSession data
String sessionData = (String) result.get("data");
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> merchantSession = mapper.readValue(sessionData, Map.class);
Map<String, Object> successResponse = new HashMap<>();
successResponse.put("merchantSession", merchantSession);
return ResponseEntity.ok(successResponse);
} else {
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("error", result.get("respMsg"));
return ResponseEntity.status(500).body(errorResponse);
}
} catch (Exception e) {
logger.error("Merchant validation failed", e);
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("error", "Merchant validation failed");
return ResponseEntity.status(500).body(errorResponse);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
app.post('/api/apple-pay/validate-merchant', async (req, res) => {
try {
const { validationURL, domainName } = req.body;
// Build request body (only essential business parameters)
const body = {
verifyUrl: validationURL,
website: domainName
};
const response = await fetch(`${process.env.ONERWAY_BASE_URL}/txn/apiCheckApplePay`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const result = await response.json();
if (result.respCode === '20000') {
// Parse merchantSession data
const merchantSession = JSON.parse(result.data);
res.json({ merchantSession });
} else {
res.status(500).json({ error: result.respMsg || 'Validation failed' });
}
} catch (error) {
console.error('Merchant validation error:', error);
res.status(500).json({ error: 'Merchant validation failed' });
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Security Requirements
Certificate Management (Merchant-owned mode):
- Set certificate file permissions to 600
- Monitor certificate expiration dates
- Regular certificate rotation
Request Security:
- Validate Apple validation URL starts with
https://apple-pay-gateway
- Use HTTPS for all communications
- Minimize error message exposure
See detailed practices in Troubleshooting.
Error Handling
Basic Principles:
- Call
session.abort()
immediately when validation fails - Display user-friendly error messages
- Log technical details on server side
Common Errors:
- Network timeout: Retry 1-2 times
- Certificate issues: Check certificate configuration
- Invalid URL: Verify domain whitelist
See complete error handling strategies in Troubleshooting.
Best Practices
Performance Requirements
- Set reasonable timeout values (5-10 seconds)
- Cache merchant certificates to improve response times
- Monitor validation success rates and response times
Security Standards
- Validate Apple validation URL legitimacy
- Securely store and manage merchant certificates
- Log audit information without exposing sensitive data
Next Steps
After completing merchant validation, continue with the following flow:
- Payment Authorization - Handle user payment confirmation
- Complete Payment Flow - View end-to-end integration examples
- Troubleshooting - Common issues and solutions
Prerequisites: Ensure Session Initialization and Account Setup are complete.