Skip to main content
The iframe integration lets you embed the Maash checkout widget directly into your payment page. Instead of redirecting customers to a hosted checkout URL, you display the payment flow inline. The iframe communicates with your page via postMessage events.

When to use iframe vs. redirect

FeatureIframeRedirect
Customer stays on your pageYesNo
Frontend code requiredYesMinimal
postMessage handlingRequiredNot needed
Mobile responsivenessYou manageBuilt-in
Custom checkout UI around widgetYesNo
Use the iframe integration when you want to keep customers on your site throughout the payment flow. Use the redirect flow when you prefer a simpler server-side integration.

Set up the iframe

1

Create a checkout session

First, call the /checkout/sessions endpoint from your backend to create a checkout session:
curl -X POST https://api.maash.io/checkout/sessions \
  -H "x-api-key: mk_live_1234567890abcdef" \
  -H "x-maash-user-type: checkout" \
  -H "Content-Type: application/json" \
  -d '{
    "merchant_id": "mer_abc123",
    "transaction_id": "order_98765",
    "amount_usd": 150.00
  }'
Response:
{
  "data": {
    "session_id": "cs_01ARZ3NDEKTSV4RRFFQ69G5FAV",
    "checkout_url": "https://pay.maash.io/checkout/cs_01ARZ3NDEKTSV4RRFFQ69G5FAV",
    "checkout_url_with_token": "https://pay.maash.io/checkout/cs_01ARZ3NDEKTSV4RRFFQ69G5FAV?token=eyJ...",
    "expires_at": "2025-02-03T12:00:00Z",
    "created_at": "2025-02-01T12:00:00Z"
  }
}
Use the checkout_url_with_token value for iframe embedding. This URL contains a secure session token that allows the iframe to access the checkout session without additional authentication.
2

Embed the iframe

Pass the checkout_url_with_token from the API response to your frontend and use it as the iframe src:
<iframe
  id="maash-checkout-iframe"
  src="https://pay.maash.io/checkout/cs_01ARZ3NDEKTSV4RRFFQ69G5FAV?token=eyJ..."
  style="width: 100%; max-width: 480px; height: 700px; border: none; border-radius: 12px;"
  allow="clipboard-write"
></iframe>
AttributeValuePurpose
srccheckout_url_with_token from APILoads the checkout widget with authentication
allowclipboard-writeLets the widget copy the wallet address to clipboard
Recommended styles:
#maash-checkout-iframe {
  width: 100%;
  max-width: 480px;
  height: 700px;
  border: none;
  border-radius: 12px;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
3

Listen for events

The iframe communicates with your page using window.postMessage. Set up a listener to handle payment events:
window.addEventListener("message", (event) => {
  // IMPORTANT: Always verify the origin
  if (event.origin !== "https://pay.maash.io") {
    return;
  }

  const { type, merchant_id, transaction_id, ...data } = event.data;

  switch (type) {
    case "maash_payment_complete":
      handlePaymentComplete(data);
      break;
    case "maash_payment_cancelled":
      handlePaymentCancelled(data);
      break;
    case "maash_payment_failed":
      handlePaymentFailed(data);
      break;
    case "maash_session_expired":
      handleSessionExpired(data);
      break;
  }
});
Always verify event.origin matches https://pay.maash.io before processing any message. Skipping this check exposes your page to spoofed events from other origins.

Event reference

Sent when the payment is confirmed on-chain and settled.Action: Close the iframe and mark the order as paid.
{
  "type": "maash_payment_complete",
  "merchant_id": "mer_abc123",
  "transaction_id": "wxp_order_98765",
  "payment_id": "pay_xyz789",
  "session_id": "cs_f47ac10b58cc4372",
  "status": "completed",
  "amount": {
    "usd": 150.00,
    "token": "150.050000",
    "token_symbol": "USDC",
    "chain": "ethereum"
  },
  "transaction_hash": "0xabc123def456789...",
  "confirmed_at": "2025-01-31T12:15:00Z"
}
function handlePaymentComplete(data) {
  document.getElementById("maash-checkout-iframe").remove();
  showSuccessMessage(`Payment of $${data.amount.usd} received!`);
  window.location.href = `/orders/${data.transaction_id}/success`;
}
Sent when the customer clicks the cancel button.Action: Close the iframe and show alternative payment options.
{
  "type": "maash_payment_cancelled",
  "merchant_id": "mer_abc123",
  "transaction_id": "wxp_order_98765",
  "session_id": "cs_f47ac10b58cc4372",
  "cancelled_at": "2025-01-31T12:05:00Z"
}
function handlePaymentCancelled(data) {
  document.getElementById("maash-checkout-iframe").remove();
  showPaymentOptions();
}
Sent when the payment transaction fails on-chain.Action: Close the iframe and show an error with a retry option.
{
  "type": "maash_payment_failed",
  "merchant_id": "mer_abc123",
  "transaction_id": "wxp_order_98765",
  "session_id": "cs_f47ac10b58cc4372",
  "payment_id": "pay_xyz789",
  "error": "Transaction reverted on chain",
  "error_code": "TRANSACTION_FAILED",
  "failed_at": "2025-01-31T12:10:00Z"
}
function handlePaymentFailed(data) {
  document.getElementById("maash-checkout-iframe").remove();
  showError(`Payment failed: ${data.error}`, {
    onRetry: () => openMaashCheckout(data.transaction_id),
  });
}
Sent when the checkout session expires (default: 48 hours).Action: Close the iframe and prompt the customer to start over.
{
  "type": "maash_session_expired",
  "merchant_id": "mer_abc123",
  "transaction_id": "wxp_order_98765",
  "session_id": "cs_f47ac10b58cc4372",
  "expired_at": "2025-02-02T12:00:00Z"
}
function handleSessionExpired(data) {
  document.getElementById("maash-checkout-iframe").remove();
  showMessage("Your checkout session has expired.", {
    onRetry: () => createNewCheckout(),
  });
}

Event summary

EventTriggerWebXPay Action
maash_payment_completePayment confirmedClose iframe, show success, verify via webhook
maash_payment_cancelledUser clicks cancelClose iframe, show alternatives
maash_payment_failedTransaction failsClose iframe, show error + retry
maash_session_expired48h timeoutClose iframe, prompt new session

Full integration examples

<!DOCTYPE html>
<html>
<head>
  <style>
    .checkout-overlay {
      position: fixed;
      top: 0; left: 0; right: 0; bottom: 0;
      background: rgba(0, 0, 0, 0.5);
      display: flex;
      align-items: center;
      justify-content: center;
      z-index: 1000;
    }
    .checkout-container {
      background: white;
      border-radius: 16px;
      padding: 8px;
      max-width: 496px;
      width: 100%;
      margin: 16px;
    }
    .checkout-iframe {
      width: 100%;
      height: 700px;
      border: none;
      border-radius: 12px;
    }
    .hidden { display: none; }
  </style>
</head>
<body>
  <div id="checkout-overlay" class="checkout-overlay hidden">
    <div class="checkout-container">
      <iframe
        id="maash-checkout-iframe"
        class="checkout-iframe"
        allow="clipboard-write"
      ></iframe>
    </div>
  </div>

  <button onclick="openCryptoCheckout()">Pay with Crypto</button>

  <script>
    async function openCryptoCheckout() {
      // Step 1: Create checkout session on your backend
      const response = await fetch('/api/create-checkout-session', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          transaction_id: "order_" + Date.now(),
          amount_usd: 150.00
        })
      });

      const { checkout_url_with_token } = await response.json();

      // Step 2: Load the checkout URL in the iframe
      document.getElementById("maash-checkout-iframe").src = checkout_url_with_token;
      document.getElementById("checkout-overlay").classList.remove("hidden");
    }

    function closeCheckout() {
      document.getElementById("checkout-overlay").classList.add("hidden");
      document.getElementById("maash-checkout-iframe").src = "";
    }

    window.addEventListener("message", (event) => {
      if (event.origin !== "https://pay.maash.io") return;

      switch (event.data.type) {
        case "maash_payment_complete":
          closeCheckout();
          alert(`Payment successful! TX: ${event.data.transaction_id}`);
          break;
        case "maash_payment_cancelled":
          closeCheckout();
          break;
        case "maash_payment_failed":
          closeCheckout();
          alert(`Payment failed: ${event.data.error}`);
          break;
        case "maash_session_expired":
          closeCheckout();
          alert("Session expired. Please try again.");
          break;
      }
    });
  </script>
</body>
</html>
Backend endpoint example (Node.js/Express):
app.post('/api/create-checkout-session', async (req, res) => {
  const { transaction_id, amount_usd } = req.body;

  const response = await fetch('https://api.maash.io/checkout/sessions', {
    method: 'POST',
    headers: {
      'x-api-key': process.env.MAASH_API_KEY,
      'x-maash-user-type': 'checkout',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      merchant_id: process.env.MAASH_MERCHANT_ID,
      transaction_id,
      amount_usd
    })
  });

  const data = await response.json();
  res.json({ checkout_url_with_token: data.data.checkout_url_with_token });
});
import React, { useEffect, useCallback, useState } from "react";

interface MaashCheckoutProps {
  checkoutUrl: string;
  onComplete: (data: PaymentCompleteData) => void;
  onCancel: () => void;
  onError: (error: string) => void;
  onClose: () => void;
}

interface PaymentCompleteData {
  transactionId: string;
  paymentId: string;
  amount: {
    usd: number;
    token: string;
    tokenSymbol: string;
    chain: string;
  };
  transactionHash: string;
}

export function MaashCheckout({
  checkoutUrl,
  onComplete,
  onCancel,
  onError,
  onClose,
}: MaashCheckoutProps) {
  useEffect(() => {
    const handleMessage = (event: MessageEvent) => {
      if (event.origin !== "https://pay.maash.io") return;

      switch (event.data.type) {
        case "maash_payment_complete":
          onComplete({
            transactionId: event.data.transaction_id,
            paymentId: event.data.payment_id,
            amount: event.data.amount,
            transactionHash: event.data.transaction_hash,
          });
          onClose();
          break;
        case "maash_payment_cancelled":
          onCancel();
          onClose();
          break;
        case "maash_payment_failed":
          onError(event.data.error || "Payment failed");
          onClose();
          break;
        case "maash_session_expired":
          onError("Checkout session expired");
          onClose();
          break;
      }
    };

    window.addEventListener("message", handleMessage);
    return () => window.removeEventListener("message", handleMessage);
  }, [onComplete, onCancel, onError, onClose]);

  return (
    <div className="maash-checkout-overlay">
      <div className="maash-checkout-container">
        <iframe
          src={checkoutUrl}
          className="maash-checkout-iframe"
          allow="clipboard-write"
          title="Maash Crypto Checkout"
        />
      </div>
    </div>
  );
}
Usage with backend session creation:
function CheckoutPage() {
  const [checkoutUrl, setCheckoutUrl] = useState<string | null>(null);

  const handleOpenCheckout = async () => {
    // Create checkout session on your backend
    const response = await fetch('/api/create-checkout-session', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        transaction_id: 'order_12345',
        amount_usd: 150.00
      })
    });

    const data = await response.json();
    setCheckoutUrl(data.checkout_url_with_token);
  };

  return (
    <>
      <button onClick={handleOpenCheckout}>Pay with Crypto</button>

      {checkoutUrl && (
        <MaashCheckout
          checkoutUrl={checkoutUrl}
          onComplete={(data) => console.log("Paid:", data)}
          onCancel={() => console.log("Cancelled")}
          onError={(err) => console.error(err)}
          onClose={() => setCheckoutUrl(null)}
        />
      )}
    </>
  );
}
Backend endpoint example (Next.js API route):
// app/api/create-checkout-session/route.ts
export async function POST(req: Request) {
  const { transaction_id, amount_usd } = await req.json();

  const response = await fetch('https://api.maash.io/checkout/sessions', {
    method: 'POST',
    headers: {
      'x-api-key': process.env.MAASH_API_KEY!,
      'x-maash-user-type': 'checkout',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      merchant_id: process.env.MAASH_MERCHANT_ID!,
      transaction_id,
      amount_usd
    })
  });

  const data = await response.json();
  return Response.json({ checkout_url_with_token: data.data.checkout_url_with_token });
}

Responsive design

DeviceWidthHeight
Desktop480px700px
Tablet480px700px
Mobile100%100vh
On narrow screens, display the iframe full-screen for a better experience:
@media (max-width: 520px) {
  .checkout-overlay {
    padding: 0;
  }
  .checkout-container {
    max-width: 100%;
    height: 100%;
    border-radius: 0;
    padding: 0;
  }
  .checkout-iframe {
    height: 100vh;
    border-radius: 0;
  }
}

Security

Every postMessage handler must check event.origin before processing:
if (event.origin !== "https://pay.maash.io") {
  return; // Ignore messages from unknown origins
}
The maash_payment_complete event is for UI updates only. Always verify payment completion on your server using webhooks before fulfilling orders.
// Frontend: update the UI immediately
case "maash_payment_complete":
  showSuccessMessage();
  break;

// Backend: only fulfill after webhook verification
app.post("/webhooks/maash", (req, res) => {
  if (verifyWebhookSignature(req)) {
    if (req.body.event === "payment.completed") {
      fulfillOrder(req.body.transaction_id);
    }
  }
});
If your site uses a Content Security Policy, add the Maash checkout origin to frame-src:
Content-Security-Policy: frame-src https://pay.maash.io;

Testing the iframe

In sandbox mode, create a test session using your test API key:
curl -X POST https://api.dev.maash.io/checkout/sessions \
  -H "x-api-key: mk_test_sandbox123" \
  -H "x-maash-user-type: checkout" \
  -H "Content-Type: application/json" \
  -d '{
    "merchant_id": "mer_test_sandbox123",
    "transaction_id": "test_txn_'$(date +%s)'",
    "amount_usd": 10.00
  }'
Use the checkout_url_with_token from the response in your iframe. The sandbox checkout provides buttons to simulate payment completion, failure, and session expiry so you can test your postMessage handlers without real transactions.

Troubleshooting

Cause: Invalid or expired checkout URL. Solution: Ensure you’re using the checkout_url_with_token returned from the /checkout/sessions API endpoint. Sessions expire after 48 hours by default.
Cause: Origin mismatch in your event listener. Solution: Confirm your listener checks for https://pay.maash.io exactly.
Cause: Missing iframe permission. Solution: Add allow="clipboard-write" to the iframe element.
Cause: Fixed or insufficient height. Solution: Set height: 700px minimum or use the responsive CSS from the section above.
Cause: Content Security Policy blocking the iframe. Solution: Add frame-src https://pay.maash.io to your CSP header.

Next steps

Webhooks

Set up server-side webhook verification for payment confirmation.

Checkout flow

Understand the full payment lifecycle and session statuses.

Testing

Test your iframe integration in sandbox mode.

Supported chains

View all tokens, chains, and confirmation times.