Skip to main content
Accept crypto purchases with a native Apple Pay sheet presented directly in your iOS app. No widget, no redirect, no web checkout. You own the entire UI; Meld and Mercuryo handle payment processing and settlement.
Provider availability. Mercuryo is live today. Additional providers are launching soon.
Platform: Apple Pay is iOS-only. On Android, and on iOS devices without Apple Pay, fall back to your card or hosted checkout flow.
Integration style — headless. Your backend talks to the Meld API with your secret key. Your app presents the payment sheet with Apple’s native PassKit framework and submits the encrypted Apple Pay token to Meld with a short-lived session token. No Meld SDK or widget is involved. Here is a demo of the Apple Pay flow (recorded inside Meld Checkout, Meld’s hosted UI). The native-app experience your users see is the same Apple sheet, launched directly from your app.

How it works

  1. Your backend creates a headless order with your secret API key and receives a short-lived sessionToken.
  2. Your app presents the native Apple Pay sheet with PassKit.
  3. On authorization, your app posts the encrypted Apple Pay token to Meld’s process endpoint, authenticated with the sessionToken only. Your API key never ships in the app binary.
  4. Settlement is confirmed asynchronously to your backend via webhooks.

Before you begin

Meld account

  • A Meld API key with payment access (start in sandbox, promote to production for go-live).
  • Headless onramps enabled on your account, with Mercuryo enabled as a provider and native Apple Pay enabled for your target corridors.
Contact your Meld representative to have these switched on. Until they are, order creation fails with WHITELABEL_NOT_ENABLED (HTTP 403).

Base URLs

Send Meld-Version: 2026-05-01 (or later) on every request. Some endpoints in this guide require it, and sending it everywhere keeps response shapes consistent.
API compatibility. New fields may appear in responses without a version bump. Use flexible JSON parsing and do not reject unknown properties.

Supported corridors

  • Mercuryo native Apple Pay does not process US or GB users, and it does not accept US or GB-issued cards. Use a supported corridor (for example EUR to BTC in France, or CAD corridors).
  • Direct onramps only. Routes that swap into the destination asset are not eligible.
  • Cards: Visa and Mastercard.

Apple Developer setup (one-time)

You need your own Apple Merchant ID and an Apple Pay Payment Processing certificate issued against a certificate signing request (CSR) that Meld provides.
Golden rule. The Apple Pay token is decrypted by the payment processor, so the processor generates the CSR and holds the private key. Never generate the Payment Processing key yourself. If you do, the token cannot be decrypted and every payment fails.
1

Create a Merchant ID

In Apple Developer, go to Certificates, Identifiers & Profiles → Identifiers → + → Merchant IDs and register a reverse-DNS, globally-unique ID such as merchant.com.yourcompany.app.
2

Enable Apple Pay on your App ID

Under Identifiers → your App ID → Capabilities, tick Apple Pay Payment Processing, click Configure, select your Merchant ID, and save.
“In-App Purchase” is a different capability, used for digital goods sold through Apple. It does nothing for Apple Pay.
3

Request the CSR from Meld

Ask your Meld representative for the Apple Pay Payment Processing CSR (a .certSigningRequest file) tied to your merchant ID.
4

Issue the certificate

In Apple Developer, go to Identifiers → Merchant IDs → your merchant ID → Apple Pay Payment Processing Certificate → Create Certificate. Answer No to the China-mainland question, upload Meld’s CSR, and download the issued .cer.
5

Return the .cer to Meld

Meld pairs the certificate with the private key held by the processor and registers your merchant ID on your account.
6

Add the app entitlement

In Xcode, open your app target’s Signing & Capabilities tab, add + Apple Pay, and include your merchant ID. This adds the com.apple.developer.in-app-payments entitlement. Regenerate provisioning profiles afterwards.
A Merchant Identity certificate and domain verification are not needed for native in-app Apple Pay. Those apply only to Apple Pay on the web. Native PassKit validates the merchant on-device.
Ship a native build, not an OTA update. PassKit code lives in the app binary. A JavaScript-only over-the-air update (EAS Update, CodePush, and similar) cannot add or change it. Every Apple Pay change must go out through a native build. Users on an older binary keep seeing your fallback flow until they update.

Who does what

Step 1 — Customer setup (once per user)

Every purchase needs a Meld customer that is KYC-approved and has an email on its Meld record. Mercuryo provisions its own customer record automatically on the first order using that email and the approved verification. There is no separate registration call, but if either prerequisite is missing the order fails.

1a. Create the customer (with email)

Request body
  • Get-or-create is keyed on externalId. Reposting with the same externalId returns the existing customer and fills in any fields (such as email) that were previously blank, without overwriting existing values.
  • name is a nested object, not top-level firstName / lastName fields.
  • The response includes the Meld id (this is the customerId). Store it against your user record.
The email must be on the Meld customer record. If your users sign in wallet-first or through a social provider, the email you display in-app may live only in your own database. Sync it to Meld at customer creation, or patch it in later:

1b. Run KYC (Meld-hosted)

There is no provider widget in a headless flow, so identity verification runs through Meld’s unified KYC:
Request body
Response
Open url for the user (an in-app browser is fine) and let them complete verification.
  • If KYC was already initiated for this customer, the POST returns HTTP 409. Call PATCH /accounts/customers/{customerId}/kyc/initiate with the same body to fetch a fresh URL for the existing verification.
  • KYC statuses: PENDING, APPROVED, REJECTED, EXPIRED.

1c. Wait for approval

There are two ways to know a customer is approved:
  • Webhook (recommended). Subscribe to CUSTOMER_KYC_STATUS_CHANGE. The payload includes customerId, serviceProvider, status, and statusUpdatedAt.
  • Poll. Call GET /accounts/customers/{customerId} with the Meld-Version header. The response then includes serviceProviderCustomers, and the customer is ready when the SUMSUB entry shows kyc.status: "APPROVED". Without the version header the response omits KYC information entirely.
The same response also shows the email field, so you can verify both prerequisites in a single call.

Step 2 — Check corridor eligibility (optional)

Send Meld-Version: 2026-05-01:
Request body
A quote row with serviceProvider: "MERCURYO", paymentMethodType: "APPLE_PAY", and integrationMode: "HEADLESS" means the corridor is catalog-eligible, and it gives you the live rate and fees to display.
The quote is not a guarantee. Live native-Apple-Pay availability is only confirmed at order creation. If it is not available for the corridor at that moment, order creation fails with INVALID_PAYMENT_METHOD. Treat that as your signal to fall back to a card flow. Also gate the Apple Pay button client-side with PassKit’s PKPaymentAuthorizationController.canMakePayments().

Step 3 — Create the order (your backend)

Create the order when the user taps Buy, not ahead of time. The order embeds a live Mercuryo quote and the session token expires after about 30 minutes. A pre-created order can go stale before the user authorizes.
Request body
Do not send redirectUrl with APPLE_PAY. It is rejected with INVALID_REDIRECT_URL.
Response (201 Created)
  • sessionToken — short-lived (roughly 30 minutes) token the app uses to authenticate the process call. Safe to hand to the device: it is scoped to this order only.
  • merchantTransactionId — echo it back verbatim on the process call.
  • merchantIdentifier — your merchant ID as configured on your Meld account. May be omitted if it is not configured; in that case your app should fall back to the merchant ID it already knows from its own entitlement.
Idempotency. Retrying with the same X-Idempotency-Key after a success returns HTTP 200 with the original body (no duplicate order). A retry while the first request is still in flight returns HTTP 425 (Too Early). Retry shortly or mint a new key. Each user-initiated new attempt should use a fresh key and a fresh externalOrderId.
Expose all of this behind a thin authenticated route on your backend (for example POST /api/apple-pay-order) that injects customerId and returns the order JSON to your app. The secret API key never reaches the device.

Step 4 — Present the Apple Pay sheet (your app)

Use PassKit directly. The exact request configuration matters. These values are what Mercuryo expects:
Swift (PassKit)
  • Gate the button with PKPaymentAuthorizationController.canMakePayments() and use Apple’s PKPaymentButton for the button itself (required for App Store review and Human Interface Guidelines compliance).
  • Merchant validation happens on-device. There is no merchant-validation network call in the native flow.
  • React Native and Expo apps wrap this in a small native module. The configuration is identical.

Step 5 — Process the payment (your app)

In the delegate’s didAuthorizePayment, post the encrypted token to Meld. This call is authenticated by the session token from the order, not your API key.
Request body
Do not send a buy_token field. The quote is already bound to the session server-side and any client-supplied value is ignored.
Response (200 OK)

Interpreting the result — read this twice

Treat the payment as accepted when the HTTP status is 200 and data.status is one of:
pending is a success. It is Mercuryo’s normal synchronous response to an accepted Apple Pay payment. Treating pending as a failure makes users retry and charges their card twice. This is the single most important rule in this guide.
Anything else (an HTTP error, or another status value) is a failure: complete the sheet with .failure and show an error. Whatever the outcome, an accepted payment means “processing,” not “success.” Show a processing state; funds are only confirmed by webhook (Step 6).
Swift (delegate)
A sheet dismissed without authorization is a user cancellation. Return to checkout quietly, without an error state.

Step 6 — Settlement (your backend)

Settlement is always confirmed server-side via webhooks, never from the app’s process response. Subscribe to the transaction lifecycle events: The payload includes paymentTransactionId, paymentTransactionStatus, customerId, externalCustomerId, transactionType, and headlessOrderId. Correlate on headlessOrderId (the order id from Step 3) or on your externalOrderId via the transaction. You can also poll (with Meld-Version: 2026-05-01):
  • GET /payments/transactions?customerIds={customerId} — list a customer’s transactions.
  • GET /payments/transactions/{transactionId} — one transaction.
Transaction statuses you will see include PENDING, AUTHORIZED, SETTLING, SETTLED, DECLINED, CANCELLED, REFUNDED, and FAILED.

Error reference

Errors return a consistent shape:

Order creation (POST /crypto/order/headless/onramp)

Payment processing (POST /crypto/session/mercuryo/apple-pay/process)

Testing

  1. Real device required. The iOS Simulator cannot produce a usable Apple Pay token.
  2. Sign into the device with an Apple sandbox tester and add an Apple test card to Wallet.
  3. Use a sandbox Meld customer that is KYC-approved with an email on the record, and a non-US, non-GB corridor (for example EUR to BTC, countryCode: "FR").
  4. Tap Buy. The native sheet opens directly, you authorize, and the process call returns pending (success). Show “processing.”
  5. Simulate settlement in sandbox with POST /payments/mock/headless/simulate and body { "externalOrderId": "order-123" }. Your webhook receives the SETTLING then SETTLED transitions.

Troubleshooting