Skip to content

Quickstart

Follow our walkthrough to get Checkout Drop-in running in minutes.

Pre-requisites

Before you start, make sure you have:

  • Node.js 22.x or higher (for Node.js backend) or .NET SDK (for .NET backend) installed on your computer
  • Your API credentials from the Unity Portal

Install the SDK

To get started, install the latest version of the Web SDK from the npm public registry. You'll need to have Node.js 22.x or higher.

npm i @pxpio/web-components-sdk
nodejs
dotnet

Create a session on your backend

Drop-in needs a session from the PXP API. This must happen on your backend using HMAC authentication.

Store your credentials securely

Set up your API credentials as environment variables — never hardcode them in your application.

Create the HMAC signature function

This function generates a secure authentication hash by combining your token ID, timestamp, request ID, request path, and request body, then hashing with your token value using HMAC SHA256.

Build the session request body

Create a request with your merchant details and transaction information. The request body must be minified (no whitespace) for the HMAC signature.

Send the session creation request

POST to https://api-services.pxp.io/api/v1/sessions with your authentication headers and request body.

Return the session data to your frontend

The API returns sessionId, hmacKey, encryptionKey, and allowedFundingTypes. Pass this entire response to your frontend.

import crypto from 'crypto';

// Your API credentials from the Unity Portal
const TOKEN_ID = '9aac6071-38d0-4545-9d2f-15b936af6d7f'; // Replace this with your token ID
const TOKEN_VALUE = 'your-token-value-here'; // Replace this with your token value
const CLIENT_ID = 'f47ac10b-58cc-4372-a567-0e02b2c3d479'; // Replace this with your client ID

/**
 * Creates an HMAC signature for authenticating API requests
 */
function createHmacSignature(tokenId, timestamp, requestId, requestPath, requestBody, tokenValue) {
  // Combine all parts with colons
  const stringToHash = `${tokenId}:${timestamp}:${requestId}:${requestPath}:${requestBody}`;
  
  // Create HMAC SHA256 hash using the token value as the secret
  const hmac = crypto.createHmac('sha256', tokenValue);
  hmac.update(stringToHash);
  
  // Return the hash as a hex string
  return hmac.digest('hex').toUpperCase();
}

/**
 * Creates a new checkout session
 */
export async function createSession(merchantId, siteId, amount, currency) {
  // Generate unique IDs for this request
  const timestamp = Math.floor(Date.now() / 1000);
  const requestId = crypto.randomUUID();
  const merchantTransactionId = crypto.randomUUID();
  
  // Build the request body
  const requestBody = {
    merchant: merchantId, // Replace with your merchant ID
    site: siteId, // Replace with your site ID
    sessionTimeout: 120,
    merchantTransactionId: merchantTransactionId, // Replace with a unique transaction ID
    transactionMethod: {
      intent: {
        card: 'Authorisation',
        paypal: 'Purchase'
      }
    },
    amounts: {
      currencyCode: currency,
      transactionValue: amount
    },
    allowTransaction: true,
    serviceType: 'CheckoutDropIn'
  };
  
  // Minify the request body (no whitespace)
  const requestBodyString = JSON.stringify(requestBody);
  const requestPath = 'api/v1/sessions';
  
  // Create the HMAC signature
  const signature = createHmacSignature(
    TOKEN_ID,
    timestamp,
    requestId,
    requestPath,
    requestBodyString,
    TOKEN_VALUE
  );
  
  // Build the Authorization header
  const authHeader = `${TOKEN_ID}:${timestamp}:${requestId}:${signature}`;
  
  // Send the request
  const response = await fetch('https://api-services.pxp.io/api/v1/sessions', {
    method: 'POST',
    headers: {
      'Authorization': authHeader,
      'X-Request-Id': requestId,
      'X-Client-Id': CLIENT_ID,
      'Content-Type': 'application/json'
    },
    body: requestBodyString
  });
  
  if (!response.ok) {
    throw new Error(`Session creation failed: ${response.statusText}`);
  }
  
  const sessionData = await response.json();
  
  return {
    ...sessionData,
    merchantTransactionId
  };
}

Initialise Drop-in on your frontend

Import the required dependencies

Import CheckoutDropIn, IntentType, and the necessary types from the Web SDK.

Set up your React component

Create a React component that will host the Checkout Drop-in interface.

Fetch the session data from your backend

Call your backend endpoint to get the session data you created in the previous steps.

Initialise Checkout Drop-in

Configure Drop-in with your environment, session data, and transaction details.

Configure the transaction data

Specify the currency, amount, entry type, and payment intents for each payment method.

Provide a shopper identifier

Implement the onGetShopper callback to provide shopper information. This is required for Card-on-File functionality.

Handle successful payments

Implement the onSuccess callback to handle successful payments. Always verify payments on your backend before fulfilling orders — frontend callbacks can be manipulated.

Handle payment errors

Implement the onError callback to handle payment failures and display appropriate error messages.

Mount Drop-in to the page

Call the create() method with your container element ID to render the payment interface.

Add a container element to your page

Return JSX that includes a container div where Checkout Drop-in will mount itself.

import CheckoutDropIn from '@pxpio/web-components-sdk/src/checkoutDropIn/CheckoutDropIn';
import IntentType from '@pxpio/web-components-sdk/src/basePxpCheckout/types/IntentType';
import PaymentMethod from '@pxpio/web-components-sdk/src/components/checkoutDropInComponents/types/PaymentMethod';
import { BaseSubmitResult } from '@pxpio/web-components-sdk/src/checkoutDropIn/types/BaseSubmitResult';
import BaseSdkException from '@pxpio/web-components-sdk/src/types/sdkExceptions/BaseSdkException';
import { useEffect } from 'react';

export default function CheckoutPage() {
  useEffect(() => {
    initialiseCheckoutDropIn();
  }, []);

  async function initialiseCheckoutDropIn() {
    // 1. Get the session data from your backend
    const sessionData = await fetch('/api/create-session', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        merchantId: 'MERCHANT-1', // Replace with your merchant ID
        siteId: 'SITE-1', // Replace with your site ID
        amount: 25.00,
        currency: 'USD'
      })
    }).then(response => response.json());

    // 2. Initialise Checkout Drop-in
    const checkoutDropIn = CheckoutDropIn.initialize({
      environment: 'test',
      session: sessionData,
      ownerId: 'MERCHANT-1',
      ownerType: 'MerchantGroup',
      transactionData: {
        currency: 'USD',
        amount: 25.00,
        entryType: 'Ecom',
        intent: {
          card: IntentType.Authorisation,
          paypal: IntentType.Purchase
        },
        merchantTransactionId: sessionData.merchantTransactionId,
        merchantTransactionDate: () => new Date().toISOString()
      },
      onGetShopper: () => Promise.resolve({ id: 'shopper-123' }),
      onBeforeSubmit: async (paymentMethod: PaymentMethod) => {
        console.log('Payment method selected:', paymentMethod);
        return true;
      },
      onSubmit: (paymentMethod: PaymentMethod) => {
        console.log('Payment being processed:', paymentMethod);
      },
      onSuccess: async (result: BaseSubmitResult) => {
        // CRITICAL: Do NOT fulfill order here!
        // Frontend callbacks can be manipulated by malicious users.
        // Always verify payment on your backend first.
        
        console.log('Payment successful:', result.systemTransactionId);
        
        // Verify payment on your backend
        const verified = await fetch('/api/verify-payment', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            systemTransactionId: result.systemTransactionId,
            merchantTransactionId: result.merchantTransactionId,
            amount: 25.00
          })
        }).then(r => r.json());
        
        if (verified.success) {
          window.location.href = `/success?orderId=${verified.orderId}`;
        } else {
          alert('Payment verification failed. Please contact support.');
        }
      },
      onError: (error: BaseSdkException) => {
        console.error('Payment failed:', error);
        alert(`Payment failed: ${error.message}`);
      }
    });

    // 3. Mount the drop-in to your page
    await checkoutDropIn.create('checkout-drop-in-container');
  }

  return (
    <div>
      <h1>Complete Your Purchase</h1>
      <div id="checkout-drop-in-container"></div>
    </div>
  );
}
nodejs
dotnet

Verify payments

When a payment succeeds, the onSuccess callback fires with transaction details. However, you must always verify the payment on your backend before fulfilling orders.

Configure webhooks in the Unity Portal to receive real-time payment notifications on your backend.

Create the webhook endpoint

Set up an endpoint at /webhooks/pxp to receive payment notifications from Unity.

Process webhook events

Loop through the events array and filter for Transaction events.

Check payment state

Verify the transaction state is Authorised or Captured before processing.

Prevent duplicate processing

Check if you've already processed this transaction using systemTransactionId.

Verify transaction details

Match the merchantTransactionId, amount, and currency against your order records.

Fulfill the order

If verification passes, fulfill the order and mark the transaction as processed.

Respond to webhook

Always return { state: 'Success' } to acknowledge receipt, even if processing failed.

You can also verify payments using the Transactions API to query transaction status directly. See the Integration guide for details.

// Webhook endpoint to receive payment notifications
// Configure this URL in the Unity Portal under Webhooks
app.post('/webhooks/pxp', async (req, res) => {
  const events = req.body;
  
  // Process each webhook event
  for (const event of events) {
    if (event.eventCategory === 'Transaction') {
      const txn = event.eventData;
      
      // Check if payment was successful
      if (txn.state === 'Authorised' || txn.state === 'Captured') {
        // Prevent duplicate processing
        const alreadyProcessed = await isTransactionProcessed(txn.systemTransactionId);
        if (alreadyProcessed) {
          continue;
        }
        
        // Verify transaction details match your records
        const expectedOrder = await getOrderByMerchantTransactionId(txn.merchantTransactionId);
        
        if (expectedOrder && 
            txn.amounts.transactionValue === expectedOrder.amount &&
            txn.amounts.currencyCode === expectedOrder.currency) {
          
          // Payment verified - fulfill the order
          await fulfillOrder(txn.merchantTransactionId);
          
          // Mark as processed to prevent duplicates
          await markTransactionProcessed(txn.systemTransactionId);
          
          console.log(`Order ${txn.merchantTransactionId} fulfilled successfully`);
        } else {
          console.error('Transaction verification failed - amount or currency mismatch');
        }
      }
    }
  }
  
  // Always return success to acknowledge receipt
  res.json({ state: 'Success' });
});

That's it! You now have a working Checkout Drop-in integration.

What's next?

Now that you have Drop-in running, here are the recommended next steps: