SDK Setup

Onboarding inputs, environments, installation and the CSP rules for the PCI Compliant SDK.

Before You Start

Read the following guides before proceeding:

GuideWhy
PCI Compliant SDKIsolation model and package map
AuthenticationHow to mint the user token the SDK authenticates with
EnvironmentsBaaS base URLs and chain IDs

Overview

The SDK has no keys of its own. Every frame authenticates to Wirex with a user token — the token your
backend mints per user through
User Token Issuance (Login as User).
There are no publishable keys, no SDK secrets and nothing to rotate.

Setup is three inputs from onboarding, one npm install, and two CSP directives.


Onboarding Inputs

InputWhat it isWhere it goes
Client idNon-secret UUID identifying your integrationclientId in the SDK config; travels as ?client=<id> on the frame URL
Registered parent originsThe exact https:// origins of the pages that embed the card fieldsHeld by Wirex; emitted as the frames' frame-ancestors policy
User tokenPer-user access token from POST /api/v1/user/authorize — see Which Token the SDK NeedsReturned by your getBearerToken callback
Font CDN originOrigin serving your brand font filesHeld by Wirex; enters the frames' font-src policy. Required only when passing fontUrl or fontFaces

Register staging and production origins separately. Wildcard subdomains are accepted after review. Origin
changes apply within seconds and need no release on your side.

The client id is not authorization. It selects which registered-origin allowlist the browser enforces.
Presenting another integration's client id selects that integration's allowlist, on which your page still
does not appear, so the browser still refuses to embed. Authorization comes entirely from the bearer.


Environments

environmentFrame originCard dataMatching BaaS environment
'prod'https://frames.wirexapp.comLivehttps://api-baas.wirexapp.com
'uat'https://frames.wirexapp.techTest cards onlyhttps://api-baas.wirexapp.tech
'dev'https://frames-dev.wirexapp.techTest cards onlyWirex internal

environment defaults to 'prod' when omitted. Origins are baked into the SDK — you select an
environment, never a URL. An unrecognised value throws where the element is created, before any iframe is
mounted: PCI SDK: unknown environment "<value>" — expected 'dev', 'uat' or 'prod'.

A frame origin is bound to one backend environment. Select the frame environment that matches the BaaS
environment that issued your bearer — a token issued by one environment is not valid against another.

Do not self-host, bundle, cache or reverse-proxy the frame documents. The frames must load from the
Wirex frame origin. Serving them from anywhere else removes the origin boundary the isolation model
depends on, blocks security patches from reaching your integration, and voids the compliance posture
described in Compliance Posture.

The frameOrigin and framePath config fields exist for local development of the frames themselves. Do
not set them in a partner integration.


Install

Install only the capabilities you use:

npm install @wirexapp/card-topup-web
npm install @wirexapp/card-oct-web
npm install @wirexapp/card-reveal-web
npm install @wirexapp/card-3ds-web

@wirexapp/card-sdk-core is installed automatically as a dependency of each. For React applications,
install @wirexapp/card-react instead. For one dependency covering every
capability, install the umbrella package:

npm install @wirexapp/card-sdk-web

All packages release in lockstep under one version number. The current release is 1.0.0; deduplicate or
upgrade until every @wirexapp/card-* entry in your lockfile shows that same version.


Quickstart

Collect a top-up card and receive a cardId:

import { createCardTopUpFields } from '@wirexapp/card-topup-web';

const sdk = {
  environment: 'uat',            // omit in production — 'prod' is the default
  clientId: 'a4f6c1e2-9b03-4d77-8a51-2f0c6b8e4d19',
};

const topUp = createCardTopUpFields(sdk, {
  auth: {
    // Return a fresh USER token (POST /api/v1/user/authorize) for the current
    // user — refresh first if the session layer says it is close to expiry.
    getBearerToken: () => session.getAccessToken(),
  },
  onChange: state => {
    submitButton.disabled = !state.complete;
  },
});

await topUp.field('number').mount(document.querySelector('#card-number'));
await topUp.field('expiry').mount(document.querySelector('#card-expiry'));
await topUp.field('cvv').mount(document.querySelector('#card-cvv'));
await topUp.field('name').mount(document.querySelector('#card-name'));

const result = await topUp.tokenizeTopUp({
  billingDetails: {
    line1: '1 High Street',
    city: 'London',
    zipCode: 'N1 9GU',
    country: 'GB',
  },
});
// { cardId, brand, bin, last4, expiryMonth, expiryYear } — no PAN, no CVV.

Field iframes report their own height and the SDK sizes them. Width and placement come from your
containers.

Which Token the SDK Needs

getBearerToken must return a user token — the per-user access token from
User Token Issuance (Login as User).

S2S tokens are not accepted. The card-data endpoints the frames call require a user-scoped token.
A company token obtained from POST /api/v1/token does not work with the SDK, and no combination of
identity headers substitutes for one.

Your backend mints the token and passes it to the browser:

StepCallProduces
1POST /api/v1/token with grant_type: client_credentialsS2S token. Stays on your backend
2POST /api/v1/user/authorize with the S2S token and exactly one of X-User-Wallet, X-User-Email or X-User-Id, plus X-Chain-Id{ access_token, expires_at } — the user token
3Transfer access_token to the client application over your own session channelThe value getBearerToken returns
4Refresh from step 2 before expires_atA fresh user token

X-User-Wallet at step 2 is the user's EOA address, not the Smart Wallet address.

A user token carries the user id, wallet address, email and chain id in its own claims. The frames
therefore need no identity headers from your page, and cannot be pointed at another user.

The getBearerToken Callback

{
  auth: {
    getBearerToken: () => Promise<string>,
  }
}

The callback runs before every request the frame makes to Wirex. Return a fresh token each time — refresh
it first when your session layer reports it is close to expires_at.

ElementWhen the callback runs
Per-field collect — createCardTopUpFields, createOctDestinationFieldsAt tokenize only. Field frames carry no token while the user types
Combined collect — createCardTopUpForm, createOctDestinationFormAt mount and again at tokenize
Reveal — createCardRevealAt mount

A token that expires between mount and submit is the most common cause of a failed tokenize.

The token is passed to the frame over postMessage and used inside the frame's own origin. It never
appears in the frame URL and is not persisted by the frame.


Content Security Policy

Where your site ships a CSP, allow the frame origin and keep the SDK bundled with your own scripts:

Content-Security-Policy:
  frame-src https://frames.wirexapp.com;
  script-src 'self';
DirectiveValueWhy
frame-srcThe frame origin for your environmentThe card-field and reveal iframes load from it
frame-srcThe 3DS orchestrator origin from your onboarding packThe 3DS challenge page is served by a Wirex backend origin, not the frame origin
script-src'self'The SDK is bundled with your application from npm

The SDK makes no network calls from your page, so no connect-src entry is required. In non-production
CSPs use https://frames.wirexapp.tech.


Timeouts

PhaseLimitFailure
Frame ready handshake10 secondsmount() rejects with PCI frame did not become ready in time
Frame request — init or tokenize30 secondsThe call rejects with PCI frame request timed out

A mount that times out is usually an unregistered parent origin: the browser blocks the frame through
frame-ancestors, the frame renders empty, and the handshake never completes. See
Events and Error Handling.


Test Cards

Sandbox only.

PANResult
4242 4242 4242 4242Visa tokenization succeeds
5555 5555 5555 4444Mastercard tokenization succeeds

Any future expiry date and any 3-digit CVV pass validation. Contact your Wirex integration contact for the
current sandbox 3DS challenge and frictionless triggers.


Compliance Posture

  • The frames, the frame origin and its deployment pipeline sit inside Wirex's PCI DSS Level 1 Service
    Provider assessment. The AOC is available on request.
  • Every element that captures card data is delivered from Wirex's environment, which shapes an
    iframe-embedding merchant integration for SAQ A. Confirm the exact treatment with your acquirer or
    assessor.
  • The January 2025 SAQ A revision asks merchants to confirm their site is not susceptible to script
    attacks. Per PCI FAQ 1588, Wirex's written confirmation that the embedded solution — implemented per
    these instructions — includes script-attack protections satisfies that item. The confirmation letter
    ships in the compliance pack with the responsibility matrix (PCI DSS 12.9.2).

"Implemented per the instructions" is load-bearing. The rules in Your Responsibilities
are those instructions. Deviating from them voids the letter's applicability to your integration.


Your Responsibilities

  • Never self-host, bundle, cache or proxy the frame documents.
  • Never wrap the frames in an iframe of an origin that is not registered against your client id. Every
    ancestor in the chain is checked.
  • Never log a tokenize result beyond cardId, externalCardId, brand and last4, and never place those
    values in URLs.
  • Protect the bearer token to the same standard as your existing REST integration.

Going Live Checklist

  • Production parent origins registered against your client id
  • environment set to 'prod', or omitted
  • Font CDN origin registered, where a brand font is passed
  • frame-src allows the frame origin and the 3DS orchestrator origin
  • Every @wirexapp/card-* package on the same version in the lockfile
  • getBearerToken returns a user token from POST /api/v1/user/authorize, refreshed before expires_at
  • Mount failures and PciFrameRequestError handled (Events and Error Handling)
  • destroy() called on every element when its view unmounts

Did this page help you?