Reveal Card Details
Display an issued card's PAN, expiry, CVV and PIN inside Wirex-served iframes without the values reaching your page.
Before You Start
Read the following guides before proceeding:
| Guide | Why |
|---|---|
| PCI Compliant SDK | Isolation model and package map |
| SDK Setup | Client id, environments, installation, CSP |
| Card Details | Confirmation methods and action-token rules |
| Card Issuance | How the card id is produced |
Overview
@wirexapp/card-reveal-web displays an issued card's sensitive details — full PAN, expiry, CVV and PIN —
inside Wirex-served iframes. The frame fetches the values directly from the Wirex API and renders them in
place. Copy-to-clipboard runs inside the frame. Your page mounts the element and receives status events; it
never receives the values.
The element calls the same endpoints documented in Card Details:
| Requested row | Endpoint the frame calls |
|---|---|
number, expiry | POST /api/v1/cards/{cardId}/details |
cvv | POST /api/v1/cards/{cardId}/cvv |
pin | POST /api/v1/cards/{cardId}/pin |
cardholder | None — rendered from the cardholderName you pass |
Prerequisite: Mint an Action Token
Reveal requires a one-shot action_token on top of the user token. Mint it through the confirmation flow —
authenticated with the same user token — immediately before mounting the element.
const nonce = Math.floor(Date.now() / 1000);
const message = `By signing this I confirm that I am executing action GetCardDetails at ${nonce}`;
const messageSignature = await wallet.signMessage(message);
const { action_token: actionToken } = await fetch(
`${baseUrl}/api/v1/confirmation/signature/verify`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'X-User-Wallet': userEoaAddress,
'X-Chain-Id': chainId,
'Content-Type': 'application/json'
},
body: JSON.stringify({ action_type: 'GetCardDetails', message_signature: messageSignature, nonce })
}
).then(response => response.json());| Property | Value |
|---|---|
| Validity | 5 minutes |
| Scope | GetCardDetails — one token covers the PAN, CVV and PIN endpoints of one reveal |
| Reuse | None. Re-opening the details view requires a fresh token |
SMS OTP confirmation produces the same token under the field name token. Both methods are documented in
Card Details.
Mount the Element
import { createCardReveal } from '@wirexapp/card-reveal-web';
const reveal = createCardReveal(
{ environment: 'uat', clientId: 'a4f6c1e2-9b03-4d77-8a51-2f0c6b8e4d19' },
{
cardId: '64120850-73a1-4df5-a074-d463258c9deb',
actionToken,
fetchDetails: true,
fetchCvv: true,
fields: ['cardholder', 'number', 'expiry', 'cvv'],
cardholderName: 'ALEX GREY',
auth: {
getBearerToken: () => session.getAccessToken(),
userEmail: user.email,
userWallet: user.walletAddress,
},
onStateChange: ({ status, errorMessage }) => {
if (status === 'error') showToast(errorMessage);
},
onCopied: ({ field }) => analytics.track('card_detail_copied', { field }),
},
);
await reveal.mount(document.querySelector('#card-details'));
// When the user closes the details view:
reveal.destroy();Configuration
| Field | Type | Required | Description |
|---|---|---|---|
cardId | string | Yes | Issued card to reveal |
actionToken | string | Yes | Single-use step-up token, consumed on fetch |
fetchDetails | boolean | Yes | Requests the number and expiry rows. Ignored when fields is set |
fetchCvv | boolean | Yes | Requests the cvv row. Ignored when fields is set |
fields | array | No | Exact rows to render, in order: cardholder, number, expiry, cvv, pin. Drives which endpoints the frame calls |
cardholderName | string | No | Rendered as a plain, non-sensitive row |
auth.getBearerToken | () => Promise<string> | Yes | Returns a fresh user token |
auth.userEmail | string | No | Sent as X-User-Email. A cross-origin frame cannot read your session |
auth.userWallet | string | No | Sent as X-User-Wallet |
appearance | object | No | Whitelisted styling tokens — see Appearance and Localization |
strings | object | No | Translated labels — see Strings Keys |
onStateChange | (state) => void | No | { status, errorMessage? } |
onCopied | (event) => void | No | { field } — which row the user copied. The value never leaves the frame |
fetchDetailsandfetchCvvare required by the TypeScript config type even whenfieldsis set. When
fieldsis present it decides the rows; the two flags are not read. Omittingfieldsrenders
cardholder, thennumberandexpirywhenfetchDetailsistrue, thencvvwhenfetchCvvis
true— thepinrow is reachable only throughfields.
The chain identifier and the API base URL are the frame service's own configuration. Neither is a config
field, so your page cannot point a reveal request at another host.
Identity Headers
auth.userEmail and auth.userWallet are sent as X-User-Email and X-User-Wallet. A
user token carries the user id, wallet address and
email in its own claims, so the API resolves the cardholder from the token and does not read these headers.
Setting both is safe with a user token and rejected with an S2S token, which reads exactly one identity
header and returnsErrorInvalidField(field: user_identifier) when given more. This is one more
reason the bearer must be a user token. See
User Identity Headers.
Element State
onStateChange: ({ status, errorMessage }) => {
// status: 'loading' | 'ready' | 'error'
}| Status | Meaning |
|---|---|
loading | The frame verified its init and started fetching. Nothing renders yet |
ready | At least one requested row loaded |
error | Every requested fetch failed. errorMessage is safe to display |
A partial failure reports
ready, noterror. Rows whose fetch failed render an em dash while the
rows that succeeded render their values. The error banner and theerrorstatus appear only when nothing
loaded at all.
Causes of error: a consumed or expired actionToken, an unknown card id, a card status that does not
permit the operation, or a backend failure. The action_token error reasons are listed in
Card Details.
Copy Behavior
Each row carries a Copy button that writes to the clipboard from inside the frame. Your page receives no
clipboard content and no event payload containing values — onCopied carries the field name and nothing
else. The reveal iframe is the only frame in the SDK granted clipboard-write; the collect frames are
granted no permissions at all.
Strings Keys
| Key | Default |
|---|---|
cardholderLabel | Cardholder |
cardNumberLabel | Card Number |
expiryLabel | Expiry date |
cvvLabel | CVV/CVC |
pinLabel | PIN |
copy | Copy |
copied | Copied |
loadError | Failed to load card details |
Lifecycle
- Mount the element when the user asks to see the details; call
destroy()when the view closes. - Elements are single-use. Re-opening the view requires a new element and a fresh
actionToken. mount()rejects when the frame document cannot load or refuses to initialize. See
Events and Error Handling.
Your Responsibilities
- Keep the reveal element short-lived. It exists for the duration of a user-initiated details view.
- Mint the
action_tokenimmediately before mounting. A token minted earlier in a flow expires 5 minutes
after issue. - Do not proxy
POST /api/v1/cards/{cardId}/details,/cvvor/pinthrough your own backend. Proxying
cardholder data is permitted only when that backend is itself PCI DSS compliant, which the SDK exists to
avoid.
Updated 15 days ago

