Skip to main content
The Reveal PIN widget allows you to securely display a card’s PIN directly in your application. The PIN is fetched, rendered, and auto-hidden entirely inside an isolated Synctera-origin iframe — it is never exposed to your page, never included in any event, and never logged — helping reduce your PCI scope. This is the Synctera replacement for the legacy Marqeta PIN reveal (pinReveal) flow. For the deprecated Marqeta integration, see Marqeta Widgets.

Step 1: Load the Widget Script

Load the Reveal PIN widget script into your page:
<script
  type="module"
  src="https://assets.synctera.com/widgets/reveal-pin/v1.0.0/index.js"
></script>

Step 2: Get a Widget Token

Request a widget token scoped to revealing the card’s PIN. Use the GET_PIN widget type:
curl -X GET "https://api.synctera.com/v2/cards/{card_id}/widget_token?widget_type=GET_PIN" \
  -H "Authorization: Bearer {apiKey}" \
  -H "Content-Type: application/json"
Widget tokens expire after 5 minutes and are scoped to a specific card. Generate a new token on each page load or when the user initiates a new reveal action.

Step 3: Add the Widget Component

Add the <reveal-pin> web component to your page:
<reveal-pin
  token="your-widget-token"
  env="production"
></reveal-pin>
By default the widget renders a placeholder (obscured dots) and waits for a reveal to be triggered — either by the built-in Show PIN button (set show-toggle) or by calling requestPin() from your own UI. Once revealed, the PIN displays with a live auto-hide countdown and is cleared automatically when the countdown ends.

Configuration Options

PropertyTypeRequiredDefaultDescription
tokenstringYes-Widget token obtained from the API (GET_PIN type)
envstringYes-Environment: sandbox or production
themestringNo"default"Theme preset: "default" or "night-shift"
stylesstringNo"{}"JSON string of design tokens for visual customization
auto-hide-secondsnumberNo5Seconds the PIN stays visible before it auto-hides, with a live countdown. Set to 0 to disable auto-hide (see PCI note below).
show-togglebooleanNofalseRenders a built-in Show PIN button inline with the obscured dots
toggle-labelstringNo"Show PIN"Custom label text for the built-in button
custom-labelsstringNo"{}"JSON string of custom labels

Auto-hide Countdown

The PIN auto-hides after auto-hide-seconds (default 5), mirroring the legacy Marqeta hidePinTimeout behavior. This is a PCI safeguard, not just a UX nicety: it limits how long the PIN is on screen.
<reveal-pin
  token="your-token"
  env="production"
  auto-hide-seconds="10"
></reveal-pin>
Setting auto-hide-seconds="0" disables auto-hide entirely: the PIN stays visible with no countdown until the widget is torn down or the PIN is re-requested. This weakens your PCI posture — only disable it deliberately.

Built-in Button

Set show-toggle to render a built-in Show PIN button next to the obscured dots. When omitted, you control the reveal yourself by calling requestPin().
<reveal-pin
  token="your-token"
  env="production"
  show-toggle
  toggle-label="Reveal PIN"
></reveal-pin>

Custom Labels

Customize the labels displayed in the widget:
<reveal-pin
  token="your-token"
  env="production"
  custom-labels='{
    "pinLabel": "Card PIN",
    "toggleLabel": "Show PIN",
    "retryButtonText": "Try Again"
  }'
></reveal-pin>
Available custom label keys:
  • pinLabel - Label shown above the PIN (default: "PIN")
  • toggleLabel - Built-in button label (default: "Show PIN"). The dedicated toggle-label attribute takes precedence if both are set.
  • retryButtonText - Error state retry button text (default: "Try Again")

Event Handling

The widget dispatches events for both initialization and PIN reveal outcomes:
  • load — The widget finished initializing and is ready to use.
  • error — The widget failed to initialize (network/CSP failure or handshake timeout).
  • success — PIN retrieved and rendered successfully inside the secure iframe.
  • failure — PIN reveal failed (API error, timeout, etc.).
The Reveal PIN widget does not emit a copy event. The PIN cannot be copied to the clipboard and text selection is disabled inside the widget — both are intentional PCI safeguards.
You can subscribe with addEventListener or by assigning the matching callback property:
EventaddEventListenerCallback property
Initialization successwidget.addEventListener('load', fn)widget.onLoad = fn
Initialization failurewidget.addEventListener('error', fn)widget.onError = fn
Reveal successwidget.addEventListener('success', fn)widget.onSuccess = fn
Reveal failurewidget.addEventListener('failure', fn)widget.onFailure = fn

Load Event

Dispatched once when the widget has finished initializing and is ready to use. Use this to reveal the widget UI only after it’s ready, or to dismiss a loading placeholder. Fires exactly once per iframe lifecycle.
PropertyTypeDescription
instanceIdstringUnique ID for this widget instance
<reveal-pin id="pin-widget" token="your-token" env="production"></reveal-pin>
<script>
  document.getElementById('pin-widget').addEventListener('load', (event) => {
    const { instanceId } = event.detail;
    console.log('Widget ready', instanceId);
  });
</script>

Error Event

Dispatched once when the widget fails to initialize — either because the underlying iframe could not load (network failure, blocked by Content Security Policy, etc.) or because the iframe loaded but never completed its handshake within 5 seconds. This is distinct from the failure event, which signals that a PIN reveal API call failed after the widget was already running.
PropertyTypeDescription
errorstringHuman-readable error message (safe to display to end users)
failedFieldsstring[]Logical surfaces that failed to initialize. The Reveal PIN widget has a single display surface, so this is always ['display-controller'].
instanceIdstringUnique ID for this widget instance
<script>
  document.getElementById('pin-widget').addEventListener('error', (event) => {
    const { error, failedFields } = event.detail;
    console.error('Widget initialization failed:', error, failedFields);
    // Show error UI or offer a retry button
  });
</script>

Success Event

Dispatched when the PIN is successfully retrieved and rendered inside the widget’s secure iframe. The PIN itself is never included in the event — only metadata.
PropertyTypeDescription
instanceIdstringUnique ID for this widget instance
<reveal-pin id="pin-widget" token="your-token" env="production"></reveal-pin>
<script>
  document.getElementById('pin-widget').addEventListener('success', (event) => {
    const { instanceId } = event.detail;
    console.log('PIN revealed successfully', instanceId);
  });
</script>

Failure Event

Dispatched when the PIN reveal API call fails.
PropertyTypeDescription
errorstringHuman-readable error message
instanceIdstringUnique ID for this widget instance
<script>
  document.getElementById('pin-widget').addEventListener('failure', (event) => {
    const { error } = event.detail;
    console.error('PIN reveal failed:', error);
    // Show error UI to user
  });
</script>

Public Methods

The widget exposes methods for programmatic control. These are useful when you want to control the reveal behavior externally instead of using the built-in button (show-toggle).
MethodDescription
requestPin()Trigger the PIN reveal API call
refresh()Reset state and re-request the PIN
<reveal-pin
  id="pin-widget"
  token="your-token"
  env="production"
></reveal-pin>

<button onclick="document.getElementById('pin-widget').requestPin()">
  Reveal PIN
</button>

PCI Considerations

The Reveal PIN widget is designed to keep the PIN out of your PCI scope:
  • The PIN is fetched and rendered entirely inside the Synctera-origin iframe. It never crosses the iframe boundary to your page.
  • The PIN is never included in any event (success carries only an instanceId) and is never logged.
  • The PIN cannot be copied to the clipboard, and text selection is disabled, so it can’t be highlighted and copied.
  • The PIN auto-hides after auto-hide-seconds and is cleared from the DOM and memory when hidden.

Complete Example

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Reveal PIN</title>
    <script
      type="module"
      src="https://assets.synctera.com/widgets/reveal-pin/v1.0.0/index.js"
    ></script>
  </head>
  <body>
    <h1>Your PIN</h1>

    <div id="error-message" style="color: red; display: none;"></div>

    <reveal-pin
      id="pin-widget"
      env="production"
      auto-hide-seconds="5"
      show-toggle
      toggle-label="Show PIN"
    ></reveal-pin>

    <script>
      async function initializeWidget() {
        try {
          const response = await fetch('/api/pin-widget-token', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              card_id: 'your-card-id',
              widget_type: 'GET_PIN'
            })
          });

          const { widget_token } = await response.json();

          const widget = document.getElementById('pin-widget');
          widget.setAttribute('token', widget_token);

          widget.addEventListener('load', (event) => {
            console.log('Widget ready:', event.detail.instanceId);
          });

          widget.addEventListener('error', (event) => {
            const { error, failedFields } = event.detail;
            console.error('Widget failed to initialize:', error, failedFields);
            document.getElementById('error-message').textContent = error;
            document.getElementById('error-message').style.display = 'block';
          });

          widget.addEventListener('success', (event) => {
            console.log('PIN revealed:', event.detail.instanceId);
          });

          widget.addEventListener('failure', (event) => {
            document.getElementById('error-message').textContent = event.detail.error;
            document.getElementById('error-message').style.display = 'block';
          });

        } catch (error) {
          console.error('Failed to initialize widget:', error);
        }
      }

      window.addEventListener('DOMContentLoaded', initializeWidget);
    </script>
  </body>
</html>

Migration from Marqeta PIN Reveal

If you previously displayed the PIN using the Marqeta.js pinReveal component, the Reveal PIN widget is the modern, Synctera-hosted replacement.
Marqeta (Old)Synctera (New)
marqeta.min.js + window.marqeta.bootstrap({ component: { pinReveal: ... } })<reveal-pin> web component
POST /v0/cards/{card_id}/client_tokenGET /v2/cards/{card_id}/widget_token?widget_type=GET_PIN
cardholderVerificationMethod: 'OTHER' (manual config)Handled internally — no configuration needed
hidePinTimeout (5–15s)auto-hide-seconds (default 5)
toggleCardPinshow-toggle / requestPin()
onSuccess / onFailure callbacksload / error / success / failure DOM events
See the Migration Guide for the Activate Card and Set PIN equivalents.