Accept & send payments with Apple Pay

Moov allows you to both accept card payments and send payouts with Apple Pay. This step by step guide covers how you can start using Apple Pay on the web using the open standard Payment Request API.

In addition to being a fast and easy way to move money, Apple Pay offers strong privacy and security. In this guide, we’ll direct you to Apple’s guidelines, and bring you through a step by step process of adding a functional Apple Pay button for either accepting a payment or sending a payout.

Starting with Moov API version v2026.04.00, linking an Apple Pay token returns every supported Apple Pay payment method in a single response so you can choose the right flow for your use case:

Payment method type Directionality Use case
apple-pay Payment Accept a card payment from a payer using Apple Pay.
push-to-apple-pay Payout (OCT) Send a near real-time disbursement to a recipient's card via Apple Pay.
pull-from-apple-pay Funding (AFT) Pull funds from a debit or prepaid card on file in Apple Pay for approved use cases.
On earlier API versions, the link Apple Pay token endpoint returns a single payment method object. On v2026.04.00 and later, it returns an array so you can filter to the paymentMethodType that matches your flow (apple-pay, push-to-apple-pay, or pull-from-apple-pay).

Prerequisites

Before getting started, make sure your website follows Apple’s guidelines, and that your server is set up accordingly.

Apple Pay is only available in production mode. Moov does not offer Apple Pay in test mode at this time.

Register your domains

Any domains accepting payments must first be registered and verified with Apple. You can do this through the Moov Dashboard. Domains must be registered for each individual merchant account, which is the destination of the Moov transfer.

Within an account, navigate to the Settings tab and click the Add a domain button to add domains to Apple Pay.

To verify your domain, download the file and host it at the path shown for each domain you are registering. Ensure the file is in place before clicking Add domain. Note that serving the domain verification file via a redirect is a common cause of failure. If you encounter any issues, refer to this Apple Pay troubleshooting guide for guidance on common causes and resolutions.

You will now see a list of the domains you’ve added. You can come back to this page to remove or add domains.

Add Apple Pay button

Using the browser-native Payment Request API, no additional libraries are needed to add an Apple branded button, since everything is supported within Safari. Alternatively, you can also use the Apple Pay JS API.

Use Apple Pay JS API to allow for support in non-Safari browsers.

Add a button element to your webpage and use the Apple Pay documentation to set the button type, and to see best practices on using CSS. For disbursements, we recommend using the continue button type from Apple.

Here’s sample code for adding a black Buy with Apple Pay button, using the default size and corner radius:

<button class="apple-pay-button"></button>
<style>
  .apple-pay-button {
    -webkit-appearance: -apple-pay-button;
    border-radius: .5rem;
    height: 2.75rem;
    cursor: pointer;
  }
</style>

You can use the canMakePayment() method to check if the device supports Apple Pay:

const supportsApplePay = new PaymentRequest(
  [{ supportedMethods: "https://apple.com/apple-pay" }],
  details
).canMakePayment();

// Supports Apple Pay?
if (await supportsApplePay) {
  // show Apple Pay logo, for instance
  return;
}

Set up the Payment Request API for Apple Pay

After adding the button, you can set up methods, details, and options in the Payment Request API.

Methods

In Methods, indicate Apple Pay as a payment method by using:

  • "https://apple.com/apple-pay"
  • Apple Pay certified Moov account ID
  • Capabilities
  • Supported networks

For more details, read the W3 documentation on Methods.

Details

In Details, you can specify transaction details. For example, the transaction’s amount, shipping options, display items, and other modifiers. For Apple Pay transactions, Moov has a platform limit of $100,000.00 per transaction. If you need a higher limit for your use-case, please contact Moov support.

For disbursements, use the modifiers array to pass an Apple Pay disbursement request, which surfaces a "Send" confirmation in the Apple Pay sheet and lets you collect recipient contact fields.

For more details, read the W3 documentation on Details.

Options

In Options, you can include any specific customer details or shipping information.

For more details, read the W3 documentation on Options.

For pay-ins, include supportsCredit and supportsDebit in merchantCapabilities, and list all card networks you intend to accept.

// Sets up methods, details, and options for Payment Request API
const methods = [
  {
    supportedMethods: "https://apple.com/apple-pay",
    data: {
      version: 3,
      merchantIdentifier: "merchants-moov-account-id",
      merchantCapabilities: ["supports3DS", "supportsCredit", "supportsDebit"],
      supportedNetworks: ["amex", "discover", "masterCard", "visa"],
      countryCode: "US",
      requiredBillingContactFields: ["postalAddress"]
    },
  }
];

// Details about the purchase
const details = {
  total: {
    label: "Typically the merchant name",
    amount: { value: "1.00", currency: "USD" },
  }
};

const options = {
  requestPayerName: true,
  requestPayerEmail: true,
  requestPayerPhone: false
};
Failing to include the requiredBillingContactFields with postalAddress may result in declines or interchange downgrades.

For disbursements (OCT), include supportsInstantFundsOut in merchantCapabilities so Apple Pay only surfaces cards eligible for instant funds out, and restrict supportedNetworks to networks that support push-to-card (visa, masterCard). Pass an ApplePayDisbursementRequest via modifiers with additionalLineItems that include a disbursement line item (and optionally an instantFundsOutFee line item).

const APPLE_PAY_METHOD_URL = "https://apple.com/apple-pay";

// Fields collected for the recipient receiving the funds
const requiredRecipientContactFields = ["name", "email", "postalAddress"];

const methods = [
  {
    supportedMethods: APPLE_PAY_METHOD_URL,
    data: {
      version: 14,
      merchantIdentifier: "merchants-moov-account-id",
      merchantCapabilities: ["supports3DS", "supportsDebit", "supportsInstantFundsOut"],
      supportedNetworks: ["visa", "masterCard"],
      countryCode: "US",
      currencyCode: "USD"
    }
  }
];

// Total the recipient will receive
const details = {
  total: {
    label: "Typically the merchant name",
    amount: { value: "1.00", currency: "USD" }
  },
  modifiers: [
    {
      supportedMethods: APPLE_PAY_METHOD_URL,
      data: {
        disbursementRequest: {
          requiredRecipientContactFields
        },
        additionalLineItems: [
          { label: "Payout", amount: "1.00" },
          {
            label: "Instant Funds Out Fee",
            amount: "0.00",
            disbursementLineItemType: "instantFundsOutFee"
          },
          {
            label: "Typically the merchant name",
            amount: "1.00",
            disbursementLineItemType: "disbursement"
          }
        ]
      }
    }
  ]
};

const options = {};
Payment Request API version 14 or higher is required to use disbursementRequest. Apple Pay will only display cards that support instant funds out when supportsInstantFundsOut is set.

Detect if Apple Pay is available

The Apple Pay button should only be displayed if your user is using a supported device. You can check if the browser will support ApplePaySession with the call below:

if (window.PaymentRequest) {
  // If available, show Apple Pay button
}

Another option is to use Apple’s JS library canMakePaymentsWithActiveCard method to check if the device supports Apple Pay, and the user has an active card in their wallet. In the request below, use the merchant’s Moov accountID as the merchantIdentifier.

if (window.ApplePaySession) {
  let merchantIdentifier = 'merchants-moov-account-id';
  let promise = ApplePaySession.canMakePaymentsWithActiveCard(merchantIdentifier);
  promise.then((canMakePayments) => {
    if (canMakePayments) {
        // Display Apple Pay button here.
    }
  });
}

Now that you have verified eligibility and added the button, you are ready to create a payment request with the previously defined options when the button is clicked:

const paymentRequest = new PaymentRequest(methods, details, options);

Create & validate a merchant session

Once the payment request has been created, it’s time to create and validate an Apple Pay merchant session using the Moov API’s create Apple Pay session POST endpoint, or Moov.js method shown below:

moov.accounts.applePay.sessions.create({ 
  accountID: 'merchants-moov-account-id', 
  applePaySessionConfig: { displayName: 'string' }
});

This request accepts the account ID and display name of the merchant that will be accepting the payment. The response is a promise that includes the opaque merchantSession response from Apple to be used to resolve the onmerchantvalidation step.

  • accountID - Moov account ID of the merchant, which is the destination of the transfer
  • displayName - Merchant name for display in the Apple Pay payment sheet, for example, “Whole Body Fitness”
moov must be initialized with an access token generated by your backend. This token must include the /accounts/{merchantAccountID}/apple-pay.write scope, where merchantAccountID is the Moov account ID of the merchant accepting the payment.
The displayName from the merchant session may be displayed as the merchant name in Apple’s native Wallet app. On the actual card statement, the statementDescriptor (or dynamicDescriptor for approved accounts) will be displayed with the same logic as other card payments.

Link an Apple Pay token to the payer or recipient's Moov account using the Moov API's link Apple Pay token POST endpoint, or Moov.js method shown below. The request accepts the account ID as well as the opaque paymentResponse from Apple.

On API v2026.04.00 and later, the response is an array of payment methods — one per supported Apple Pay flow that could be created for the linked token. Filter the array by paymentMethodType to pick the paymentMethodID you need for your transfer.

For pay-ins, use the apple-pay payment method as the transfer source.

const paymentMethods = await moov.accounts.applePay.tokens.create({
  accountID: 'payers-moov-account-id',
  applePayToken: paymentResponse.details
});

const paymentMethodID = paymentMethods.find(
  (pm) => pm.paymentMethodType === "apple-pay"
)?.paymentMethodID;

For disbursements, use the push-to-apple-pay payment method (OCT) as the transfer destination. For approved AFT use cases, pull-from-apple-pay will also be present in the response.

const paymentMethods = await moov.accounts.applePay.tokens.create({
  accountID: 'recipients-moov-account-id',
  applePayToken: paymentResponse.details
});

const paymentMethodID = paymentMethods.find(
  (pm) => pm.paymentMethodType === "push-to-apple-pay"
)?.paymentMethodID;
This call requires the access token to also include the /accounts/{accountID}/cards.write scope, where accountID is the Moov account ID of the payer (for pay-ins) or the recipient (for payouts). Since the two scopes reference different account IDs — the merchant for sessions.create and the payer or recipient for tokens.create — your backend must generate a single token with both scopes before initializing moov. This means the payer or recipient account must be created before the token is requested.
On Moov API versions earlier than v2026.04.00, this endpoint returns a single payment method object (with paymentMethodType: "apple-pay") rather than an array. If you're on an older version, upgrade to v2026.04.00 to access push-to-apple-pay and pull-from-apple-pay.

Create a transfer & dismiss payment sheet

Use the paymentMethodID you filtered from the link-token response as the transfer source (for pay-ins) or destination (for payouts) when calling Moov's create transfer POST endpoint. No Moov.js method exists for creating a transfer.

Use the X-Wait-For header on the transfer request to receive a synchronous response so you can close out the payment sheet.

Apple Pay pay-ins are created the same as other card payments — the apple-pay payment method is the transfer source. To learn more about card payments with Moov, read our Accept card payments use case guide.

// Pseudo-code: instruct your server to make the transfer
const moovTransferResponse = await fetch(`/pay`, {
  method: 'post',
  body: JSON.stringify({ paymentMethodID })
});

let status;
if (moovTransferResponse.source.cardDetails.status === 'confirmed') {
  status = 'success';
} else {
  status = 'fail';
}

try {
  paymentResponse.complete(status);
  // Payment sheet is dismissed
} catch (error) {
  console.error(error);
}

Apple Pay payouts are created the same as other push to card transfers — the push-to-apple-pay payment method is the transfer destination, and the source is a Moov wallet with sufficient funds. The source account must have the send-funds.push-to-card capability enabled for funds disbursement use cases.

// Pseudo-code: instruct your server to make the payout
const moovTransferResponse = await fetch(`/payout`, {
  method: 'post',
  body: JSON.stringify({ paymentMethodID })
});

let status;
if (moovTransferResponse.destination.cardDetails.status === 'completed') {
  status = 'success';
} else {
  status = 'fail';
}

try {
  paymentResponse.complete(status);
  // Payment sheet is dismissed
} catch (error) {
  console.error(error);
}
Moov has a transfer limit of $50,000 per push-to-apple-pay transaction. Network velocity limits also apply — see the push to card guide for details.

Seeing it altogether

Here’s a sample that synthesizes the steps above all in one place. Switch tabs to see the full flow for either accepting a payment or sending a payout.

// Sets up methods, details, and options for Payment Request API
const methods = [
  {
    supportedMethods: "https://apple.com/apple-pay",
    data: {
      version: 3,
      merchantIdentifier: "merchants-moov-account-id",
      merchantCapabilities: ["supports3DS", "supportsCredit", "supportsDebit"],
      supportedNetworks: ["amex", "discover", "masterCard", "visa"],
      countryCode: "US",
    },
  }
];

const details = {
  total: {
    label: "Typically the merchant name",
    amount: { value: "1.00", currency: "USD" },
  }
};

const options = {
  requestPayerName: true,
  requestPayerEmail: true,
  requestPayerPhone: false
};

// Your backend generates a single access token with both required scopes:
//   /accounts/{merchantAccountID}/apple-pay.write
//   /accounts/{payerAccountID}/cards.write
// The payer account must already exist before this token is requested.
const { access_token } = await fetch('/token').then(r => r.json());
const moov = Moov(access_token);

// Assumes a button click handler
async function handleApplePayClick(event) {
  event.preventDefault();
  try {
    // Uses native Payment Request API and passes the options we configured above
    const paymentRequest = new PaymentRequest(methods, details, options);

    // Event is fired as soon as the payment sheet is opened
    paymentRequest.onmerchantvalidation = async (event) => {
      const merchantSession = await moov.accounts.applePay.sessions.create({ accountID: 'merchant-uuid', applePaySessionConfig: { displayName: 'Merchant name' }});
      event.complete(merchantSession);
    };

    // Display the payment sheet via Payment Request API
    const paymentResponse = await paymentRequest.show();

    // On v2026.04.00+, tokens.create() returns an array of payment methods.
    // Filter by `apple-pay` to get the pay-in paymentMethodID.
    const paymentMethods = await moov.accounts.applePay.tokens.create({
      accountID: 'payer-uuid',
      applePayToken: paymentResponse.details
    });
    const paymentMethodID = paymentMethods.find(
      (pm) => pm.paymentMethodType === "apple-pay"
    )?.paymentMethodID;

    // Pseudo-code: instruct your server to make the transfer. X-Wait-For header is needed to get response.
    const moovTransferResponse = await fetch(`/pay`, {
      method: 'post',
      body: JSON.stringify({ paymentMethodID })
    });

    let status;
    if (moovTransferResponse.source.cardDetails.status === 'confirmed') {
      status = 'success';
    } else {
      status = 'fail';
    }

    // Completes the payment request process and closes the payment sheet
    paymentResponse.complete(status);
    // Payment sheet is dismissed
  } catch (error) {
    console.error(error);
  }
};
const APPLE_PAY_METHOD_URL = "https://apple.com/apple-pay";

// Fields collected for the recipient receiving the funds
const requiredRecipientContactFields = ["name", "email", "postalAddress"];

// Sets up methods, details, and options for Payment Request API
const methods = [
  {
    supportedMethods: APPLE_PAY_METHOD_URL,
    data: {
      version: 14,
      merchantIdentifier: "merchants-moov-account-id",
      merchantCapabilities: ["supports3DS", "supportsDebit", "supportsInstantFundsOut"],
      supportedNetworks: ["visa", "masterCard"],
      countryCode: "US",
      currencyCode: "USD"
    }
  }
];

const details = {
  total: {
    label: "Typically the merchant name",
    amount: { value: "1.00", currency: "USD" }
  },
  modifiers: [
    {
      supportedMethods: APPLE_PAY_METHOD_URL,
      data: {
        disbursementRequest: {
          requiredRecipientContactFields
        },
        additionalLineItems: [
          { label: "Payout", amount: "1.00" },
          {
            label: "Instant Funds Out Fee",
            amount: "0.00",
            disbursementLineItemType: "instantFundsOutFee"
          },
          {
            label: "Typically the merchant name",
            amount: "1.00",
            disbursementLineItemType: "disbursement"
          }
        ]
      }
    }
  ]
};

const options = {};

// Your backend generates a single access token with both required scopes:
//   /accounts/{merchantAccountID}/apple-pay.write
//   /accounts/{recipientAccountID}/cards.write
// The recipient account must already exist before this token is requested.
const { access_token } = await fetch('/token').then(r => r.json());
const moov = Moov(access_token);

// Assumes a button click handler
async function handleApplePayDisbursementClick(event) {
  event.preventDefault();
  try {
    const paymentRequest = new PaymentRequest(methods, details, options);

    paymentRequest.onmerchantvalidation = async (event) => {
      const merchantSession = await moov.accounts.applePay.sessions.create({
        accountID: 'merchant-uuid',
        applePaySessionConfig: { displayName: 'Merchant name' }
      });
      event.complete(merchantSession);
    };

    const paymentResponse = await paymentRequest.show();

    // Returns all supported Apple Pay payment methods for this token.
    // Filter by `push-to-apple-pay` to get the OCT destination paymentMethodID.
    const paymentMethods = await moov.accounts.applePay.tokens.create({
      accountID: 'recipient-uuid',
      applePayToken: paymentResponse.details.token
    });
    const paymentMethodID = paymentMethods.find(
      (pm) => pm.paymentMethodType === "push-to-apple-pay"
    )?.paymentMethodID;

    // Pseudo-code: instruct your server to make the payout. X-Wait-For header is needed to get response.
    const moovTransferResponse = await fetch(`/payout`, {
      method: 'post',
      body: JSON.stringify({ paymentMethodID })
    });

    let status;
    if (moovTransferResponse.destination.cardDetails.status === 'completed') {
      status = 'success';
    } else {
      status = 'fail';
    }

    paymentResponse.complete(status);
    // Payment sheet is dismissed
  } catch (error) {
    console.error(error);
  }
};

For more information, refer to Apple’s documentation and the Apple Pay disbursement request reference.

Summary Beta