Skip to main content
Guest checkout lets customers who do not yet have a MoonPay account buy crypto with Apple Pay. You supply the customer’s email and phone number when you create the session, and MoonPay creates a guest account at transaction time from the Apple Pay payment details. Returning customers are recognized automatically and connect instead. Guest checkout is available for customers in the United States, excluding New York and Washington. See Going live for the requirements you must meet before taking this integration to production.

Prerequisites

  • Guest checkout enabled on your partner account. Contact your MoonPay account team.
  • A server that can create session tokens with your secret key.
  • A UI surface where you can render the Apple Pay frame and the challenge frame.
  • The customer’s email and phone number, collected on your server. MoonPay accepts these as partner-verified, so you are responsible for their accuracy.
  • The timestamp at which the customer accepted MoonPay’s terms (see Record terms acceptance).
You can test the full flow without a real Apple Pay account using test mode.

How it works

  1. Your server creates a session that includes the customer’s email, phone number, and terms acceptance.
  2. You check the connection, which returns the customer’s capabilities. When guestCheckout is present, offer the Apple Pay guest path; otherwise connect the customer with the standard flow.
  3. You get a quote and render the Apple Pay frame.
  4. On the customer’s first purchase, MoonPay creates the guest account and processes the payment. If extra verification is needed, the frame emits a challenge that resolves the purchase.

Record terms acceptance

Capture the timestamp when the customer accepts MoonPay’s terms, and pass it as termsAcceptedAt when you create the session. MoonPay records the live terms version at that moment and binds the acceptance to the guest account. termsAcceptedAt must be no more than 60 seconds ahead of server time. Capture it the moment the customer taps your accept control, then create the session immediately.

Create a session

Create the session on your server with your secret key. For guest checkout, include the customer’s email, phoneNumber, and termsAcceptedAt alongside the standard fields.
const url = "https://api.moonpay.com/platform/v1/sessions";

const res = await fetch(url, {
  headers: {
    "Content-Type": "application/json",
    "X-Api-Key": "sk_test_123",
  },
  method: "POST",
  body: JSON.stringify({
    externalCustomerId: "your_user_id",
    deviceIp: "...ip address from client",
    email: "customer@example.com",
    phoneNumber: "+14155551234", // E.164 format
    termsAcceptedAt: "2026-01-12T14:44:30Z", // ISO 8601, within 60s ahead of server time
  }),
});

console.log(await res.json());
See the sessions API reference for all fields and error responses.
If guest checkout is not enabled for your account, or the customer’s region is not supported, the session still succeeds — the guestCheckout capability is simply absent in the next step, and you connect the customer with the standard flow instead.

Check for guest checkout

Pass the sessionToken to the client and check the connection. The check runs in an invisible frame and returns the customer’s capabilities. When capabilities.guestCheckout is present, offer the Apple Pay guest path. You can also check the session manually.
import { createClient } from "@moonpay/platform-sdk-web";

const client = createClient({
  sessionToken: "c3N0XzAwMQ==", // The session token from your server
});

const connectionResult = await client.getConnection();

if (!connectionResult.ok) {
  // Handle error
}

const connection = connectionResult.value;

if (connection.capabilities?.guestCheckout) {
  // Offer the Apple Pay guest path (continue below)
} else {
  // Connect the customer with the standard flow.
  // See /platform/guides/connect-a-customer
}
A returning customer whose email and phone match an existing MoonPay account is recognized at this step. Connect them with the standard flow — low-friction authentication prompts for a one-time passcode instead of a full login.

Get a quote

Request an executable quote for Apple Pay. Only quotes with executable: true can be used to execute a transaction.
const quoteResult = await client.getQuote({
  source: { asset: { code: "USD" }, amount: "100.00" }, // The fiat currency and amount to pay
  destination: { asset: { code: "ETH" } }, // The crypto the customer will receive
  wallet: { address: "0x1234..." }, // The destination wallet address
  paymentMethod: { type: "apple_pay" },
});

if (!quoteResult.ok) {
  // Handle error
}

console.log(quoteResult.value.signature);

Render the Apple Pay frame

Render the Apple Pay frame with the quote signature. When the customer taps the button and authorizes, MoonPay creates the guest account from the Apple Pay name and billing address, then processes the payment. For the frame URL, size, permissions, and events, see the Apple Pay frame reference.
Apple Pay
import type { ApplePayEvent } from "@moonpay/platform-sdk-web";

const applePayResult = await client.setupApplePay({
  quote: quoteResult.value.signature, // The quote signature from getQuote
  container: document.querySelector("#applePayContainer"), // DOM element to render the button

  onEvent: (event: ApplePayEvent) => {
    switch (event.kind) {
      case "ready":
        // The frame is ready. Reveal the button if needed.
        break;

      case "complete": {
        const txn = event.payload.transaction;

        if (txn.status === "failed") {
          // Show txn.failureReason. If the amount is above the customer's limit,
          // prompt them to try a smaller amount.
          break;
        }

        // The transaction is executing. Track the final status via polling or webhooks.
        console.log(txn); // { id: "txn_01", status: "pending" }
        break;
      }

      case "challenge":
        // Verification required — render the challenge frame at the provided URL.
        // See "Handle verification" below.
        console.log(event.payload.url);
        break;

      case "quoteExpired":
        // Fetch a new quote, then pass its signature into the frame:
        // event.payload.setQuote(newQuote.value.signature);
        break;

      case "error":
        console.error(event.payload.message);
        break;

      case "unsupported":
        // Apple Pay isn't supported in the current environment.
        break;
    }
  },
});

if (!applePayResult.ok) {
  // Handle error setting up Apple Pay
}

Handle verification

Some guest purchases need the customer to complete an extra step before the payment goes through. For example, the customer confirms their identity when the email and phone match an existing account, authenticates when the purchase is larger than the guest limit allows, or provides more identity details. In every case the Apple Pay frame emits a challenge event with a URL. Render the challenge frame at that URL. The challenge frame guides the customer through the required steps and completes the purchase itself, then emits complete — you do not re-render the Apple Pay button. See Handle challenges for the full flow. An amount above the customer’s maximum limit that verification cannot raise is terminal. The frame returns a failed transaction, so prompt the customer to try a smaller amount.

Upgrade a guest account

A guest account is a real MoonPay account with lower limits. To raise them, connect the customer and have them complete full identity verification — the limits lift on the same account, with no migration. Use the connect flow, then guide the customer through verification. You can prompt this after a completed guest purchase, or when a purchase exceeds the guest limit.

Transaction statuses

Transactions have the following statuses:
  • Pending: The transaction has been initiated and the payment accepted. The assets are being transferred.
  • Complete: The transaction is finalized. The payment is complete and the assets have been delivered to their destination.
  • Failed: The transaction has failed. The payment was not executed and funds were not transferred.