Skip to main content

Accept Apple Pay™

Decrypt and vault Apple Pay™ tokens to route one-time and recurring payments to any processor without touching raw card data.

When a customer authorizes an Apple Pay™ payment, their device produces an encrypted payload containing a token and a one-time cryptogram. Basis Theory decrypts the payload, vaults the credentials, and gives you a token reference you can route to any processor via the Basis Theory Proxy.

Accept Apple Pay™ payment flow

Token Types

Apple returns one of two token types depending on how you configure the payment request and whether the issuer supports it:

  • DPAN (Device Primary Account Number) — a device-specific token. The DPAN persists on the device and is reused across transactions, but each authorization generates a unique one-time cryptogram. This is the default token type. DPANs can be used for recurring payments, but because they are tied to a specific device, they can be deactivated if the customer switches devices or removes the card from their wallet.

  • MPAN (Merchant Primary Account Number) — a merchant-specific token tied to a business and customer rather than a device. Apple recommends MPANs for recurring payment scenarios because they work across all of the customer's devices, enable recurring payments independent of a device, and remain active even when removed from a lost or stolen device.

Both token types follow the same implementation flow — the only difference is how you configure the payment request upfront. The tokenization response includes a type field (dpan or mpan) so your backend knows which was issued.

Requesting an MPAN

To receive an MPAN instead of a DPAN, your payment request must include one of Apple's recurring payment configurations. Apple's documentation covers the required fields and payment sheet behavior for each scenario:

  • Recurring Payments (subscriptions, memberships): Web | iOS
  • Automatic Reload (balance top-ups): Web | iOS
  • Deferred Payments (pre-orders, delayed charges): Web | iOS

All three configurations share two required fields:

  • managementURL — a URL on your site where the customer can manage their recurring payment.
  • tokenNotificationURL — the URL Apple calls when events affect the MPAN. Set this to https://api.basistheory.com/apple-pay/<YOUR_TENANT_ID> so Basis Theory can receive lifecycle updates from Apple and keep the token current.

If the issuer does not support MPAN generation, a DPAN is returned instead. Your backend should check the type field and handle both cases — the tokenization and payment processing flows are identical regardless of which type is issued.

Getting Started

To get started, you will need to create a Basis Theory Account and a TEST Tenant.

Be sure to use your work email (e.g., john.doe@yourcompany.com)
Ensure you have completed the Apple Pay™ Setup guide. This step is only required once.

Public Application

You will need a Public Application to authenticate requests. Click here to create one using the Basis Theory Customer Portal.

This will create an application with the following Access Controls:

  • Permissions: apple-pay:session, apple-pay:create
Save the key from the created Public Application as it will be used later in this guide.

Private Application

You will need a Private Application to allow your backend to call Basis Theory APIs. Click here to create one using the Basis Theory Customer Portal.

This will create an application with the following Access Controls:

  • Permissions: proxy:invoke
Save the key from the created Private Application as it will be used later in this guide.

Create a Session (Web)

When the customer taps the Apple Pay button on your website, your page must initiate merchant validation before the payment sheet appears. Handle the onvalidatemerchant event by calling the Create Merchant Session endpoint.

<html>
<head>
<script crossorigin src="https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js"></script>
<style>
apple-pay-button {
--apple-pay-button-width: 194px;
--apple-pay-button-height: 64px;
--apple-pay-button-border-radius: 13px;
--apple-pay-button-padding: 0px 0px;
--apple-pay-button-box-sizing: border-box;
}
</style>
<script>
function onApplePayButtonClicked() {
if (!ApplePaySession) {
return;
}

const session = new ApplePaySession(3, {
countryCode: "US",
currencyCode: "USD",
merchantCapabilities: ["supports3DS"],
supportedNetworks: ["visa", "masterCard", "amex", "discover"],
total: {
label: "My Store",
type: "final",
amount: "1.99"
},
// MPAN only: include one of the following to request a merchant token.
// See "Requesting an MPAN" above for details.
// recurringPaymentRequest: {
// paymentDescription: "Monthly Premium Subscription",
// regularBilling: { label: "Monthly Premium", amount: "9.99", type: "final" },
// managementURL: "https://yoursite.com/manage-subscription",
// tokenNotificationURL: "https://api.basistheory.com/apple-pay/<YOUR_TENANT_ID>"
// }
// automaticReloadPaymentRequest: {
// paymentDescription: "Automatic Account Reload",
// automaticReloadBilling: { label: "Account Reload", amount: "25.00", type: "final" },
// managementURL: "https://yoursite.com/manage-auto-reload",
// tokenNotificationURL: "https://api.basistheory.com/apple-pay/<YOUR_TENANT_ID>"
// }
// deferredPaymentRequest: {
// paymentDescription: "Pre-order Payment",
// deferredBilling: { label: "Item Charge", amount: "149.99", type: "final" },
// managementURL: "https://yoursite.com/manage-preorders",
// tokenNotificationURL: "https://api.basistheory.com/apple-pay/<YOUR_TENANT_ID>"
// }
});

session.onvalidatemerchant = async (event) => {
const merchantSession = await createSession();
session.completeMerchantValidation(merchantSession);
};

session.begin();
}

async function createSession() {
const response = await fetch("https://api.basistheory.com/apple-pay/session", {
method: "POST",
headers: {
"Content-Type": "application/json",
"BT-API-KEY": "<PUBLIC_APPLICATION_KEY>"
},
body: JSON.stringify({
display_name: "My Store",
domain: window.location.host,
// BYOK only: include the merchant_registration_id from Setup (BYOK) step 3
// merchant_registration_id: "<MERCHANT_REGISTRATION_ID>"
})
});

if (!response.ok) {
throw new Error(`Session request failed: ${response.status}`);
}

return response.json();
}
</script>
</head>
<body>
<apple-pay-button buttonstyle="black" type="plain" locale="en-US"></apple-pay-button>
<script>
document.querySelector("apple-pay-button")
.addEventListener("click", onApplePayButtonClicked);
</script>
</body>
</html>

This step is web-only. iOS apps using PassKit skip merchant validation entirely — Apple validates the merchant identifier against your app's entitlements automatically when the payment sheet is presented.

Tokenize

After the customer authorizes the payment, Apple passes the encrypted payment token to your handler. Send it to the Tokenize endpoint so Basis Theory can decrypt and vault the credentials.

Add the onpaymentauthorized handler to the session you created in the previous step:

session.onpaymentauthorized = async (event) => {
try {
const applePayToken = await tokenize(event.payment.token);
session.completePayment(ApplePaySession.STATUS_SUCCESS);
// send applePayToken.id to your backend for processing
await chargePayment(applePayToken.id);
} catch (e) {
console.error(e);
session.completePayment(ApplePaySession.STATUS_FAILURE);
}
};

async function tokenize(paymentToken) {
const response = await fetch("https://api.basistheory.com/apple-pay", {
method: "POST",
headers: {
"Content-Type": "application/json",
"BT-API-KEY": "<PUBLIC_APPLICATION_KEY>"
},
body: JSON.stringify({
apple_payment_data: paymentToken,
// BYOK only: include the merchant_registration_id from Setup (BYOK) step 3
// merchant_registration_id: "<MERCHANT_REGISTRATION_ID>"
})
});

if (!response.ok) {
throw new Error(`Tokenization failed: ${response.status}`);
}

const data = await response.json();
return data.apple_pay;
}

The response includes an apple_pay object. Save the id and pass it to your backend to process the payment.

Response
{
"apple_pay": {
"id": "4bdbd69e-2678-4c1e-8650-26e3e6a98949",
"type": "dpan",
"data": {
"number": "4895370012003478",
"expiration_month": 10,
"expiration_year": 2031
},
"authentication": {
"eci_indicator": "07",
"threeds_cryptogram": "AAAAAA..."
}
}
}

First Charge

The first transaction after tokenization is a customer-initiated transaction (CIT) that consumes the one-time cryptogram included with the DPAN or MPAN. Process this charge immediately — the cryptogram expires quickly, and if it expires before the first charge, subsequent merchant-initiated transactions linked to it will also fail.

From your backend, invoke an ephemeral Proxy to forward the decrypted credentials to your payment processor. The proxy resolves the Apple Pay token references in the request body before forwarding to the processor.

The cryptogram is one-time use and expires quickly. If the first charge is not immediate (e.g., free trials, deferred payments), use a $0 authorization or setup transaction to consume the cryptogram before it expires. Consult your processor's documentation for the equivalent operation.

In Checkout.com, you can charge a card by calling the /payments API.

Request a Payment
curl 'https://api.basistheory.com/proxy' \
-X 'POST' \
-H 'BT-API-KEY: <PRIVATE_API_KEY>' \
-H 'BT-PROXY-URL: https://api.sandbox.checkout.com/payments' \
-H 'Authorization: Bearer <CHECKOUT_AUTH_TOKEN>' \
-H 'Content-Type: application/json' \
--data '{
"source": {
"type": "network_token",
"token_type": "applepay",
"token": "{{ apple_pay: <APPLE_PAY_TOKEN_ID> | json: \"$.data.number\" }}",
"expiry_month": "{{ apple_pay: <APPLE_PAY_TOKEN_ID> | json: \"$.data\" | card_exp: \"MM\" }}",
"expiry_year": "{{ apple_pay: <APPLE_PAY_TOKEN_ID> | json: \"$.data\" | card_exp: \"YYYY\" }}",
"eci": "{{ apple_pay: <APPLE_PAY_TOKEN_ID> | json: \"$.authentication.eci_indicator\" }}",
"cryptogram": "{{ apple_pay: <APPLE_PAY_TOKEN_ID> | json: \"$.authentication.threeds_cryptogram\" }}"
},
"amount": 1000,
"currency": "USD",
"processing_channel_id": "<CHECKOUT_PROCESSING_CHANNEL_ID>"
}'
Payment Response
{
"id": "pay_myvj6zxrt45edgrswrliyxbtnq",
"approved": true,
"status": "Authorized",
"amount": 1000,
"currency": "USD",
"source": {
"type": "network_token",
"id": "src_gguer4xlawke3ctljczft7573a",
...
},
"scheme_id": "647537243670773",
...
}

Beware:

  • Some payment processors require you to explicitly enable decrypted Apple Pay acceptance on your account before they will accept these payloads. Confirm with your processor before going live.
  • You are not limited to the processors listed above. Any processor that accepts decrypted Apple Pay credentials can be reached through the ephemeral proxy.
  • Store the network transaction ID: processors need this to link subsequent merchant-initiated transactions to the original CIT and achieve the best approval rates.

Subsequent Charges

Once the first charge has consumed the cryptogram, you can process merchant-initiated transactions (MITs) for recurring billing, subscriptions, or follow-up charges without the customer being present. These transactions reuse the Apple Pay token with the network transaction ID from the original CIT — no new cryptogram is needed.

Apple Pay terms require a new authorization and cryptogram for each transaction where the customer is present (on-session). You cannot reuse a saved token for on-session payments — only for merchant-initiated transactions when the customer is not in the checkout flow.

In Checkout.com, you can charge a saved card by calling the /payments API. Pass the network transaction ID from the first charge into the previous_payment_id field.

Request a Payment
curl 'https://api.basistheory.com/proxy' \
-X 'POST' \
-H 'BT-API-KEY: <PRIVATE_API_KEY>' \
-H 'BT-PROXY-URL: https://api.sandbox.checkout.com/payments' \
-H 'Authorization: Bearer <CHECKOUT_AUTH_TOKEN>' \
-H 'Content-Type: application/json' \
--data '{
"source": {
"type": "network_token",
"token_type": "applepay",
"token": "{{ apple_pay: <APPLE_PAY_TOKEN_ID> | json: \"$.data.number\" }}",
"expiry_month": "{{ apple_pay: <APPLE_PAY_TOKEN_ID> | json: \"$.data\" | card_exp: \"MM\" }}",
"expiry_year": "{{ apple_pay: <APPLE_PAY_TOKEN_ID> | json: \"$.data\" | card_exp: \"YYYY\" }}",
"stored": true
},
"amount": 1000,
"currency": "USD",
"processing_channel_id": "<CHECKOUT_PROCESSING_CHANNEL_ID>",
"previous_payment_id": "<NETWORK_TRANSACTION_ID>"
}'

You are not limited to the processors listed above. Any processor that accepts decrypted Apple Pay credentials can be reached through the ephemeral proxy.

FAQ

How do I know whether a DPAN or MPAN was returned?

The tokenization response includes a type field on the apple_pay object with the value dpan or mpan. Your backend should read this field to determine how to route and store the token.

I requested an MPAN but received a DPAN.

MPAN generation depends on issuer and network support. If the issuer does not support MPANs, Apple returns a DPAN instead. The tokenization and payment processing flows are identical regardless of which type is issued — check the type field and handle both cases.

What if the first charge fails?

The cryptogram is consumed on the first authorization attempt, whether it succeeds or fails. If the first charge fails, subsequent merchant-initiated transactions linked to it will also fail. The customer must re-authorize through the Apple Pay payment sheet to generate a new cryptogram.

Do I need a separate integration for iOS and web?

The Basis Theory API calls for tokenization and payment processing are identical. The differences are in the front end: the web flow uses the Apple Pay JS API and requires a session creation step, while the iOS flow uses PassKit and skips session creation entirely. iOS integrations also always require Setup (BYOK).

What happens in a Test tenant?

In a Test tenant, Basis Theory accepts real Apple Pay tokens but returns simulated card data — real network tokens are not issued. To test with actual credentials, use a Production tenant with a live card added to your Apple Wallet.

How does Basis Theory handle MPAN lifecycle updates?

When you set tokenNotificationURL to your Basis Theory endpoint, Apple sends lifecycle events (e.g., card updates, suspensions) directly to Basis Theory. Basis Theory processes these events and updates your MPAN tokens accordingly — no action is required on your side.

My Stripe PaymentIntent or SetupIntent fails when using Apple Pay.

For PaymentIntents, the currency in the Apple Pay session request and the currency in the PaymentIntent must match, or Stripe will decline the transaction. For SetupIntents, the session currency must match the default currency of your Stripe account — SetupIntents do not allow currency to be overridden.