Follow our walkthrough to get Components for Web running in minutes.
This quickstart focuses on card payments, but Components for Web also supports Apple Pay, Google Pay, and PayPal. See the What's next? section for links to other payment methods.
Before you start, make sure you have:
- Node.js 22.x or higher installed on your computer
- Your API credentials from the Unity Portal
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-sdkComponents for Web 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 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.
Generate the HMAC signature
Call the signature function with your credentials and request details.
Send the session creation request
POST to https://api-services.dev.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.
If you want to use 3DS, call the Modify session API to set authentication = true. If you want to skip 3DS, set authentication = false.
import crypto from 'node: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
const REQUEST_PATH = 'api/v1/sessions';
/**
* Creates an HMAC signature for authenticating API requests
*/
function createHmacSignature(
timestamp,
requestId,
requestPath,
requestBody,
tokenValue,
) {
const stringToHash = `${timestamp}${requestId}${requestPath}${requestBody}`;
const hmac = crypto.createHmac('sha256', tokenValue);
hmac.update(stringToHash);
return hmac.digest('hex').toUpperCase();
}
/**
* This function is a helper to create a new checkout session from merchant backend.
*/
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,
site: siteId,
sessionTimeout: 120,
merchantTransactionId,
transactionMethod: {
intent: {
card: 'Authorisation',
},
},
amounts: {
currencyCode: currency,
transactionValue: amount,
},
allowTransaction: true,
};
const requestBodyString = JSON.stringify(requestBody);
const signature = createHmacSignature(
timestamp,
requestId,
REQUEST_PATH,
requestBodyString,
TOKEN_VALUE,
);
const response = await fetch(`https://api-services.dev.pxp.io/${REQUEST_PATH}`, {
method: 'POST',
headers: {
'Authorization': `PXP-UST1 ${TOKEN_ID}:${timestamp}:${signature}`,
'X-Request-Id': requestId,
'X-Client-Id': CLIENT_ID,
'Content-Type': 'application/json',
},
body: requestBodyString,
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(
`Session creation failed (${response.status}): ${errorText || response.statusText}`,
);
}
const sessionData = await response.json();
return {
...sessionData,
merchantTransactionId,
};
}Import the required dependencies
Import PxpCheckout and necessary types from the Web SDK.
Set up your React component
Create a React component that will host the card payment interface.
Fetch the session data from your backend
Call your backend endpoint to get the session data you created in the previous steps.
Initialise the SDK
Configure the SDK with your environment, session data, owner details, and transaction information.
Configure the transaction data
Specify the currency, amount, entry type, and payment intent for card transactions.
Provide shopper information
Implement the onGetShopper callback to provide shopper details. This callback is called whenever the SDK needs shopper data during the payment flow.
The shopper id is particularly important because it enables card-on-file (COF), one-click payments, and the consent tickbox for storing cards. You can fetch shopper data from your API or return hardcoded values.
If you don't provide a shopper ID:
- Card-on-file components won't be able to retrieve saved cards
- One-click payment functionality won't work
- The consent tickbox for storing cards will not appear
If you do provide a shopper ID:
- Card-on-file and one-click components will retrieve cards from the Token Vault for that shopper
- When the shopper ticks the consent checkbox during payment, their card will be stored in the Token Vault under that shopper ID for future use
Create the new card component
Use the SDK's create() method to create a new card component with your desired configuration.
Configure the card input fields
Set up the card number, expiry date, CVC, and cardholder name fields with placeholders and validation rules.
Configure the submit button
Customise the submit button text and styling to match your brand.
Handle successful payments
Implement the onPostAuthorisation callback to handle successful payments. Always verify payments on your backend before fulfilling orders — frontend callbacks can be manipulated.
Handle payment errors
Implement the onSubmitError callback to handle payment failures and display error messages to the user.
Implement 3DS callbacks (if using 3DS)
If you want to use 3DS authentication, implement the required onPreInitiateAuthentication and onPreAuthentication callbacks. You can optionally add onPostInitiateAuthentication and onPostAuthentication for monitoring.
Your backend must set authentication = true in the session if you include these callbacks, or false if you omit them. If they don't match, you'll see CAD4005: Authentication decision is required but not provided.
To skip 3DS, set authentication = false in your backend session via the Modify session API, and omit the 3DS callbacks from your component configuration.
Implement pre-authorisation callback
Implement the onPreAuthorisation callback. Return an empty object {} to proceed, or optionally include AVS or risk screening data.
Mount the component to the page
Call the mount() method with your container element ID to render the payment form.
Add a container element to your page
Return JSX that includes a container div where the card component will mount itself.
import { PxpCheckout, IntentType } from '@pxpio/web-components-sdk';
import { useEffect, useState } from 'react';
export default function CheckoutPage() {
const [sessionData, setSessionData] = useState<any>(null);
const createSession = async () => {
const sessionData = await fetch('/api/sessions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
merchantId: MERCHANT_ID, // Replace with your merchant ID
siteId: SITE_ID, // Replace with your site ID
amount: 25,
currency: 'USD'
}),
}).then(response => response.json());
setSessionData(sessionData);
};
useEffect(() => {
//1. Create a new session
createSession();
}, []);
useEffect(() => {
if (sessionData) {
// 2. Initialise the SDK
const pxpSdk = PxpCheckout.initialize({
environment: 'test',
session: sessionData,
ownerId: MERCHANT_GROUP_ID // Replace width your merchant group id,
ownerType: 'MerchantGroup',
transactionData: {
currency: 'USD',
amount: 25,
entryType: 'Ecom',
intent: {
card: IntentType.Authorisation
},
merchantTransactionId: sessionData.merchantTransactionId,
merchantTransactionDate: () => new Date().toISOString()
},
onGetShopper: async () => {
// Option 1: Fetch from your API
// const shopper = await fetch('/api/get-shopper').then(r => r.json());
// return shopper;
// Option 2: Return hardcoded values
return {
id: 'shopper-123', // Required for COF and one-click
email: 'customer@example.com',
firstName: 'John',
lastName: 'Doe'
};
}
});
// 3. Create the new card component
const newCard = pxpSdk.create('new-card', {
fields: {
cardNumber: {
required: true,
placeholder: '1234 5678 9012 3456',
acceptedCardBrands: ['visa', 'mastercard', 'cup', 'diners', 'discover', 'jcb'],
},
expiryDate: {
required: true,
placeholder: 'MM/YY'
},
cvc: {
required: true,
placeholder: '123'
},
holderName: {
required: true,
placeholder: 'John Doe'
}
},
submit: {
submitText: 'Pay $25.00',
styles: {
backgroundColor: '#4CAF50',
color: 'white',
padding: '15px',
borderRadius: '6px',
fontSize: '16px',
fontWeight: 'bold',
width: '100%'
},
// Only include onPreInitiateAuthentication, onPostInitiateAuthentication, onPreAuthentication, onPostAuthentication if backend set authentication = true
onPreInitiateAuthentication: () => {
return {
providerId: 'pxpfinancial',
timeout: 12
};
},
onPostInitiateAuthentication: (data: any) => {
console.log('3DS authentication initiated:', data);
},
onPreAuthentication: async () => {
return {
merchantCountryNumericCode: '100',
merchantLegalName: 'TestMerchant_10',
challengeWindowSize: 1,
requestorChallengeIndicator: '02'
};
},
onPostAuthentication: (data: any) => {
console.log('3DS authentication completed:', data);
// Optional: Retrieve authentication details from backend
// and update session decision if needed
},
onPreAuthorisation: () => {
return {};
},
onPostAuthorisation: async (data: any) => {
// CRITICAL: Do NOT fulfill order here!
// Frontend callbacks can be manipulated by malicious users.
// Always verify payment on your backend first.
console.log('Transaction completed:', data.systemTransactionId, data.merchantTransactionId);
// Verify payment on your backend
const verified = await fetch('/api/verify-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
systemTransactionId: data.systemTransactionId,
merchantTransactionId: data.merchantTransactionId,
amount: 25
})
}).then(r => r.json());
if (verified.success) {
globalThis.location.href = `/success?orderId=${verified.orderId}`;
} else {
alert('Payment verification failed. Please contact support.');
}
},
onSubmitError: (error: any) => {
console.error('Payment failed:', error);
alert('Payment failed. Please check your card details and try again.');
},
}
});
// 4. Mount the component to your page
newCard.mount('new-card-container');
return () => {
// 5. Unmount the component
newCard.unmount();
};
}
}, [sessionData]);
return (
<div>
<h1>Complete Your Purchase</h1>
<div id="new-card-container"></div>
</div>
);
}When a payment succeeds, the onPostAuthorisation 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 card payment integration with Components for Web.
Now that you have card components running, here are the recommended next steps:
Explore other payment methods:
- Apple Pay - Accept payments with Apple Pay
- Google Pay - Accept payments with Google Pay
- PayPal - Accept payments with PayPal
Enhance your card integration:
- Customise the look and feel to match your brand
- Explore additional components like billing address, card-on-file, and one-click payments
- Add event callbacks to enhance the user experience with validation and loading states
- Enable 3D Secure for enhanced security and liability shift