Apple Pay S2S Integration Guide
This guide walks you through integrating Apple Pay payments using the SysPay Wallet Pay S2S API. Your backend orchestrates the full Apple Pay flow through three API calls, while your frontend handles the native Apple Pay session using configuration returned by the API.
Prerequisites
- Apple Pay activated on your SysPay API feature
ROLE_API_PAYMENTpermission granted (plusROLE_PREAUTHfor pre-authorizations)- Your checkout domain registered for Apple Pay (see Domain Registration below)
- Your WSSE API credentials (login + passphrase) — see Authentication
Domain Registration
Before you can display the Apple Pay button on your website, the domain serving your checkout page must be registered and verified with Apple. This is a one-time setup handled in coordination with SysPay.
How it works
- Request Apple Pay — Contact SysPay to enable the Apple Pay payment method on your contract.
- Provide your domain — Tell SysPay the exact domain where Apple Pay will be used (e.g.
checkout.mystore.com). - Receive the verification file — SysPay will provide you with the domain verification file:
apple-developer-merchantid-domain-association. - Host the verification file — Place the file on your server so that it is accessible at:
https://<your-domain>/.well-known/apple-developer-merchantid-domain-association
Hosting requirements
The verification file must be publicly reachable. Apple's servers will fetch it to verify domain ownership. Ensure that:
- The URL is served over HTTPS
- The server returns HTTP 200
- There is no authentication required (no Basic Auth, no API key)
- There are no IP restrictions or geo-blocking
- There are no redirects (or only clean redirects that Apple can follow)
After uploading, verify it yourself by opening https://<your-domain>/.well-known/apple-developer-merchantid-domain-association in a browser — the file content should be returned directly with a 200 status.
Once SysPay confirms the domain is verified with Apple, you can proceed with the integration.
Payment Flow Overview
The Apple Pay S2S flow consists of three steps:
- Init — Create a payment operation and receive Apple Pay configuration
- Session — Validate the merchant session with Apple (called from
onvalidatemerchant) - Payment — Submit the Apple Pay payment token (called from
onpaymentauthorized)
Browser (Safari) Your Server SysPay API
│ │ │
│ 1. User clicks Apple Pay │ │
│─────────────────────────────> │ │
│ │ 2. POST wallet init │
│ │──────────────────────────────> │
│ │ <── apple_pay_config ──────── │
│ 3. new ApplePaySession() │ │
│ <──── config ──────────────── │ │
│ │ │
│ 4. onvalidatemerchant fires │ │
│─── validationURL ───────────> │ │
│ │ 5. POST session validation │
│ │──────────────────────────────> │
│ │ <── merchantSession ───────── │
│ 6. completeMerchantValidation │ │
│ <──── merchantSession ─────── │ │
│ │ │
│ 7. User authorizes payment │ │
│ (Touch ID / Face ID) │ │
│ │ │
│ 8. onpaymentauthorized fires │ │
│─── paymentData ─────────────> │ │
│ │ 9. POST wallet payment │
│ │──────────────────────────────> │
│ │ <── payment result ────────── │
│ 10. completePayment() │ │
│ <──── SUCCESS / FAILURE ───── │ │
Step 1 — Initialize Payment
Call the Initialize wallet payment endpoint with payment_method.type set to APPLE_PAY.
{
"payment_method": {
"type": "APPLE_PAY"
},
"amount": "1999",
"currency": "EUR",
"reference": "order-20260219-001",
"description": "Order #001",
"preauth": false,
"ems_url": "https://merchant.example.com/notify",
"customer": {
"firstname": "John",
"lastname": "Doe",
"email": "john@example.com",
"ip": "203.0.113.42",
"language": "en",
"billing_address": {
"address1": "123 Main St",
"city": "Paris",
"postal_code": "75001",
"country": "FR"
}
}
}
The response contains:
id— Save this. You need it for Steps 2 and 3.apple_pay_config— Pass these fields to your frontend to create theApplePaySession.
{
"wallet_pay_init": {
"id": 370529,
"status": "OPEN",
"payment_method": "APPLE_PAY",
"apple_pay_config": {
"merchant_id": "merchant.com.syspay.sandbox",
"country_code": "FR",
"label": "My Store",
"supported_networks": ["visa", "masterCard", "amex"],
"merchant_capabilities": ["supports3DS"]
}
}
}
See Initialize wallet payment for the full request and response reference.
Step 2 — Validate Merchant Session
When the onvalidatemerchant event fires on the frontend, forward the validationURL and your domain to the Apple Pay session validation endpoint.
{
"validationURL": "https://apple-pay-gateway.apple.com/paymentservices/startSession",
"domain": "checkout.mystore.com"
}
The response contains a session_data field (base64-encoded). Decode it and pass the resulting JSON to session.completeMerchantValidation():
const merchantSession = JSON.parse(atob(response.session_data));
session.completeMerchantValidation(merchantSession);
See Apple Pay session validation for the full request and response reference.
Step 3 — Submit Payment
When the onpaymentauthorized event fires (after the user authenticates with Touch ID / Face ID), base64-encode the payment token and send it to the Submit wallet payment endpoint.
{
"payment_method": {
"type": "APPLE_PAY",
"token": "eyJkYXRhIjoiYmFzZTY0X2VuY3J5cHRlZF9wYXltZW50X2RhdGEuLi4iLC..."
}
}
The token is built by base64-encoding the paymentData object:
const token = btoa(JSON.stringify(event.payment.token.paymentData));
Based on the returned status, call session.completePayment() with either ApplePaySession.STATUS_SUCCESS or ApplePaySession.STATUS_FAILURE.
Payment Statuses
| Status | Meaning |
|---|---|
SUCCESS | Payment captured successfully |
AUTHORIZED | Pre-authorization successful (capture later) |
FAILED | Payment was declined — see failure_category and failure_message |
ERROR | Processing error |
TIMED_OUT | Payment timed out |
See Submit wallet payment for the full request and response reference.
Frontend Integration
Checking Apple Pay Availability
Before showing the Apple Pay button, check that the device supports it:
if (window.ApplePaySession && ApplePaySession.canMakePayments()) {
// Show Apple Pay button
}
Full JavaScript Example
async function startApplePay(orderData) {
// Step 1: Initialize payment via your backend
const initResponse = await fetch('/your-backend/wallet/init', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(orderData)
});
const { wallet_pay_init } = await initResponse.json();
const operationId = wallet_pay_init.id;
const config = wallet_pay_init.apple_pay_config;
// Create Apple Pay payment request
const paymentRequest = {
countryCode: config.country_code,
currencyCode: orderData.currency,
supportedNetworks: config.supported_networks,
merchantCapabilities: config.merchant_capabilities,
total: {
label: config.label,
amount: (orderData.amount / 100).toFixed(2)
}
};
// Create Apple Pay session
const session = new ApplePaySession(14, paymentRequest);
// Step 2: Merchant validation
session.onvalidatemerchant = async (event) => {
const sessionResponse = await fetch(
`/your-backend/wallet/${operationId}/applepay/session`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
validationURL: event.validationURL,
domain: window.location.hostname
})
}
);
const data = await sessionResponse.json();
const merchantSession = JSON.parse(atob(data.session_data));
session.completeMerchantValidation(merchantSession);
};
// Step 3: Payment authorization
session.onpaymentauthorized = async (event) => {
const token = btoa(JSON.stringify(event.payment.token.paymentData));
const paymentResponse = await fetch(
`/your-backend/wallet/${operationId}/payment`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
payment_method: { type: 'APPLE_PAY', token }
})
}
);
const { payment } = await paymentResponse.json();
if (payment.status === 'SUCCESS' || payment.status === 'AUTHORIZED') {
session.completePayment(ApplePaySession.STATUS_SUCCESS);
} else {
session.completePayment(ApplePaySession.STATUS_FAILURE);
}
};
session.oncancel = () => {
// Handle user cancellation
};
session.begin();
}
Backend Integration (PHP)
class ApplePayService
{
private $apiLogin;
private $apiPassphrase;
private $baseUrl;
public function __construct(string $apiLogin, string $apiPassphrase, string $baseUrl)
{
$this->apiLogin = $apiLogin;
$this->apiPassphrase = $apiPassphrase;
$this->baseUrl = $baseUrl;
}
/**
* Step 1: Initialize Apple Pay payment
*/
public function init(array $orderData): array
{
$orderData['payment_method'] = ['type' => 'APPLE_PAY'];
return $this->request('POST', '/merchant/payment/wallet', $orderData);
}
/**
* Step 2: Validate merchant session
*/
public function validateSession(int $operationId, string $validationURL, string $domain): array
{
return $this->request('POST', "/merchant/payment/{$operationId}/wallet/applepay/session", [
'validationURL' => $validationURL,
'domain' => $domain,
]);
}
/**
* Step 3: Submit payment token
*/
public function submitPayment(int $operationId, string $base64Token): array
{
return $this->request('POST', "/merchant/payment/{$operationId}/wallet", [
'payment_method' => [
'type' => 'APPLE_PAY',
'token' => $base64Token,
],
]);
}
private function request(string $method, string $path, array $body): array
{
$ch = curl_init($this->baseUrl . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-WSSE: ' . $this->buildWsseHeader(),
],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($httpCode >= 400) {
throw new \RuntimeException(
sprintf('API error %d: %s', $httpCode, $response)
);
}
return $data;
}
private function buildWsseHeader(): string
{
$nonce = random_bytes(16);
$timestamp = time();
$digest = base64_encode(sha1($nonce . $timestamp . $this->apiPassphrase, true));
return sprintf(
'AuthToken MerchantAPILogin="%s", PasswordDigest="%s", Nonce="%s", Created="%d"',
$this->apiLogin,
$digest,
base64_encode($nonce),
$timestamp
);
}
}
Usage
$applePay = new ApplePayService('139112001', 'your-passphrase', 'https://app.syspay.com/api/v2');
// Step 1
$init = $applePay->init([
'amount' => '1999',
'currency' => 'EUR',
'reference' => 'order-001',
'customer' => [
'ip' => $_SERVER['REMOTE_ADDR'],
'email' => 'john@example.com',
],
]);
$operationId = $init['wallet_pay_init']['id'];
// Step 2 (called from your session validation endpoint)
$sessionData = $applePay->validateSession($operationId, $validationURL, $domain);
// Step 3 (called from your payment endpoint)
$result = $applePay->submitPayment($operationId, $base64Token);
Testing Apple Pay
Use the sandbox environment (https://app-sandbox.syspay.com/api/v2) for testing.
To test Apple Pay payments, you need:
- An Apple Pay compatible device — iPhone with Face ID/Touch ID, iPad, Mac with Touch ID, or Mac paired with an iPhone.
- A sandbox Apple Pay wallet — Sign in to your test device with an Apple sandbox tester account, then add a test card to the Wallet app. See Test Cards for Apps and the Web on Apple's sandbox testing page for available test card numbers and expected behaviors.
Sandbox Setup
- Create a sandbox tester account in App Store Connect under Users and Access > Sandbox > Testers.
- On your test device, sign out of your personal Apple ID and sign in with the sandbox account (or use a dedicated test device).
- Open the Wallet app and add one of Apple's test cards.
- Point your integration to the SysPay sandbox environment.
- Run through the full payment flow — init, session validation, and payment submission.
What to Verify
- The Apple Pay payment sheet displays correctly with your merchant name and amount.
- Merchant session validation succeeds (Step 2 returns a valid session).
- After authenticating with Face ID/Touch ID, the payment processes and returns
SUCCESSorAUTHORIZED. - Error scenarios are handled: cancel the payment sheet, use a declined test card, and verify your frontend and backend respond appropriately.
- If you configured an
ems_url, verify that webhook notifications are received.
Further Reading
- Apple Pay Implementation Guide — covers
ApplePaySession, payment request configuration, merchant validation, and payment authorization in depth. - Apple Pay JS API Reference — complete API reference for
ApplePaySession, events, and payment data structures. - Apple Pay Sandbox Testing — test card numbers and expected behaviors.
Checklist
- Apple Pay is activated on your SysPay API feature
- Your domain is registered with Apple for Apple Pay (verification file hosted)
- WSSE authentication is implemented with unique nonces
-
amountis sent in cents as a string -
customer.ipis the end-user's real IP (not your server's IP) -
validationURLis forwarded exactly as received fromonvalidatemerchant -
session_datais base64-decoded before passing tocompleteMerchantValidation() - Payment token is base64-encoded before sending to the payment endpoint
- Payment status is checked and
completePayment()is called accordingly - Error responses are handled gracefully on both frontend and backend
-
ems_urlis set for webhook notifications (recommended) - Tested end-to-end in sandbox before going live