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 the cardId (a Pliant UUIDv4) and design parameters. Full configuration details are available in the widget config section.
<iframe 
        src="{host}/card-details/widget?traceId={traceId}&params={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&params=ewogICJ0aXRsZSI6ICJDYXJkIERldGFpbHMiLAogICJ0b2tlbiI6ICJ7cmVxdWVzdGVkIE9UUH0iLAogICJjYXJkSWQiOiAie1BsaWFudCdzIGNhcmRJZH0iLAogICJvdmVybGF5VGV4dCI6ICJTaG93IGNhcmQgZGV0YWlscyIsCiAgImNvcHlMYWJlbCI6ICJDb3B5IiwKICAiZnJhbWVJZCI6ICJjYXJkLWRldGFpbHMtd2lkZ2V0LWZyYW1lIiwKICAiY3NzQ2xhc3MiOiAid2lkZ2V0LWNvbnRhaW5lciIsCiAgInN0eWxlVXJsIjogIi4vd2lkZ2V0L3N0YXRpYy9wY2ktd2lkZ2V0LmNzcyIsCiAgInBhbiI6IHsKICAgICJsYWJlbCI6ICJDYXJkIG51bWJlciIsCiAgICAiZGlzcGxheSI6IHRydWUsCiAgICAiY2xpY2tUb0NvcHkiOiB0cnVlLAogICAgInRleHRPbkNvcHkiOiAiQ29weSBjYXJkIG51bWJlciIKICB9LAogICJjYXJkaG9sZGVyTmFtZSI6IHsKICAgICJsYWJlbCI6ICJDYXJkaG9sZGVyIG5hbWUiLAogICAgImRpc3BsYXkiOiB0cnVlLAogICAgImNsaWNrVG9Db3B5IjogdHJ1ZSwKICAgICJ0ZXh0T25Db3B5IjogIkNvcHkgY2FyZGhvbGRlciBuYW1lIgogIH0sCiAgImV4cGlyeURhdGUiOiB7CiAgICAibGFiZWwiOiAiRXhwIiwKICAgICJkaXNwbGF5IjogdHJ1ZSwKICAgICJjbGlja1RvQ29weSI6IHRydWUsCiAgICAidGV4dE9uQ29weSI6ICJDb3B5IGV4cGlyeSBkYXRlIgogIH0sCiAgImN2diI6IHsKICAgICJsYWJlbCI6ICJDVlYiLAogICAgImRpc3BsYXkiOiB0cnVlLAogICAgImNsaWNrVG9Db3B5IjogdHJ1ZSwKICAgICJ0ZXh0T25Db3B5IjogIkNvcHkgQ1ZWIgogIH0KfSA=" 
        allow="clipboard-read; clipboard-write">
</iframe>

Data Flow

The process works as follows:

  1. The cardholder authenticates on your frontend.
  2. The cardholder clicks a button to view sensitive card data.
  3. 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}'
    
    1. Your backend must validate the request to ensure the requesting user has permission to access the card data.
    2. Include a Pliant-Trace-Id header in the OTP request to enable tracing through all systems.
  4. After receiving the OTP, compile the PCI widget configuration using the schema below. Base64-encode the JSON configuration for use as the widgetConfig parameter.
  5. Create and load the iframe with src="{host}/card-details/widget?traceId={traceId}&params={widgetConfig}" using the same trace ID from the OTP request.
  6. During iframe loading, an RSA key pair is generated for data encryption (valid only for a short time).
  7. The sensitive card data is requested using the public RSA key and your widget configuration.
  8. The system verifies the browser's user agent against a blacklist, allowing only actual browser requests (tools like Postman or CLI utilities are blocked).
  9. The public RSA key encrypts the card data response containing the sensitive information, with the widget config determining which specific fields are returned.
  10. The iframe decrypts the card data using the private RSA key and displays it temporarily.
  11. 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.
  12. 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 with eventType set to CARD_DATA_LOADING_FAILED.

Environments

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=
ParameterTypeDescription
token (mandatory)stringOTP obtained through Pliant's endpoint
cardId (mandatory)uuidPliant's card id
titlestringIframe page title
styleUrlstringURL to CSS styles for the iframe content
{fieldName}.labelstringOverwrite field names for card details fields. Defaults:
pan → Card number
cardholderName → Cardholder name
expiryDate → Exp
cvv → CVV
{fieldName}.clickToCopybooleanEnable click to copy block for a particular field. Default: true
{fieldName}.displaybooleanLoad field block into view. Default: true
{fieldName}.textOnCopystringTitle of the copy element in the HTML DOM for a particular field. Defaults:
pan → Copy card number
cardholderName → Copy cardholder name
expiryDate → Copy expiry date
cvv → Copy CVV
overlayTextstringText displayed after sensitive card data is destroyed permanently. Default: Show card details
copyLabelstringLabel for the copy button. Default: Copy
cssClassstringdiv[@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
frameIdstringIframe 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 displayed
  • CARD_DATA_CLEARED - Card data display timed out
  • CARD_DATA_LOADING_SUCCESS - Card data displayed successfully
  • CARD_DATA_PAN_COPIED - PAN copied to clipboard
  • CARD_DATA_CARDHOLDER_NAME_COPIED - Cardholder name copied to clipboard
  • CARD_DATA_EXP_COPIED - Expiry date copied to clipboard
  • CARD_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 if pan.display = false
  • pliant-cardholder-result will not be rendered if cardholderName.display = false
  • pliant-expiry-result will not be rendered if expiryDate.display = false
  • pliant-cvv-result will not be rendered if cvv.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].