Configure Hosted Fields SDK

Hosted fields SDK is a secure, embeddable payment form SDK that integrates card payment fields (number, expiry, CVV, and cardholder name) into your web application via iFrames. This SDK provides a PCI-compliant way to handle sensitive payment data.

Installation

  1. Include the SDK in your project directly via a script tag. The SDK script should be loaded from your environment's Base URL.
<!-- Load the SDK directly -->
<script src="https://api.example.com/sdk/index.umd.js"></script>
  1. Alternatively, you can load it dynamically in JavaScript:
const script = document.createElement("script");
script.src = "https://api.example.com/sdk/index.umd.js";
document.head.appendChild(script);

script.onload = async () => {
  // SDK is ready on window.HostedFieldsSDK
};

Initialisation

The SDK uses a two-phase initialisation pattern:

  1. createElements(config) — creates a session manager (synchronous)
  2. elements.create(type, options) — creates individual field instances (synchronous)
  3. field.mount(selector) — mounts a field to the DOM (async, returns a Promise)

This pattern enables multi-step / wizard forms where fields are created in advance and mounted only when their DOM containers become visible.

Basic Usage

// 1. Create the session manager
const elements = window.HostedFieldsSDK.createElements({
  baseUrl: 'https://api.example.com',
  apiKey: '<your_api_key>',
  merchantAccountId: '<your_merchant_id>',
  paymentRequestId: '<your_payment_request_id>',
  style: {
    height: '40px',
    fontSize: '16px',
    color: '#333',
  },
});

// 2. Create field instances
const cardNumber = elements.create('cardNumber', {
  placeholder: '0000 0000 0000 0000',
});
const expiryDate = elements.create('expiryDate', {
  placeholder: 'MM/YY',
});
const cvv = elements.create('cvv', {
  placeholder: 'CVV',
});
const cardholderName = elements.create('cardholderName', {
  placeholder: 'Cardholder Name',
});

// 3. Mount all fields (can be done at different times for wizard flows)
await Promise.all([
  cardNumber.mount('#card-number'),
  expiryDate.mount('#card-expiry'),
  cvv.mount('#card-csc'),
  cardholderName.mount('#cardholder-name'),
]);

Session Configuration (ElementsConfig)

interface ElementsConfig {
  baseUrl?: string; // Base URL for iframe endpoints (defaults to window.location.origin)
  apiKey: string; // Your API key for authentication (required)
  merchantAccountId: string; // Your merchant account ID (required)
  paymentRequestId: string; // The payment request session identifier (required)
  timeout?: number; // Network timeout in ms for field submissions (default: 30000)
  initTimeout?: number; // How long to wait for each iframe readiness (default: 30000)
  loadingBehavior?: 'disabled' | 'readonly'; // How fields behave during loading/submission (optional)
  style?: Partial<CommonStyling>; // Global styling applied to all fields (optional)
}

Field Options (FieldOptions)

Passed to elements.create(type, options):

interface FieldOptions {
  placeholder?: string; // Placeholder text (optional)
  style?: Partial<CommonStyling>; // Field-specific styling, overrides global (optional)
  loadingBehavior?: 'disabled' | 'readonly'; // Override global loading behavior (optional)
}

Field Types

The SDK supports four field types:

Type Description Required
cardNumber Card number with Luhn validatio Yes
expiryDate Expiry date (MM/YY format Yes
cvv Security code (CVV/CVC) Yes
cardholderName Cardholder name No

Mount Point

The field.mount() method accepts either:

  • CSS Selector (string): '#card-number', '.payment-field'
  • HTMLElement: Direct reference to a DOM element

The target element is replaced by the iframe. Make sure the mount point is a disposable <div> placeholder.

Styling

Styling Options (CommonStyling)

interface CommonStyling {
  height?: string; // Field height (e.g., '40px', '100%')
  width?: string; // Field width (e.g., '100%')
  fontSize?: string; // Font size (e.g., '16px')
  fontFamily?: string; // Font family (e.g., 'Arial, sans-serif')
  color?: string; // Text color (e.g., '#333', 'rgb(51, 51, 51)')
  background?: string; // Background color (e.g., '#fff', 'transparent')
  paddingTop?: string; // Top padding (e.g., '10px')
  paddingBottom?: string; // Bottom padding (e.g., '10px')
  paddingLeft?: string; // Left padding (e.g., '12px')
  paddingRight?: string; // Right padding (e.g., '12px')
  lineHeight?: string; // Line height (e.g., '40px', '1.5')
  letterSpacing?: string; // Letter spacing (e.g., '1px', '0.5em')
  placeholderColor?: string; // Placeholder text color (e.g., '#999')
}

Supported CSS Value Formats

Value Format Sample
Length Units Pixels '10px', '40px'
Rem '1rem', '2.5rem'
Em '1em', '1.5em'
Percentage '100%', '50%'
Viewport units '100vw', '50vh'
Auto/inherit 'auto', 'inherit'
Colours Hex '#ffffff', '#fff'
RGB 'rgb(255, 255, 255)'
RGBA 'rgba(255, 255, 255, 0.5)'
Named colours 'white', 'black', 'red', etc.
Font Families Generic 'serif', 'sans-serif', 'monospace'
Safe fonts 'Arial', 'Helvetica', 'Times New Roman', 'Georgia'

Global vs Field-Specific Styles

  1. Global styling applies to all fields:
const elements = window.HostedFieldsSDK.createElements({
  // ...credentials
  style: { height: '40px', fontSize: '16px' },
});
  1. Field-specific styling overrides global:
const cardNumber = elements.create('cardNumber', {
  placeholder: '0000 0000 0000 0000',
  style: { color: '#007bff' }, // Override global color for this field only
});

Field Interactions

Event Listeners

Each Field instance provides an .on(event, callback) method. It returns an unsubscribe function. Events can be registered before the field is mounted — they are buffered and replayed automatically.

// Subscribe to events
const unsubFocus = cardNumber.on('focus', () => console.log('Focused'));
const unsubBlur = cardNumber.on('blur', () => console.log('Blurred'));
const unsubInput = cardNumber.on('input', () => console.log('Input changed'));

// Unsubscribe when no longer needed
unsubFocus();

Available Events

Event Description
focus Field received focus.
blur Field lost focus.
input User typed or changed the value.
ready Iframe is mounted and ready.

Available Methods per Field

Method Returns Description
mount(sel) 'Promise' Mount the field into a DOM container.
unmount() void Remove from DOM but keep instance alive.
destroy() void Full teardown (unmount + clear listeners).
focus() void Programmatically focus the input.
blur() void Programmatically blur the input.
validate() 'Promise' Validate the current value.
update(opts) void Merge new options and push to iframe.
on(event, cb) () => void Subscribe to an event; returns unsubscribe fn.

Elements-Level State Management

The elements instance provides methods to control state across all fields:

  • **setLoading(isLoading: boolean)** — Toggles loading behaviour (disabling/readonly) for all mounted fields.
  • **setDisabled(disabled: boolean, type?: FieldType)** — Enable or disable all fields, or a specific field by type.
  • **setReadonly(readonly: boolean, type?: FieldType)** — Set all fields (or a specific one) to readonly.

Validation

Per-field validation

const result = await cardNumber.validate();
// { valid: true }
// or
// { valid: false, error: { type: 'invalid_card_number', message: '...', field: 'cardNumber' } }

Global validation (validateAll)

Validates all mounted fields at once. Requires cardNumber, expiryDate, and cvv to be created and mounted.

const result = await elements.validateAll();
// { valid: true }
// or
// {
//   valid: false,
//   errors: {
//     cardNumber: { type: 'invalid_card_number', message: '...', field: 'cardNumber', retryable: true }
//   }
// }

Submitting Payment Data

elements.tokenize()

Submit payment data and create a token. Requires cardNumber, expiryDate, and cvv to be created and mounted.

🚧

Important: The tokenize()will automatically abort if fields are obviously invalid. Best Practice: Always call validateAll()before calling tokenize()to get a full error map for your UI.

// 1. Validate all fields first
const validation = await elements.validateAll();

if (!validation.valid) {
  console.error('Form contains invalid fields:', validation.errors);
  return;
}

// 2. If validation passes, attempt to tokenize
const result = await elements.tokenize();

if (result.token) {
  // Send the token to your backend securely
  await processPaymentOnBackend(result.token);
} else {
  console.error('Tokenization failed:', result.error?.message);
}

Lifecycle & Cleanup

Unmount vs Destroy

  • **field.unmount()**— Removes the iframe from the DOM but keeps the Field instance alive. Registered event listeners are preserved and will be reattached on the next mount() call.
  • **field.destroy()** — Full teardown: unmounts and clears all listeners.

Cleaning up the session

Call elements.destroy() when removing the payment form. This destroys all field instances.

// Clean up all fields, remove iframes, and clear event listeners
elements.destroy();
👍

In single-page applications, always call destroy() when the payment form component unmounts.

Error Handling

Error Codes

Validation Errors:

  • field_invalid - field validation failed
  • field_required - required field is empty
  • invalid_card_number - invalid card number format
  • invalid_expiry_date - invalid or expired expiry date
  • invalid_cvv - invalid CVV/CVC code
  • invalid_cardholder_name - invalid cardholder name
  • validation_failed - general validation failure

Network & Token Errors:

  • network_timeout - network request timed out
  • invalid_config - invalid SDK configuration provided
  • forbidden_origin - domain is not authorized to load the hosted fields
  • token_creation_failed - token creation failed on the server
  • unknown_error - unknown error occurred

Security & Compliance

This SDK is designed from the ground up to minimise your PCI DSS compliance scope by ensuring sensitive card data never touches your application environment.

Data Protection & Architecture

  • Isolated IFrames: All sensitive card data is exclusively processed within secure iFrames.
  • No Data Leakage: Data never leaves the secure iframe boundary. Input masking prevents data leakage via browser history.
  • PCI DSS SAQ A: Designed specifically to support SAQ A for merchants by isolating PAN and CVV.
  • Secure Communication: Uses secure cross-origin communication with strict origin validation. No external API calls are allowed from the parent domain.

Integration Best Practices (Do's & Don'ts)

To maintain the integrity of the secure fields, adhere to the following rules:

  • Never expose the fields instance to the window object or global scope.
  • Never log, inspect, or attempt to extract raw field data in the browser console.
  • Never pass user-controlled data directly into the SDK styles or configuration.
  • Always keep the fields instance private within your module scope.
  • Always use HTTPS in production environments.
  • Always validate generated tokens on your backend before processing payments.

Strict Input & Styling Validation

The SDK actively validates all configurations to prevent CSS injection attacks and ensure visual integrity.

  • Rejected CSS Patterns: url(), calc(), !important, CSS variables (var()), animations/transforms.
  • Safe Formatting: Numeric values are auto-normalised with safe units (px, rem, %). Colours and font families are validated against a strict whitelist of safe formats and generic/system fonts.
  • DOM Validation: Mount point selectors are validated before any iframe creation occurs.

Complete Example

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Payment Form</title>
  </head>
  <body>
    <div class="payment-form">
      <h1>Payment Details</h1>

      <div class="form-group">
        <label>Card Number</label>
        <div class="field-wrapper" id="card-number"></div>
      </div>

      <div class="form-group">
        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
          <div>
            <label>Expiry Date</label>
            <div class="field-wrapper" id="card-expiry"></div>
          </div>
          <div>
            <label>CVV</label>
            <div class="field-wrapper" id="card-csc"></div>
          </div>
        </div>
      </div>

      <button onclick="window.handlePaymentSubmit()">Pay Now</button>
    </div>

    <script src="https://api.example.com/sdk/index.umd.js"></script>

    <script type="module">
      let elements;

      async function initializePaymentForm() {
        try {
          // 1. Create session manager
          elements = window.HostedFieldsSDK.createElements({
            baseUrl: 'https://api.example.com',
            apiKey: '<your_api_key>',
            merchantAccountId: '<your_merchant_id>',
            paymentRequestId: '<your_payment_request_id>',
            style: {
              height: '40px',
              fontSize: '16px',
            },
          });

          // 2. Create fields
          const cardNumber = elements.create('cardNumber', {
            placeholder: '0000 0000 0000 0000',
          });
          const expiryDate = elements.create('expiryDate', {
            placeholder: 'MM/YY',
          });
          const cvv = elements.create('cvv', {
            placeholder: 'CVV',
          });

          // 3. Register event listeners (before mount is OK)
          cardNumber.on('blur', async () => {
            const result = await cardNumber.validate();
            if (!result.valid) {
              console.warn('Card number:', result.error.message);
            }
          });

          // 4. Mount all fields
          await Promise.all([
            cardNumber.mount('#card-number'),
            expiryDate.mount('#card-expiry'),
            cvv.mount('#card-csc'),
          ]);

          console.log('Payment form ready');
        } catch (error) {
          console.error('Initialization failed:', error.message);
        }
      }

      if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializePaymentForm);
      } else {
        initializePaymentForm();
      }

      window.handlePaymentSubmit = async () => {
        if (!elements) return;

        const validation = await elements.validateAll();
        if (!validation.valid) {
          alert('Please correct the highlighted fields.');
          return;
        }

        try {
          const result = await elements.tokenize();
          if (result.token) {
            alert('Success! Token: ' + result.token);
          } else {
            alert('Payment failed: ' + (result.error?.message || 'Unknown error'));
          }
        } catch (error) {
          alert('Submission failed. Please try again.');
        }
      };
    </script>
  </body>
</html>