Secure iframes
As an Airwallex customer, you are required to be PCI compliant to retrieve sensitive details of individual cards or cards issued to connected accounts (if you have a Scale implementation) using Get sensitive card details API.
The PAN delegation feature allows you to retrieve sensitive details using a secure token in a PCI-compliant manner regardless of your PCI compliance status.
Before you begin
- Contact your Airwallex Account Manager to enable Issuing APIs, Cards, and PAN delegation for your Airwallex account.
- Obtain your access token API by authenticating to Airwallex using your unique Client ID and API key. You will need the access token to make API calls.
Step 1: Retrieve a token
Use Create a PAN token API with the card_id parameter (the ID of the individual card or the card issued to the connected account). Note that as a platform account, must specify the connected account’s open ID (starting with the prefix acct_) in the x-on-behalf-of header as you are acting on behalf of the connected account.
A successful response will return a token and its expiration date. The token expires after a minute from when it’s issued. The iframe in the next step will need to be loaded within this time.
Example request
Shell1curl --request POST \2--url 'https://api-demo.airwallex.com/api/v1/issuing/pantokens/create'3--header 'Authorization: Bearer <your_bearer_token>'4--data '{5 "card_id": "75c89b87-eb15-453a-85d7-621c104f707d"6}
Example response
JSON1{2 "expires_at": "2021-03-22T00:29:34.558+0000",3 "token": "1HxHDfKJNnITGTULgnNoAADkgE1q+tMecRMVnk0w5LbpuqXeYdytS/EorRNvJZAFftjQCse6+0UPJNe+dzDwfZv+lxuLt06Blc59BSZkwRx1kIMwtkvPmam7PZNf8ZQvOEfTv6+5Cei/jkjUpivhRA4THHc2hX2gpDSIr/mljHhKXMkKxQU+F7USo2x52cNslYhQVl04U5PYdUbnygJnFtmE+Fd3ENy+HHfkErCCTOTcVzwRRA=="4}
Step 2: Prepare a hash
After retrieving the token, prepare a hash for the iframe. The hash allows you to inject the initial values and options for the iframe. Currently, its typing is as below:
1const hash = {2 token: "The token fetched via api for the card",3 rules: {4 ".details": { CSSType },5 ".pin": { CSSType }6 },7 langKey: "zh"8 };910 const hashURI = encodeURIComponent(JSON.stringify(hash));
The hash accepts three fields described in the following sections.
token [Required]
1const res = await get('https://api.airwallex.com/api/v1/issuing/pantokens/create', {2 card_id3});45const hash = {6 token: res.token7};
rules [Optional]
To provide a consistent, seamless user experience when displaying the card details or card PIN, you can provide rules to style the iframe. Rules are a map of selectors and CSS properties. Both the selectors and the CSS properties are white listed.
Here's an example of hash with style configuration.
1const hash = {2 token: YOUR_TOKEN,3 rules: {4 '.pin': {5 fontSize: '40px',6 fontWeight: '800',7 letterSpacing: '2px'8 }9 }10}1112const hashURI = encodeURIComponent(JSON.stringify(hash));
The available CSS properties are listed below, for a list of available selectors, see Style iframe.
1type CSSProperties =2 | '-moz-osx-font-smoothing'3 | '-webkit-font-smoothing'4 | '-webkit-text-fill-color'5 | 'backgroundColor'6 | 'border'7 | 'borderBottom'8 | 'borderColor'9 | 'borderLeft'10 | 'borderRadius'11 | 'borderRight'12 | 'borderStyle'13 | 'borderTop'14 | 'borderWidth'15 | 'boxShadow'16 | 'color'17 | 'font'18 | 'fontFamily'19 | 'fontFeatureSettings'20 | 'fontSize'21 | 'fontStyle'22 | 'fontVariant'23 | 'fontWeight'24 | 'letterSpacing'25 | 'lineHeight'26 | 'margin'27 | 'marginBottom'28 | 'marginLeft'29 | 'marginRight'30 | 'marginTop'31 | 'outline'32 | 'outlineColor'33 | 'outlineOffset'34 | 'outlineStyle'35 | 'outlineWidth'36 | 'padding'37 | 'paddingBottom'38 | 'paddingLeft'39 | 'paddingRight'40 | 'paddingTop'41 | 'textAlign'42 | 'textDecoration'43 | 'textIndent'44 | 'textJustify'45 | 'textShadow'46 | 'textTransform'47 | 'transition'48 | 'wordBreak'49 | 'wordSpacing'50 | 'wordWrap';
langKey [Optional]
You can use langKey to define the language of the iframe. When not provided or if the specified language is unavailable, it defaults to “en”. The list of valid langKey values include:
| Value | Language |
|---|---|
| en | English |
| zh | Chinese (Simplified) |
| zh-Hant | Chinese (Traditional) |
1const hash = {2...3langKey: 'en',4};5
Step 3: Embed iframe on your page
You can embed two types of iframes on your page:
- The card details iframe displays the card number, expiry and cvv.
- The card PIN iframe displays the card PIN
Display card details iframe
The card details iframe can be displayed using the following endpoint.
https://airwallex.com/issuing/pci/v2/:cardId/details#:hash
1const res = await get('https://api.airwallex.com/v1/issuing/pantokens/create', {2 card_id3});45const hash = {6 token: res.token7};89const hashURI = encodeURIComponent(JSON.stringify(hash));1011return12 <iframe src={`https://airwallex.com/issuing/pci/v2/$\{cardId\}/details#$\{hashURI\}`}/>
Display card PIN iframe
The card PIN iframe can be displayed using the following endpoint.
https://airwallex.com/issuing/pci/v2/:cardId/pin/#:hash
1const hashURI = encodeURIComponent(JSON.stringify(hash));23return4 <iframe src={`https://airwallex.com/issuing/pci/v2/$\{cardId\}/pin#$\{hashURI\}`}/>
The PIN iframe does not accept any langKey as it only displays numbers.
Step 4: Style iframe
You can style the iframe by providing rules when preparing the hash.
Style card details iframe
The available selectors for the card details iframe are listed below:
1.details2.details__row3.details__row--card-number4.details__row--expiry-date5.details__row--security-code6.details__content7.details__label8.details__value9.details__tooltip10.details__button11.details__button:hover12.details__button:active13.details__button:focus14.details__button svg
Example code
1const hash = {2 token: token,3 rules: {4 '.details': {5 backgroundColor: '#2a2a2a',6 color: 'white',7 borderRadius: '20px',8 fontFamily: 'Arial'910 },11 '.details__row': {12 display: 'flex',13 justifyContent: 'space-between',14 padding: '20px'15 },16 '.details__label': {17 width: '100px',18 fontWeight: 'bold'19 },20 '.details__content': { display: 'flex' },21 '.details__button svg': { color: 'white' }22 }23};
Styled card details iframe
The example code above creates the styling changes as shown in the visual.
Before styling vs after styling

Style card PIN iframe
The available selectors for the card PIN iframe are:
1.pin2.pin__value
Example code, PIN iframe
1const hash = {2 token: token,3 rules: {4 '.pin': {5 backgroundColor: '#2a2a2a',6 color: 'white',7 borderRadius: '12px',8 fontFamily: 'Arial',9 padding: '20px'10 },11 '.pin_value': {12 fontSize: '24px',13 fontWeight: 'bold',14 letterSpacing: '4px'15 }16 }17};
Step 5: Handle iframe event lifecycle
The iframe uses the postMessage API to inform the parent page about its lifecycle including load status and/or errors. Possible event types for the card details iframe are listed below.
1// iframe has completed its first render2{3 type: `$\{cardId\}:details:mounted`4}
1// iframe is loading details2{3 type: `$\{cardId\}:details:loading`4}
1// iframe has loaded details2{3 type: `$\{cardId\}:details:loaded`4}
1// iframe has encountered an error while parsing hash or loading2{3 type: `$\{cardId\}:details:error`;4 error: 'cannot_retrieve_details' | 'unable_to_parse_hash' | 'unknown_hash_format' | 'invalid_hash_format';5}
1// iframe has resized and is returning its new height2{3 type: `$\{cardId\}:details:resize`;4 height: number;5};
The card PIN iframe has the same events except details is replaced with pin.
1// Card details iframe event2{3 type: `$\{cardId\}:details:error`;4 error: 'cannot_retrieve_details' | 'unable_to_parse_hash' | 'unknown_hash_format' | 'invalid_hash_format';5}
1// Card PIN iframe event2{3 type: `$\{cardId\}:pin:error`;4 error: 'cannot_retrieve_pin' | 'unable_to_parse_hash' | 'unknown_hash_format' | 'invalid_hash_format';5}
Full example code (React)
1export const IframeDetails = ({ cardId }) => {2 const [error, setError] = useState(false);3 const [loaded, setLoaded] = useState(false);4 const [height, setHeight] = useState(0);56 function handleMessage(event) {7 if (!event.origin.includes('airwallex.com')) return;8 if (event?.data.type === `$\{cardId\}:details:loaded`) setLoaded(true);9 if (event?.data.type === `$\{cardId\}:details:error`) setError(true);10 if (event?.data.type === `$\{cardId\}:details:resize`) setHeight(event?.data.height);11 }1213 useEffect(() => {14 window.addEventListener('message', handleMessage);15 return () => window.removeEventListener('message', handleMessage);16 }, []);1718 const hash = {19 token: getAuthToken(),20 langKey: 'en',21 rules: {22 '.details': {23 font: '14px Arial, sans-serif',24 margin: '0',25 },26 },27 };28 const hashURI = encodeURIComponent(JSON.stringify(hash));2930 return (31 <>32 {!loaded && <p>loading...</p>}33 {error && <p>Oops something went wrong</p>}34 <iframe35 style={{ height, display: loaded ? 'block' : 'none' }}36 frameBorder="0"37 src={`https://airwallex.com/issuing/pci/v2/$\{cardId\}/details#$\{hashURI\}`}38 />39 </>40 );41}