---
title: Hosted Fields SDK
slug: hosted-fields-sdk
docTags: 
createdAt: 2026-06-01T13:20:14.000Z
---

# Intro

Our hosted fields are rendered in secure iframes, ensuring that card numbers, CVV codes, and other sensitive payment data are handled securely. This approach allows you to customize the look and feel of the payment form while keeping sensitive data processing isolated from your application infrastructure.

## Example Implementation

You can see a working example of our hosted fields integration in this CodeSandbox:

[Hosted Fields Example](https://codesandbox.io/p/sandbox/c24k68)

# Integration Steps

:::::WorkflowBlock
:::WorkflowBlockItem
### Install the SDK

Add the Hosted Fields SDK to your checkout page:

```html
<script type="text/javascript" data-site-id="XXXXXXX" id="checkoutTools__script" src="https://sdk.checkouttools.com/v1.2/collect.js"> </script>
```

**If using Content Security Policy (CSP)**, update your directives:

```text
connect-src <https://sdk.checkouttools.com>  
frame-src <https://sdk.checkouttools.com>  
script-src <https://sdk.checkouttools.com>
```
:::

::::WorkflowBlockItem
### Generate an auth token

Before using the Forter Hosted Fields SDK, you will need to generate a temporary authentication token in your backend.

Before rendering your checkout page, issue a request to the `/client-key` endpoint in the Tokenization API from your backend.

After making the call, you will receive a JWT authentication token that you need to pass your checkout page.

:::hint{type="info"}
You will need to authenticate the request using your Tokenization API credentials, which are different from your Core API credentials.
:::

**NOTE**: When creating the token server-side, you are able to control its TTL. It is recommended to use the smallest TTL value necessary for the end-user to successfully complete the transaction in the frontend, in order to reduce the chance of abuse.
::::

:::WorkflowBlockItem
### Initialize the SDK

Call the `init` method on the hosted fields SDK, passing in the auth token you received in the previous step:

```javascript
const forterCollect = await window.checkoutTools.collect.init(
  {
    environment: 'sandbox',
    authToken: 'TOKEN.VALUE'
  }
);
```
:::

:::WorkflowBlockItem
### Add Hosted Fields to the checkout page form

```html
<form>  
  <div id="cc-holder"></div>    
  <div id="cc-number"></div>    
  <div id="cc-exp"></div>       
  <div id="cc-cvc"></div>       
</form>
```

Insert placeholder elements into your checkout page:

Initialize the Hosted Fields using the SDK instance you created in the previous step, by calling the `addFields` method:

```javascript
forterCollect.addFields({  
  'cc-holder': {
    type: 'CARD_HOLDER_NAME'
  },
  'cc-number': {
      type: 'CARD_NUMBER',
  },
  'cc-exp': {
    type: 'CARD_EXPIRATION_DATE',
  },
  'cc-cvc': {
    type: 'CARD_CVC',
  },
...
});
```

**Note 1:** The minimum required fields are: `CARD_NUMBER`, `CARD_HOLDER_NAME` and `CARD_EXPIRATION_DATE`.

**Note2:** Each field ID must be unique and match its HTML container.
:::

:::WorkflowBlockItem
### Submit the form & receive a token

When the customer submits the form, call the `submit()` method to receive a single-use token.

```javascript
const tokenResult = await forterCollect.submit();  
console.log(result)
```

**Successful Response: (example)**

```json
{  
  "success": true,  
  "token": "ftr12d4e830283b647d4b3b2a3d65f02b8ab"  
}
```
:::

:::WorkflowBlockItem
### Send the token to your backend for later processing

```javascript
const response = await fetch("https://example.org/pay", {
  method: "POST",
  body: JSON.stringify({ shippingAddress,cartItems, ... , forterToken: tokenResult.token }),
});
```
:::
:::::

***

# Client-Side Encryption

For scenarios where you need to collect and encrypt card data directly (without using Hosted Fields iframes), you can use client-side encryption. This encrypts sensitive card data in the browser or mobile app before transmitting it to your server.

## How It Works

The encryption uses a hybrid encryption scheme to securely encrypt card data:

1. A random AES-256 key is generated for each encryption
2. Card data is encrypted with AES-256-GCM (fast, authenticated encryption)
3. The AES key is wrapped (encrypted) with RSA-OAEP-SHA256 using Forter's public key
4. Only Forter's backend can decrypt the data using the corresponding private key

## Encrypted Payload Format

Both JavaScript and mobile implementations produce the same JSON structure:

```json
{
  "kid": "prod-v1",
  "alg": "RSA-OAEP-256",
  "enc": "A256GCM",
  "wrappedKey": "<base64-encoded-wrapped-aes-key>",
  "nonce": "<base64-encoded-12-byte-iv>",
  "ciphertext": "<base64-encoded-encrypted-data>",
  "tag": "<base64-encoded-16-byte-auth-tag>"
}
```

| **Field**  | **Description**                                                  |
| ---------- | ---------------------------------------------------------------- |
| kid        | Key ID for key rotation (e.g., prod-v1, sandbox-v1)              |
| alg        | Algorithm used to wrap the AES key (RSA-OAEP-256)                |
| enc        | Content encryption algorithm (A256GCM)                           |
| wrappedKey | RSA-OAEP wrapped AES key (base64, 344 characters)                |
| nonce      | AES-GCM initialization vector (base64, 16 characters / 12 bytes) |
| ciphertext | Encrypted card data JSON (base64, variable length)               |
| tag        | AES-GCM authentication tag (base64, 24 characters / 16 bytes)    |

***

# JavaScript SDK

## Installation

The encryption module is included in the Hosted Fields SDK script:

```html
<script type="text/javascript" data-site-id="XXXXXXX" id="checkoutTools__script" src="https://sdk.checkouttools.com/v1.2/encrypt.js"> </script>
```

## Usage

```javascript
const encryptedPayload = await window.checkoutTools.encrypt.encryptTokenizeRequest(
  {
    cardNumber: '4111111111111111',
    expirationMonth: '12',
    expirationYear: '2028',
    cvc: '123',
    cardHolderName: 'John Doe'
  },
  'production' // environment: 'sandbox' | 'production' 
);

// Send the encrypted payload to your backend
const response = await fetch('https://example.org/api/tokenize', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(encryptedPayload)
});
```

## Parameters

| **Parameter**   | **Type** | **Required** | **Description**                                |
| --------------- | -------- | ------------ | ---------------------------------------------- |
| cardNumber      | string   | Yes          | The card number (PAN)                          |
| expirationMonth | string   | Yes          | Two-digit expiration month (e.g., "12")        |
| expirationYear  | string   | Yes          | Four-digit expiration year (e.g., "2028")      |
| cvc             | string   | Yes          | Card security code (3-4 digits)                |
| cardHolderName  | string   | Yes          | Name as it appears on the card                 |
| environment     | string   | No           | "sandbox" \| "production" (default: "sandbox") |

***

# Flutter / Dart Implementation

For Flutter mobile applications, add the following dependencies and encryption code to your project.

## Dependencies

Add to your `pubspec.yaml`:

```yaml
dependencies:
  pointycastle: ^3.9.1
  cryptography: ^2.7.0
```

## Encryption Code

Add this file to your project (e.g., `lib/forter_encryption.dart`):

```dart
import 'dart:convert';
import 'dart:typed_data';
import 'package:pointycastle/export.dart';
import 'package:pointycastle/asn1.dart';
import 'package:cryptography/cryptography.dart' as crypto;

enum ForterEnvironment { production, sandbox }

const _sandboxPublicKey =
    'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF4SVdEVEVGQ1haTlBaVkxidzFMeQpjUWpHMzFkb3NKQjRiVGd2S2lDcVBEY3d0MC9SWno1NkxVdGFNaVFiZFp1UTU3amkvSEhvUDExL2xhZW94WGNrCi9rK1FUREJDNDFqcTQ0N05aVjNzdWtLR1VaWFgrODFEWTFDbERsRXRTSFQzM1VmTVJjZ3p3bG81WGxIR01XUWQKL3BMbGVPSEN2N0h0TGs5alRtNjI3SWRwQkVONTZBdGV6SUl6NDNHVjcrRGJjYnBVbUZoQVVCd0lnWUwrYUhKbwpZdmdTTk5obmN3QUVJdE5JRzRPdGVKaWZKWlBiU0ZWb0JwUzBrcG43T0J5NHd3UzBVTmRsZ213NWdDWEJTQ0hmClJUNVVYZzE1N2VGMkNwcDhRakFGSlRHYzJ4VUJIS3NobjRGSTczcGRZYklUM2FhSHNielFpRUpxbk5HQXo5NzQKc1FJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t';

const _prodPublicKey =
    'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFza3JBTWp2b01BbUF4eGJQU0d5ZgpYdXl4Vy9uQlJucWRWd0N3UVhSOUY4Z1lEV2haTEErbU1qY3hZRzd6MjJYemZzeXBnNG4yTzV0c2VXb3RBek1yCmRRUnc3RVVFWjdZaWJ2bGFPS2JRNk1FZHVNYlVvQk4yWnJENlBrakZLMDI3OGIvZ2toNERKNi9mckcwMmJ6em0KQ1V1djAyNUw2dS9tV0QyZERpdDBXR2RmdjQ0cDdVSHltRk1mc1lqYUtFYVFKZkVsUVVpRHBrZzhBWkUvbXFGZgpSS0FvRzNGMDBMUzNKZStkME4rZ3VYd3VWalh1RE1Kemg2aHkwWWxPdkFZdWtZR1h6RFc0Yk5Yd2FQR0N2SmJGClRGSHdTcmRGQlRFbHZuRVdKcUEzYkdMb05tcis5Mkd5b0lTK3RFVmNpUU0zZ1c3ckxpaVFpU09pZXJVYkZqZ1EKa3dJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==';

const _keyIds = {
  ForterEnvironment.production: 'prod-v1',
  ForterEnvironment.sandbox: 'sandbox-v1',
};

/// Encrypts card data using hybrid encryption (AES-256-GCM + RSA-OAEP-SHA256).
/// Returns a Map that can be JSON-encoded and sent to your backend.
Future<Map<String, String>> encryptCardData({
  required String cardNumber,
  required String expirationMonth,
  required String expirationYear,
  required String cvc,
  required String cardHolderName,
  ForterEnvironment environment = ForterEnvironment.sandbox,
}) async {
  final payload = {
    'cardNumber': cardNumber,
    'expirationMonth': expirationMonth,
    'expirationYear': expirationYear,
    'cvc': cvc,
    'cardHolderName': cardHolderName,
  };

  final publicKeyB64 = environment == ForterEnvironment.production
      ? _prodPublicKey
      : _sandboxPublicKey;

  final publicKey = _parsePublicKey(utf8.decode(base64.decode(publicKeyB64)));
  final kid = _keyIds[environment]!;

  // Generate AES key and encrypt payload
  final aes = crypto.AesGcm.with256bits();
  final secretKey = await aes.newSecretKey();
  final nonce = aes.newNonce();
  final plaintext = Uint8List.fromList(utf8.encode(jsonEncode(payload)));

  final secretBox = await aes.encrypt(plaintext, secretKey: secretKey, nonce: nonce);

  // Wrap AES key with RSA-OAEP
  final aesKeyBytes = Uint8List.fromList(await secretKey.extractBytes());
  final encryptor = OAEPEncoding.withSHA256(RSAEngine())
    ..init(true, PublicKeyParameter<RSAPublicKey>(publicKey));
  final wrappedKey = encryptor.process(aesKeyBytes);

  return {
    'kid': kid,
    'alg': 'RSA-OAEP-256',
    'enc': 'A256GCM',
    'wrappedKey': base64.encode(wrappedKey),
    'nonce': base64.encode(secretBox.nonce),
    'ciphertext': base64.encode(secretBox.cipherText),
    'tag': base64.encode(secretBox.mac.bytes),
  };
}

RSAPublicKey _parsePublicKey(String pem) {
  final lines = pem.split('\n').where((l) => !l.startsWith('-----') && l.isNotEmpty).join('');
  final bytes = base64.decode(lines);
  final seq = (ASN1Parser(Uint8List.fromList(bytes)).nextObject() as ASN1Sequence);
  final pubKeyBytes = (seq.elements![1] as ASN1BitString).valueBytes!.sublist(1);
  final pubSeq = ASN1Parser(Uint8List.fromList(pubKeyBytes)).nextObject() as ASN1Sequence;
  return RSAPublicKey(
    (pubSeq.elements![0] as ASN1Integer).integer!,
    (pubSeq.elements![1] as ASN1Integer).integer!,
  );
}
```

## Usage

```dart
import 'forter_encryption.dart';

// Encrypt card data
final encrypted = await encryptCardData(
  cardNumber: '4111111111111111',
  expirationMonth: '12',
  expirationYear: '2028',
  cvc: '123',
  cardHolderName: 'John Doe',
  environment: ForterEnvironment.production,
);

// Send to your backend
final response = await http.post(
  Uri.parse('https://your-api.com/api/tokenize'),
  headers: {'Content-Type': 'application/json'},
  body: jsonEncode(encrypted),
);
```

## Parameters

| **Parameter**   | **Type**          | **Required** | **Description**                             |
| --------------- | ----------------- | ------------ | ------------------------------------------- |
| cardNumber      | String            | Yes          | The card number (PAN)                       |
| expirationMonth | String            | Yes          | Two-digit expiration month (e.g., "12")     |
| expirationYear  | String            | Yes          | Four-digit expiration year (e.g., "2028")   |
| cvc             | String            | Yes          | Card security code (3-4 digits)             |
| cardHolderName  | String            | Yes          | Name as it appears on the card              |
| environment     | ForterEnvironment | No           | .sandbox \| .production (default: .sandbox) |

***

# JS SDK Reference

## Init

Initializes the Forter Hosted Fields SDK.

### Standard Initialization

`init({environment, authToken}, [cb: (err, results)]) => Promise<void>`

**Description**

Initialize the Forter Hosted Fields SDK.

**Parameters**

| **Parameter** | **Type** | **Required** | **Description**                                                                        |
| ------------- | -------- | ------------ | -------------------------------------------------------------------------------------- |
| environment   | string   | No           | Tokenization environment to use - `"sandbox"` \| `"production"` (default: `"sandbox"`) |
| authToken     | string   | Yes          | Authentication token retrieved from PCI Tokenization API                               |

**Example**

```javascript
const forterCollect = await checkoutTools.collect.init({
  environment: 'sandbox', 
  authToken: 'YOUR_AUTH_TOKEN'
});
```

***

### Advanced Initialization

For more control over the SDK's behavior, you can provide additional configuration options and event handlers:

`init({...options, ...eventHandlers}, [cb: (err, results)]) => Promise<void>`

**Description**

This method returns a Promise that can be awaited and will surface any errors. Alternatively, you may provide an optional callback function which will be invoked asynchronously when the action completes.

**Parameters**

| **Parameter** | **Type** | **Required** | **Description**                           |
| ------------- | -------- | ------------ | ----------------------------------------- |
| options       | object   | Yes          | Configuration object                      |
| eventHandlers | object   | No           | Object containing event handler functions |

**Options**

| **Parameter**     | **Type** | **Required** | **Description**                                                                        |
| ----------------- | -------- | ------------ | -------------------------------------------------------------------------------------- |
| environment       | string   | No           | Tokenization environment to use - `"sandbox"` \| `"production"` (default: `"sandbox"`) |
| authToken         | string   | Yes          | Authentication token retrieved from PCI Tokenization API                               |
| validationTrigger | string   | No           | When to trigger fields validation.`'ON_BLUR'` \| `'ON_CHANGE'` (default: `'ON_BLUR'`)  |

**eventHandlers**

| **Parameter**                                                                                      | **Type** | **Required** | **Description**                                                                                              |
| -------------------------------------------------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------ |
| onLoad()                                                                                           |          | No           | Triggered when fields are loaded. **Note:** Only triggered on first load.                                    |
| onError(errors: \{\[fieldName: string]: \{message: string, errorCode\:string} })                   |          | No           | Triggered on form validation errors                                                                          |
| onEnterPress()                                                                                     |          | No           | Triggered when Enter key is pressed in any field                                                             |
| onEscapePress()                                                                                    |          | No           | Triggered when Escape key is pressed in any field                                                            |
| onSuccess()                                                                                        |          | No           | Triggered after successful form submission. **Note:** Consider showing a loading indicator until this event. |
| onValidityChange(fieldId\:string, isValid\:boolean, error?: \{message: string, errorCode\:string}) |          | No           | Triggered when field validity changes                                                                        |
| onCardBrandChange(cardBrand: string)                                                               |          | No           | Triggered when card brand is detected                                                                        |

**Example**

```javascript
let formLoading = false;

const forterCollect = window.checkoutTools.collect.init({  
 onLoad() {  
   formLoading = true;  
 },  
 async onEnterPress() {  
   const tokenizeResult = await forterCollect.submit();  
   if (tokenizeResult.success) {  
     console.log('Form successfully submitted', tokenizeResult);  
   }  
 },  
 onError(errors){  
   console.log('Form submit error:', errors)  
 }  
});
```

***

## Fields

Adds one or more Hosted Fields to the form.

### Add Fields

`addFields(fieldsDefinition, [cb: (err, results)]) => Promise<Void>`

**Description**

Add one or more Hosted Fields to the form

**Notes:**

- This method returns a promise that resolves when all Hosted Fields have finished loading and are ready for use. (See also: `onLoad()` event.
- **Alternatively**, this method accepts an optional callback function which is invoked asynchronously once the action is completed.

**Parameters**

| **Parameter**    | **Type** | **Required** | **Description** |
| ---------------- | -------- | ------------ | --------------- |
| fieldsDefinition |          | Yes          |                 |

***

### Update Fields

`updateFields(fieldsDefinition, [cb: (err, results)]) => Promise<Void>`

**Description**

Update one or more Hosted Fields in the form.

**Notes:**

- This method returns a promise that resolves when all Hosted Fields have finished loading and are ready for use. (See also: `onLoad()` event.
- **Alternatively**, this method accepts an optional callback function which is invoked asynchronously once the action is completed.

**Parameters**

| Parameter        | Type | Required | Description |
| ---------------- | ---- | -------- | ----------- |
| fieldsDefinition |      | Yes      |             |

***

### Submit

`submit(cb: (err, results)) => Promise<{status: boolean, token:string, errors?: Array<{message: string, errorCode:string}>}>`

**Description**

Once the customer finishes entering all the relevant fields in the payment form, this method will submit them securely to be stored by Forter.

**Notes:**

- This method returns a Promise object that can be awaited to receive the tokenized fields. It will return the tokenized payload if successful, or the field errors if the form is still in an invalid state.
- **Alternatively**, this method accepts an optional callback function which is invoked asynchronously once the action is completed.
- The asynchronous result of this function is a `single token` string, that can later be unpacked in the server side to the individual fields, or sent as is to a third party using the `Forter Detokenization Proxy`.
- A successful tokenization does not execute a transaction or validation. In order to verify the payment method, you will need to invoke a purchase or authorization from your secure, server-side environment.

### Reset Fields

`reset(fieldName?: string, cb?: (err, results)) => Promise<Void>`

**Description**

Reset all fields or a specific field in the form. If a field name is not provided, all of the fields will be reset.

**Notes:**

- This method returns a Promise object that can be awaited.
- **Alternatively**, this method accepts an optional callback function which is invoked asynchronously once the action is completed.

**Parameters**

| Parameter | Type   | Required | Description |
| --------- | ------ | -------- | ----------- |
| fieldName | string | No       |             |

***

### Focus

`focus(fieldName?: string) => void`

**Description**

Focus on a specific field in the form.

**Parameters**

| Parameter | Type   | Required | Description |
| --------- | ------ | -------- | ----------- |
| fieldName | string | No       |             |

## Styling

Forter Hosted Fields can mostly be styled using standard CSS rules and techniques. When customizing your field's styles, the required techniques will differ when styling the **container** element itself (i.e., the form control), or the input element. This separation is required because the wrapping container element is hosted on your page, and the input in another, secured, web app.

### Container elements

Simply target the container elements you’ve created in HTML using the same CSS rules you apply to your other, non-hosted, form controls. This applies to macro layout like width, height, and spacing, and styling like border and box-shadow.

```html
<form>
    <label>
        Name on card
       <div id="card-holder-name" style="border: solid 1px red; height: 40px" />
    </label>
</form>
```

Note: Even if your other inputs calculate their own height, Forter Hosted fields require an explicit ‘height’ to be set on the container element.

### State classes

The following CSS rules are automatically added to the container element, which match the internal style “**form states**” mentioned above.

1. `forter-hosted-fields-focus`
2. `forter-hosted-fields-valid`
3. `forter-hosted-fields-invalid`
4. `forter-hosted-fields-empty`
5. `forter-hosted-fields-touched`
6. `forter-hosted-fields-empty`
7. `forter-hosted-fields-dirty`

Simply reference these classes in your stylesheets as normal:

```css
{
  .card-holder-name.forter-hosted-fields-focus {
    border: solid 2px blue;
  }
}
```

### Input elements

In case advanced styling rules are required for the input elements themselves, additional css rules can be passed in to the javascript SDK object using the style configuration option.

Two types of styles can be used:

1. A string containing an existing CSS class you may have already defined on your page used to style input elements. All CSS rules will be copied from this existing class and applied to the Hosted Field input element.

```css
await sdk.addFields({
  'card-number': {
    type: 'CARD_NUMBER',
    style: '.existing-form-input-class'  
  },
});
```

2. A configuration object containing custom css definitions. Accepted keys in this object are:
   1. *input* - Standard css properties that will be applied to the internal input element (See below for supported properties)
   2. **Pseudo-classes** (eg. \:hover, \:focus) - Nest additional rules for every html state
   3. **Form state classes**: Specific css classes are automatically applied to every Hosted Field, depending on its state. Every class can be styled using specific css properties.
      1. Supported state classes:
         **.invalid** - Field has failed validation.
         **.empty** - Field doesn't have a value.
         **.touched** - Applied forever once the field’s first ‘blur’ event has triggered, or first keystroke when the `validationTrigger` option is set to `ON_CHANGE`
         **.dirty** - Applied forever once the field’s value has changed for the first time.
         \:focus - Field is currently focused
         Media queries - (These apply to the iframe dimensions) - define a rule in pixels and nest additional rules of the other formats inside
      2. Allowed CSS Properties (browser specific variants will also work): "appearance", "box-shadow", "color", "direction", "font", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-variant-alternates", "font-variant-caps", "font-variant-east-asian", "font-variant-ligatures", "font-variant-numeric", "font-weight", "letter-spacing", "line-height", "margin", "margin-top", "margin-right", "margin-bottom", "margin-left", "opacity", "outline", "padding", "padding-top", "padding-right", "padding-bottom", "padding-left", "text-align", "text-shadow", "transition", "tap-highlight-color", "tap-highlight-color"

```css
 
await sdk.addFields({
  'card-number': {
    type: 'CARD_NUMBER',
    style: {  
      'input': {  
       'color': 'black',  
       'padding': '10px 5px',  
      }  
      ':focus': {  
        'border-color': 'blue',  
      },  
      '.touched': {  
        'color: 'green',  
      },  
      '.touched.error': {  
        'color: 'orange',  
        'text-decorations': 'underline',  
      },  
      '@media screen and (max-width: 600px)': {  
         'input': {  
           'font-size': '10px'  
        }  
      },
    },
  }
});
```

## Examples

### Full Credit Card Details Form

Create a common form containing fields for collecting a credit card’s number, security code, expiration date, and cardholder name.

First, create the HTML scaffolding - a form with placeholders elements that will later be replaced with Forter Hosted Fields.

```html
<body>
<form id="payment-form">
 <label>
   Name on card
   <div id="card-holder-name" />
 </label>
 <label>
   Card
   <div id="card-number" />
 </label>
 <label>
   CVC
   <div id="card-cvc" />
 </label>
 <label>
   Expiration date
   <div id="card-expiration" />
 </label>
</form>
</body>
```

Next, in a Javascript tag copy and paste the following.

```javascript
const forterCollect = await checkoutTools.collect.init();

const commonStyles = {  
  ":focus": {  
    color: "orange",  
  },  
  ".touched": {  
    color: "green",  
  },  
  ".touched.error": {  
    color: "red",  
    "text-decorations": "underline",  
  },  
};

forterCollect.addFields({  
  "card-holder-name": {  
    type: "TEXT",  
    placeholder: "Name on card",  
    style: commonStyles,  
  },  
  "card-number": {  
    type: "CARD_NUMBER",  
    placeholder: "1111-2222-3333-4444",  
    onFocus() {  
      // TODO: handle focus  
    },  
    style: commonStyles,  
  },  
  "card-cvc": {  
    type: "CARD_CVC",  
    onError(errorMessage) {  
      console.error("Error in field card-cvv", errorMessage);  
    },  
    style: {  
      ...commonStyles,  
      display: "inline-box",  
    },  
  },  
  "card-expiration": {  
    type: "CARD_EXPIRATION_DATE",  
    style: commonStyles,  
  },  
});
```

Finally, wire up the form’s `submit` event to Forter’s `submit` method in order to securely store the form’s state.

```javascript
document  
 .getElementById("#payment-form")  
 .addEventListener("submit", function (e) {  
   e.preventDefault();

   // Callback based method:  
   forterCollect.submit(function (err, results) {  
     if (err) {  
       console.error("error submitting payment form", err);  
       return;  
     }

     console.log("payment form submitted successfully!", results);

     // Combine the tokenized fields along with other non-sensitive fields that may exist in the form, and submit them to your backend server for further processing.
     backend.submitPaymentForm({ fields: { ...ownFields, ...results } });

   });

   // or alternatively, ESNext promise based method:  
   forterCollect  
     .submit()  
     .then((results) => {  
       if (results.success) {  
         console.log("Form successfully submitted", tokenizeResult);  
       }  
       console.log("payment form submitted successfully!", results);  
       backend.submitPaymentForm({ fields: { ...ownFields, ...results } });  
     })  
     .catch((e) => {  
       console.error("error submitting payment form", e);  
     });  
 });
```

### Re-collecting CVC

The Hosted Fields SDK supports the collection of the CVC only. This is useful in repeat transactions, along with a multi-use token in order to reduce the risk of a fraud-decline.

```javascript
const forterCollect = await checkoutTools.collect.init();

forterCollect.addFields({  
  "card-cvc": {  
    type: "CARD_CVC",  
    onError(errorMessage) {  
      console.error("Error in field card-cvv", errorMessage);  
    },  
  },  
});
```

**Collecting Card and CVC Separately**

In some cases, especially when complying with PCI DSS (which prohibits storing CVC), you may need to collect the card details and CVC in separate sessions. For example, you might collect and tokenize the card number during checkout, but defer collecting the CVC until just before authorization.

To support this, you can initialize two separate Hosted Fields SDK instances: one for the card details without CVC, and another exclusively for CVC collection.

```html
<form>
  <div id="card-holder"></div>
  <div id="card-number"></div>
  <div id="card-exp"></div>
  <div id="card-cvc"></div>
  <button type="button" id="submit">Submit</button>
</form>
```

```javascript
(async () => {
  // Initialize first instance for card details (without CVC)
  const forterCollectCard = await window.checkoutTools.collect.init({
    environment: "sandbox",
    authToken: "{AUTH_TOKEN}" // Replace with actual auth token
  });

  // Initialize second instance for CVC only
  const forterCollectCVC = await window.checkoutTools.collect.init({
    environment: "sandbox",
    authToken: "{AUTH_TOKEN}" // Replace with actual auth token
  });

  // Add card details fields (excluding CVC)
  forterCollectCard.addFields({
    "card-holder": { type: "CARD_HOLDER_NAME" },
    "card-number": { type: "CARD_NUMBER" },
    "card-exp": { type: "CARD_EXPIRATION_DATE" }
  });

  // Add CVC field separately
  forterCollectCVC.addFields({
    "card-cvc": { type: "CARD_CVC" }
  });

  document.querySelector("#submit").addEventListener("click", async () => {
    // Tokenize both card and CVC fields
    const [cardTokenResult, cvcTokenResult] = await Promise.all([
      forterCollectCard.submit(),
      forterCollectCVC.submit()
    ]);

    console.log("cardTokenResult", cardTokenResult);
    console.log("cvcTokenResult", cvcTokenResult);

    // Send both tokens to your backend (recommended)
    /*
    await fetch("/api/process-payment", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        cardToken: cardTokenResult.token,
        cvcToken: cvcTokenResult.token
      })
    });
    */
  });
```

**Sending Tokens via Forter Detokenization Proxy**

You'll now have two tokens:

1. **Payment Method Token:** Forter-issued PCI token for PAN, expiration, etc.
2. **CVC-Only Token:** Forter-issued token strictly for the CVC.

Once both tokens are collected, you can forward both to your PSP through the [Detokenization Proxy](https://docs.forter.com/docs/detokenization-proxy-integration-guide#for-cvc-only-tokens) for secure, PCI-compliant forwarding of card data. In your proxy request, you must *include both tokens in the header*.

If you are using direct tokens, send the `forter-token` and `forter-cvc-token`.&#x20;

If using alias-based tokens, send the `forter-token-alias-key` `forter-token-alias-value` pair and the `forter-cvc-token-alias-key` `forter-cvc-token-alias-value` pair.

:::hint{type="warning"}
The CVC token is short-lived, in line with PCI requirements. Always re-collect it before sending to your PSP.
:::
