Retrieve a card image with the JS SDK
Render a digital, digital-first, or virtual card image securely in the cardholder's browser without card data touching your servers.
You can display the image of a digital or digital-first debit, prepaid, or virtual card directly in the cardholder's browser using the PCE JavaScript SDK. The SDK collects and renders sensitive card details client-side, so card data never touches your servers. This reduces your liability and keeps your PCI compliance scope small. By default the card's front image renders with the card details masked.
Why use the JS SDK
- PCI compliance and security: card data is handled securely in the browser, reducing the risk of a data breach.
- Reduced liability: because card data is never stored on your server, you avoid costly security infrastructure and simplify PCI compliance.
Set up the card renderer
Step 1: Import the SDK
Add the card renderer script to your HTML template. Use the environment that matches your integration.
<!-- Production -->
<script src="https://js.prioritypassport.com/public/card-renderer/card.renderer.js"></script>
<!-- Sandbox -->
<script src="https://js.sandbox.prioritypassport.com/public/card-renderer/card.renderer.js"></script>Step 2: Construct the token API URL
Build the token URL for the card whose image you want to render.
<baseURL>/v1/customer/id/<customerID>/account/id/<accountID>/<cardType>/id/<cardID>/image/tokenCollect these values from your inputs:
| Attribute | Description |
|---|---|
baseURL | PCE API base URL. Sandbox: https://api.sandbox.prioritypassport.com. Production: https://api.prioritypassport.com. Set as the default when creating the SDK instance. |
customerID | Unique identifier of the customer. |
accountID | Unique identifier of the customer's account. |
cardID | Unique identifier of the issued debit, prepaid, or virtual card. |
cardType | Type of card. Accepted values: debitCard, prepaidCard, virtualCard. |
Call the token API with the Program Manager's secure Bearer token.
Do not generate the access token from the browser. Doing so exposes your bearer token, which does not expire, and is a security risk. Generate the token server-side and pass only the short-lived image token to the browser.
Step 3: Define the image container
Add an img element where the rendered card image will be displayed. The SDK populates it dynamically.
<div id="image-container">
<img id="api-image" alt="Rendered card image" />
</div>Initialize and render the card image
You can also let cardholders copy the full card number (without spaces) to the clipboard with a single click, and position the copy button where you want it.
Step 1: Create a card instance
// Debit or virtual card
const sdk = new VirtualDebitCard();
// Prepaid card
const sdk = new PrepaidCard();This configures the base URL of the PCE integration API.
Step 2: Construct the token URL and fetch the token
// Debit or virtual card
const debitCardTokenURL = `${baseURL}/v1/customer/${customerIdType}/${customerID}/account/${accountIdType}/${accountID}/${cardType}/${cardIdType}/${cardID}/image/token`;
// Prepaid card
const prepaidCardTokenURL = `${baseURL}/v1/customer/${customerIdType}/${customerID}/issuance/card/${cardIdType}/${cardID}/image/token`;
async function fetchToken(tokenUrl, bearerToken) {
const response = await fetch(tokenUrl, {
method: 'GET',
headers: { 'Authorization': bearerToken }
});
if (!response.ok) {
throw new Error(`Token API returned status: ${response.status}`);
}
const data = await response.json();
return data.token;
}
const imageToken = await fetchToken(tokenURL, bearerToken);Step 3: Render the card image
Call renderImage() with the token from Step 2 to populate the img element.
sdk.renderImage(customerID, cardID, imageToken, "api-image", "pan-container");| Parameter | Description |
|---|---|
customerID | Unique identifier of the customer. |
cardID | Unique identifier of the issued card. |
imageToken | Token generated in Step 2. |
"api-image" | Id of the img element defined in the image container. |
"pan-container" | Id of the element where the copy-card-number button is inserted (optional). |
Complete HTML example
The following template shows the full integration, including card-type selection, token fetch, and rendering.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Card token and image rendering</title>
</head>
<body>
<div class="left-section">
<h1>Enter card details</h1>
<div class="radio-group">
<label><input type="radio" name="cardType" value="debitCard" checked /> Debit card</label>
<label><input type="radio" name="cardType" value="virtualCard" /> Virtual card</label>
<label><input type="radio" name="cardType" value="prepaidCard" /> Prepaid card</label>
</div>
<label for="baseURL">Base URL</label>
<input type="text" id="baseURL" placeholder="Enter base URL" />
<label for="customerID">Customer</label>
<div class="input-group">
<select id="customerIdType">
<option value="id">ID</option>
<option value="externalId">External ID</option>
</select>
<input type="text" id="customerID" placeholder="Enter customer ID" />
</div>
<label for="accountID">Account</label>
<div class="input-group">
<select id="accountIdType">
<option value="id">ID</option>
<option value="externalId">External ID</option>
</select>
<input type="text" id="accountID" placeholder="Enter account ID" />
</div>
<label for="cardID">Card</label>
<div class="input-group">
<select id="cardIdType">
<option value="id">ID</option>
<option value="externalId">External ID</option>
</select>
<input type="text" id="cardID" placeholder="Enter card ID" />
</div>
<label for="token">Token</label>
<input type="text" id="token" placeholder="Enter token" />
<label style="margin-top: 16px; display: block;">Copy card number button location</label>
<div class="radio-group">
<label><input type="radio" name="panButtonLocation" value="default" checked /> Default</label>
<label><input type="radio" name="panButtonLocation" value="custom" /> Custom (below the Render Card button)</label>
</div>
<button onclick="renderCard()">Render card</button>
<div id="pan-container"></div>
</div>
<div class="right-section">
<h1>Rendered card image</h1>
<div id="image-container">
<img id="api-image" />
</div>
</div>
<script src="https://js.prioritypassport.com/public/card-renderer/card.renderer.js"></script>
<script>
document.querySelectorAll('input[name="cardType"]').forEach((radio) => {
radio.addEventListener("change", function () {
const cardIdTypeDropdown = document.getElementById("cardIdType");
const customerIdTypeDropdown = document.getElementById("customerIdType");
const accountIdTypeDropdown = document.getElementById("accountIdType");
const accountIdInput = document.getElementById("accountID");
const accountLabel = document.querySelector('label[for="accountID"]');
customerIdTypeDropdown.value = "id";
accountIdTypeDropdown.value = "id";
cardIdTypeDropdown.value = "id";
cardIdTypeDropdown.disabled = this.value === "virtualCard";
if (this.value === "prepaidCard") {
accountIdTypeDropdown.style.display = "none";
accountIdInput.style.display = "none";
accountLabel.style.display = "none";
} else {
accountIdTypeDropdown.style.display = "block";
accountIdInput.style.display = "block";
accountLabel.style.display = "block";
}
});
});
</script>
<script>
const CardType = Object.freeze({
DEBIT: "debitCard",
VIRTUAL: "virtualCard",
PREPAID: "prepaidCard"
});
async function renderCard() {
const renderButton = document.querySelector("button");
renderButton.disabled = true;
const baseURL = document.getElementById("baseURL").value.trim();
const customerID = document.getElementById("customerID").value;
const customerIdType = document.getElementById("customerIdType").value;
const accountID = document.getElementById("accountID").value;
const accountIdType = document.getElementById("accountIdType").value;
const cardIdType = document.getElementById("cardIdType").value;
const cardID = document.getElementById("cardID").value;
const token = 'Bearer ' + document.getElementById("token").value;
const cardType = document.querySelector('input[name="cardType"]:checked').value;
const imageElement = document.getElementById("api-image");
imageElement.src = "";
if (!baseURL || !customerID || ((cardType === CardType.DEBIT || cardType === CardType.VIRTUAL) && !accountID) || !cardID || !token) {
alert("Please fill out all fields.");
renderButton.disabled = false;
return;
}
const tokenURL = cardType === CardType.PREPAID
? `${baseURL}/v1/customer/${customerIdType}/${customerID}/issuance/card/${cardIdType}/${cardID}/image/token`
: `${baseURL}/v1/customer/${customerIdType}/${customerID}/account/${accountIdType}/${accountID}/${cardType}/${cardIdType}/${cardID}/image/token`;
try {
const imageToken = await fetchToken(tokenURL, token);
if (imageToken) {
const sdk = cardType === CardType.PREPAID ? new PrepaidCard() : new VirtualDebitCard();
sdk.setBaseURL(baseURL);
const customer = customerIdType === 'id' ? new Customer(customerID, null) : new Customer(null, customerID);
const card = cardIdType === 'id' ? new Card(cardID, null) : new Card(null, cardID);
const panButtonLocation = document.querySelector('input[name="panButtonLocation"]:checked').value;
if (panButtonLocation === 'custom') {
sdk.renderImage(customer, card, imageToken, "api-image", "pan-container");
} else {
sdk.renderImage(customer, card, imageToken, "api-image");
}
} else {
alert("Failed to render card. Please check the inputs.");
}
} catch (error) {
console.error("Error rendering card:", error);
alert("Failed to render card. Please check the inputs.");
}
renderButton.disabled = false;
}
async function fetchToken(tokenApiUrl, token) {
const response = await fetch(tokenApiUrl, {
method: 'GET',
headers: { 'Authorization': token }
});
if (!response.ok) {
throw new Error(`Token API returned status: ${response.status}`);
}
const data = await response.json();
return data.token;
}
</script>
</body>
</html>Next steps
See also
- Manage your cards: activate, replace, or cancel an issued card
- Card Issuance: card types, formats, and the issuance workflow
Updated about 8 hours ago