PCI Widget Web
As part of our embedded scope, you'll need secure access to sensitive card data. For data like PAN and CVV, using the PCI widget is mandatory due to PCI-DSS security requirements. If you're PCI-DSS certified, you can alternatively use the API to access this sensitive data directly.
The PCI widget offers several customization options to match your UI/UX requirements.

The widget integrates into your frontend through an iframe
and displays card data for approximately 45 seconds. After this period, the card details are automatically hidden for security purposes.
You'll need two parameters for implementation:
traceId
: A random, unique UUIDv4 that you generate when making the request. This ID helps trace the request through all systems involved and must remain consistent throughout the PCI widget request - use the same ID in both the iFrame URL and the OTP request (see endpoint).widgetConfig
: The configuration object for the widget, containing elements like thecardId
(a Pliant UUIDv4) and design parameters. Full configuration details are available in the widget config section.
<iframe
src="{host}/card-details/widget?traceId={traceId}¶ms={widgetConfig}"
allow="clipboard-read; clipboard-write">
</iframe>
Here's an example of a fully parameterized iframe
:
<iframe
src="https://pci-api.getpliant.com/card-details/widget?traceId=5c68c142-50a0-46e1-8a27-736a7908cc59¶ms=ewogICJ0aXRsZSI6ICJDYXJkIERldGFpbHMiLAogICJ0b2tlbiI6ICJ7cmVxdWVzdGVkIE9UUH0iLAogICJjYXJkSWQiOiAie1BsaWFudCdzIGNhcmRJZH0iLAogICJvdmVybGF5VGV4dCI6ICJTaG93IGNhcmQgZGV0YWlscyIsCiAgImNvcHlMYWJlbCI6ICJDb3B5IiwKICAiZnJhbWVJZCI6ICJjYXJkLWRldGFpbHMtd2lkZ2V0LWZyYW1lIiwKICAiY3NzQ2xhc3MiOiAid2lkZ2V0LWNvbnRhaW5lciIsCiAgInN0eWxlVXJsIjogIi4vd2lkZ2V0L3N0YXRpYy9wY2ktd2lkZ2V0LmNzcyIsCiAgInBhbiI6IHsKICAgICJsYWJlbCI6ICJDYXJkIG51bWJlciIsCiAgICAiZGlzcGxheSI6IHRydWUsCiAgICAiY2xpY2tUb0NvcHkiOiB0cnVlLAogICAgInRleHRPbkNvcHkiOiAiQ29weSBjYXJkIG51bWJlciIKICB9LAogICJjYXJkaG9sZGVyTmFtZSI6IHsKICAgICJsYWJlbCI6ICJDYXJkaG9sZGVyIG5hbWUiLAogICAgImRpc3BsYXkiOiB0cnVlLAogICAgImNsaWNrVG9Db3B5IjogdHJ1ZSwKICAgICJ0ZXh0T25Db3B5IjogIkNvcHkgY2FyZGhvbGRlciBuYW1lIgogIH0sCiAgImV4cGlyeURhdGUiOiB7CiAgICAibGFiZWwiOiAiRXhwIiwKICAgICJkaXNwbGF5IjogdHJ1ZSwKICAgICJjbGlja1RvQ29weSI6IHRydWUsCiAgICAidGV4dE9uQ29weSI6ICJDb3B5IGV4cGlyeSBkYXRlIgogIH0sCiAgImN2diI6IHsKICAgICJsYWJlbCI6ICJDVlYiLAogICAgImRpc3BsYXkiOiB0cnVlLAogICAgImNsaWNrVG9Db3B5IjogdHJ1ZSwKICAgICJ0ZXh0T25Db3B5IjogIkNvcHkgQ1ZWIgogIH0KfSA="
allow="clipboard-read; clipboard-write">
</iframe>
Data Flow

The process works as follows:
- The cardholder authenticates on your frontend.
- The cardholder clicks a button to view sensitive card data.
- Your frontend requests a one-time password (OTP) to reveal this data, by sending this
GET
request through your backend, which then forwards it to Pliant's PCI API. See the endpoint documentation.curl 'https://{host}/card-details/widget/{cardId}/otp' \ --header 'Pliant-Trace-Id: {random UUIDv4, generated by you}' \ --header 'Authorization: Bearer {token}'
- Your backend must validate the request to ensure the requesting user has permission to access the card data.
- Include a
Pliant-Trace-Id
header in the OTP request to enable tracing through all systems.
- After receiving the OTP, compile the PCI widget configuration using the schema below. Base64-encode the JSON configuration for use as the
widgetConfig
parameter. - Create and load the iframe with
src="{host}/card-details/widget?traceId={traceId}¶ms={widgetConfig}"
using the same trace ID from the OTP request. - During iframe loading, an RSA key pair is generated for data encryption (valid only for a short time).
- The sensitive card data is requested using the public RSA key and your widget configuration.
- The system verifies the browser's user agent against a blacklist, allowing only actual browser requests (tools like Postman or CLI utilities are blocked).
- The public RSA key encrypts the card data response containing the sensitive information, with the widget config determining which specific fields are returned.
- The iframe decrypts the card data using the private RSA key and displays it temporarily.
- After 45 seconds, the iframe content is replaced with a static placeholder, and all sensitive data is permanently deleted from the page and browser clipboard.
- The system sends an event with the message
{eventType: CARD_DATA_CLEARED, frameId: <frameId>}
to the parent page. Your page should include an event handler that destroys the iframe when it receives this event. For errors, the system sends an event with the same structure but witheventType
set toCARD_DATA_LOADING_FAILED
.
Environments
Environment | Host URL |
---|---|
PROD | https://pci-api.getpliant.com |
SANDBOX | https://pci-sandbox.partner-api.getpliant.com |
Widget Config
The widget configuration is transmitted to the iframe as a base64-encoded JSON payload. For implementation examples, see examples for iframe widget usage.
{
"title": "Card Details",
"token": "{requested OTP}",
"cardId": "{Pliant's cardId (36 chars UUIDv4)}",
"overlayText": "Show card details",
"copyLabel": "Copy",
"frameId": "card-details-widget-frame",
"cssClass": "widget-container",
"styleUrl": "./widget/static/pci-widget.css",
"pan": {
"label": "Card number",
"display": true,
"clickToCopy": true,
"textOnCopy": "Copy card number"
},
"cardholderName": {
"label": "Cardholder name",
"display": true,
"clickToCopy": true,
"textOnCopy": "Copy cardholder name"
},
"expiryDate": {
"label": "Exp",
"display": true,
"clickToCopy": true,
"textOnCopy": "Copy expiry date"
},
"cvv": {
"label": "CVV",
"display": true,
"clickToCopy": true,
"textOnCopy": "Copy CVV"
}
}
This configuration is attached to the URL as a base64-encoded string:
ewogICJ0aXRsZSI6ICJDYXJkIERldGFpbHMiLAogICJ0b2tlbiI6ICJ7cmVxdWVzdGVkIE9UUH0iLAogICJjYXJkSWQiOiAie1BsaWFudCdzIGNhcmRJZH0iLAogICJvdmVybGF5VGV4dCI6ICJTaG93IGNhcmQgZGV0YWlscyIsCiAgImNvcHlMYWJlbCI6ICJDb3B5IiwKICAiZnJhbWVJZCI6ICJjYXJkLWRldGFpbHMtd2lkZ2V0LWZyYW1lIiwKICAiY3NzQ2xhc3MiOiAid2lkZ2V0LWNvbnRhaW5lciIsCiAgInN0eWxlVXJsIjogIi4vd2lkZ2V0L3N0YXRpYy9wY2ktd2lkZ2V0LmNzcyIsCiAgInBhbiI6IHsKICAgICJsYWJlbCI6ICJDYXJkIG51bWJlciIsCiAgICAiZGlzcGxheSI6IHRydWUsCiAgICAiY2xpY2tUb0NvcHkiOiB0cnVlLAogICAgInRleHRPbkNvcHkiOiAiQ29weSBjYXJkIG51bWJlciIKICB9LAogICJjYXJkaG9sZGVyTmFtZSI6IHsKICAgICJsYWJlbCI6ICJDYXJkaG9sZGVyIG5hbWUiLAogICAgImRpc3BsYXkiOiB0cnVlLAogICAgImNsaWNrVG9Db3B5IjogdHJ1ZSwKICAgICJ0ZXh0T25Db3B5IjogIkNvcHkgY2FyZGhvbGRlciBuYW1lIgogIH0sCiAgImV4cGlyeURhdGUiOiB7CiAgICAibGFiZWwiOiAiRXhwIiwKICAgICJkaXNwbGF5IjogdHJ1ZSwKICAgICJjbGlja1RvQ29weSI6IHRydWUsCiAgICAidGV4dE9uQ29weSI6ICJDb3B5IGV4cGlyeSBkYXRlIgogIH0sCiAgImN2diI6IHsKICAgICJsYWJlbCI6ICJDVlYiLAogICAgImRpc3BsYXkiOiB0cnVlLAogICAgImNsaWNrVG9Db3B5IjogdHJ1ZSwKICAgICJ0ZXh0T25Db3B5IjogIkNvcHkgQ1ZWIgogIH0KfSA=
Parameter | Type | Description |
---|---|---|
token (mandatory) | string | OTP obtained through Pliant's endpoint |
cardId (mandatory) | uuid | Pliant's card id |
title | string | Iframe page title |
styleUrl | string | URL to CSS styles for the iframe content |
{fieldName}.label | string | Overwrite field names for card details fields. Defaults:pan → Card numbercardholderName → Cardholder nameexpiryDate → Expcvv → CVV |
{fieldName}.clickToCopy | boolean | Enable click to copy block for a particular field. Default: true |
{fieldName}.display | boolean | Load field block into view. Default: true |
{fieldName}.textOnCopy | string | Title of the copy element in the HTML DOM for a particular field. Defaults:pan → Copy card numbercardholderName → Copy cardholder nameexpiryDate → Copy expiry datecvv → Copy CVV |
overlayText | string | Text displayed after sensitive card data is destroyed permanently. Default: Show card details |
copyLabel | string | Label for the copy button. Default: Copy |
cssClass | string | div[@id=app] will have a class equal to pliant-pci-widget {cssClass} , which is pliant-pci-widget widget-container by default.This helps to customize the CSS on a more refined level. For example, if you have multiple color cards, you can have two styles defined: .card-color-1 .a {color: #AAAAAA} .card-color-2 .a {color: #BBBBBB} If you pass cssClass=card-color-2 the second CSS block will be applied. Default: widget-container |
frameId | string | Iframe id for which a destruction event will be triggered. Default: card-details-widget-frame |
Event Details
Event format: {eventType: {eventType}, frameId: {frameId}}
Event example: {eventType: CARD_DATA_CLEARED, frameId: myFrame123}
Available event types:
CARD_DATA_LOADING_FAILED
- An error occurred; card data could not be displayedCARD_DATA_CLEARED
- Card data display timed outCARD_DATA_LOADING_SUCCESS
- Card data displayed successfullyCARD_DATA_PAN_COPIED
- PAN copied to clipboardCARD_DATA_CARDHOLDER_NAME_COPIED
- Cardholder name copied to clipboardCARD_DATA_EXP_COPIED
- Expiry date copied to clipboardCARD_DATA_CVV_COPIED
- CVV copied to clipboard
iFrame HTML Structure
Structure in Default State
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Card Details</title>
<script crossorigin="" defer="" src="./widget/static/pci-widget.js" type="application/javascript"></script>
<link crossorigin="" href="./widget/static/pci-widget.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="wrapper">
<div app-frame-id="card-details-widget-frame" aria-label="Card Details" class="pliant-pci-widget widget-container"
id="app"
role="region">
<div aria-label="Show card details" class="pliant-preloaded-data-overlay" role="button" style="display: none;"
tabindex="0">Show card details
</div>
<div class="pliant-pci-widget-container">
<div aria-label="Card number" class="pliant-data pliant-pan-result" role="group">
<div class="pliant-field-name pliant-field-pan-name">Card number</div>
<div class="pliant-field-value pliant-field-pan-value">
<span class="mono pliant-field-pan-value-text" data-field="pan" title="4444 5555 6666 7777">4444 5555 6666 7777</span>
<a aria-label="Copy card number" class="pliant-copy-btn" data-target="pliant-field-pan-value-text" href="#"
role="button"
tabindex="0">
<svg class="pliant-copy-btn-icon" fill="none" height="20" viewBox="0 0 32 32" width="20"
xmlns="http://www.w3.org/2000/svg">
<path
d="M23 8H5C4.73478 8 4.48043 8.10536 4.29289 8.29289C4.10536 8.48043 4 8.73478 4 9V27C4 27.2652 4.10536 27.5196 4.29289 27.7071C4.48043 27.8946 4.73478 28 5 28H23C23.2652 28 23.5196 27.8946 23.7071 27.7071C23.8946 27.5196 24 27.2652 24 27V9C24 8.73478 23.8946 8.48043 23.7071 8.29289C23.5196 8.10536 23.2652 8 23 8ZM22 26H6V10H22V26ZM28 5V23C28 23.2652 27.8946 23.5196 27.7071 23.7071C27.5196 23.8946 27.2652 24 27 24C26.7348 24 26.4804 23.8946 26.2929 23.7071C26.1054 23.5196 26 23.2652 26 23V6H9C8.73478 6 8.48043 5.89464 8.29289 5.70711C8.10536 5.51957 8 5.26522 8 5C8 4.73478 8.10536 4.48043 8.29289 4.29289C8.48043 4.10536 8.73478 4 9 4H27C27.2652 4 27.5196 4.10536 27.7071 4.29289C27.8946 4.48043 28 4.73478 28 5Z"></path>
</svg>
<span class="pliant-copy-btn-text">Copy</span>
</a>
</div>
</div>
<div aria-label="Cardholder name" class="pliant-data pliant-cardholder-result" role="group">
<div class="pliant-field-name pliant-field-cardholder-name">Cardholder name</div>
<div class="pliant-field-value pliant-field-cardholder-value">
<span class="pliant-field-cardholder-value-text" data-field="cardholderName"
title="Jane Dow">Jane Dow</span>
<a aria-label="Copy cardholder name" class="pliant-copy-btn" data-target="pliant-field-cardholder-value-text"
href="#"
role="button" tabindex="0">
<svg class="pliant-copy-btn-icon" fill="none" height="20" viewBox="0 0 32 32" width="20"
xmlns="http://www.w3.org/2000/svg">
<path
d="M23 8H5C4.73478 8 4.48043 8.10536 4.29289 8.29289C4.10536 8.48043 4 8.73478 4 9V27C4 27.2652 4.10536 27.5196 4.29289 27.7071C4.48043 27.8946 4.73478 28 5 28H23C23.2652 28 23.5196 27.8946 23.7071 27.7071C23.8946 27.5196 24 27.2652 24 27V9C24 8.73478 23.8946 8.48043 23.7071 8.29289C23.5196 8.10536 23.2652 8 23 8ZM22 26H6V10H22V26ZM28 5V23C28 23.2652 27.8946 23.5196 27.7071 23.7071C27.5196 23.8946 27.2652 24 27 24C26.7348 24 26.4804 23.8946 26.2929 23.7071C26.1054 23.5196 26 23.2652 26 23V6H9C8.73478 6 8.48043 5.89464 8.29289 5.70711C8.10536 5.51957 8 5.26522 8 5C8 4.73478 8.10536 4.48043 8.29289 4.29289C8.48043 4.10536 8.73478 4 9 4H27C27.2652 4 27.5196 4.10536 27.7071 4.29289C27.8946 4.48043 28 4.73478 28 5Z"></path>
</svg>
<span class="pliant-copy-btn-text">Copy</span>
</a>
</div>
</div>
<div aria-label="Exp, CVV" class="pliant-data pliant-expiry-cvv-result" role="group">
<div class="pliant-expiry-result">
<div class="pliant-field-name pliant-field-expiry-name">Exp</div>
<div class="pliant-field-value pliant-field-expiry-value">
<span class="pliant-field-expiry-value-text" data-field="expiryDate" title="10/26">10/26</span>
<a aria-label="Copy expiry date" class="pliant-copy-btn" data-target="pliant-field-expiry-value-text"
href="#" role="button"
tabindex="0">
<svg class="pliant-copy-btn-icon" fill="none" height="20" viewBox="0 0 32 32" width="20"
xmlns="http://www.w3.org/2000/svg">
<path
d="M23 8H5C4.73478 8 4.48043 8.10536 4.29289 8.29289C4.10536 8.48043 4 8.73478 4 9V27C4 27.2652 4.10536 27.5196 4.29289 27.7071C4.48043 27.8946 4.73478 28 5 28H23C23.2652 28 23.5196 27.8946 23.7071 27.7071C23.8946 27.5196 24 27.2652 24 27V9C24 8.73478 23.8946 8.48043 23.7071 8.29289C23.5196 8.10536 23.2652 8 23 8ZM22 26H6V10H22V26ZM28 5V23C28 23.2652 27.8946 23.5196 27.7071 23.7071C27.5196 23.8946 27.2652 24 27 24C26.7348 24 26.4804 23.8946 26.2929 23.7071C26.1054 23.5196 26 23.2652 26 23V6H9C8.73478 6 8.48043 5.89464 8.29289 5.70711C8.10536 5.51957 8 5.26522 8 5C8 4.73478 8.10536 4.48043 8.29289 4.29289C8.48043 4.10536 8.73478 4 9 4H27C27.2652 4 27.5196 4.10536 27.7071 4.29289C27.8946 4.48043 28 4.73478 28 5Z"></path>
</svg>
<span class="pliant-copy-btn-text">Copy</span>
</a>
</div>
</div>
<div class="pliant-cvv-result">
<div class="pliant-field-name pliant-field-cvv-name">CVV</div>
<div class="pliant-field-value pliant-field-cvv-value">
<span class="pliant-field-cvv-value-text" data-field="cvv" title="123">123</span>
<a aria-label="Copy CVV" class="pliant-copy-btn" data-target="pliant-field-cvv-value-text" href="#"
role="button"
tabindex="0">
<svg class="pliant-copy-btn-icon" fill="none" height="20" viewBox="0 0 32 32" width="20"
xmlns="http://www.w3.org/2000/svg">
<path
d="M23 8H5C4.73478 8 4.48043 8.10536 4.29289 8.29289C4.10536 8.48043 4 8.73478 4 9V27C4 27.2652 4.10536 27.5196 4.29289 27.7071C4.48043 27.8946 4.73478 28 5 28H23C23.2652 28 23.5196 27.8946 23.7071 27.7071C23.8946 27.5196 24 27.2652 24 27V9C24 8.73478 23.8946 8.48043 23.7071 8.29289C23.5196 8.10536 23.2652 8 23 8ZM22 26H6V10H22V26ZM28 5V23C28 23.2652 27.8946 23.5196 27.7071 23.7071C27.5196 23.8946 27.2652 24 27 24C26.7348 24 26.4804 23.8946 26.2929 23.7071C26.1054 23.5196 26 23.2652 26 23V6H9C8.73478 6 8.48043 5.89464 8.29289 5.70711C8.10536 5.51957 8 5.26522 8 5C8 4.73478 8.10536 4.48043 8.29289 4.29289C8.48043 4.10536 8.73478 4 9 4H27C27.2652 4 27.5196 4.10536 27.7071 4.29289C27.8946 4.48043 28 4.73478 28 5Z"></path>
</svg>
<span class="pliant-copy-btn-text">Copy</span>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
The rendering of elements depends on your configuration:
pliant-pan-result
will not be rendered ifpan.display = false
pliant-cardholder-result
will not be rendered ifcardholderName.display = false
pliant-expiry-result
will not be rendered ifexpiryDate.display = false
pliant-cvv-result
will not be rendered ifcvv.display = false
Structure in Destroyed State
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Card Details</title>
<script crossorigin="" src="./widget/static/pci-widget.js" defer="true"></script>
<link rel="stylesheet" href="./widget/static/pci-widget.css">
</head>
<body>
<div class="wrapper">
<div id="app" class="pliant-pci-widget" role="region" aria-label="Card Details" app-frame-id="cardholder-name-frame">
<div class="pliant-preloaded-data-overlay" role="button" tabindex="0" aria-label="Show card details" style="display: block;">Show card details</div>
</div>
</div>
</body>
</html>
Further Information
For technical questions or implementation support, contact [email protected].
Updated 1 day ago