SDK Setup
Onboarding inputs, environments, installation and the CSP rules for the PCI Compliant SDK.
Before You Start
Read the following guides before proceeding:
| Guide | Why |
|---|---|
| PCI Compliant SDK | Isolation model and package map |
| Authentication | How to mint the user token the SDK authenticates with |
| Environments | BaaS 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
| Input | What it is | Where it goes |
|---|---|---|
| Client id | Non-secret UUID identifying your integration | clientId in the SDK config; travels as ?client=<id> on the frame URL |
| Registered parent origins | The exact https:// origins of the pages that embed the card fields | Held by Wirex; emitted as the frames' frame-ancestors policy |
| User token | Per-user access token from POST /api/v1/user/authorize — see Which Token the SDK Needs | Returned by your getBearerToken callback |
| Font CDN origin | Origin serving your brand font files | Held 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
environment | Frame origin | Card data | Matching BaaS environment |
|---|---|---|---|
'prod' | https://frames.wirexapp.com | Live | https://api-baas.wirexapp.com |
'uat' | https://frames.wirexapp.tech | Test cards only | https://api-baas.wirexapp.tech |
'dev' | https://frames-dev.wirexapp.tech | Test cards only | Wirex 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-webAll 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 fromPOST /api/v1/tokendoes 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:
| Step | Call | Produces |
|---|---|---|
| 1 | POST /api/v1/token with grant_type: client_credentials | S2S token. Stays on your backend |
| 2 | POST /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 |
| 3 | Transfer access_token to the client application over your own session channel | The value getBearerToken returns |
| 4 | Refresh from step 2 before expires_at | A 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
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.
| Element | When the callback runs |
|---|---|
Per-field collect — createCardTopUpFields, createOctDestinationFields | At tokenize only. Field frames carry no token while the user types |
Combined collect — createCardTopUpForm, createOctDestinationForm | At mount and again at tokenize |
Reveal — createCardReveal | At 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';
| Directive | Value | Why |
|---|---|---|
frame-src | The frame origin for your environment | The card-field and reveal iframes load from it |
frame-src | The 3DS orchestrator origin from your onboarding pack | The 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
| Phase | Limit | Failure |
|---|---|---|
| Frame ready handshake | 10 seconds | mount() rejects with PCI frame did not become ready in time |
| Frame request — init or tokenize | 30 seconds | The 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.
| PAN | Result |
|---|---|
4242 4242 4242 4242 | Visa tokenization succeeds |
5555 5555 5555 4444 | Mastercard 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 andlast4, 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
-
environmentset to'prod', or omitted - Font CDN origin registered, where a brand font is passed
-
frame-srcallows the frame origin and the 3DS orchestrator origin - Every
@wirexapp/card-*package on the same version in the lockfile -
getBearerTokenreturns a user token fromPOST /api/v1/user/authorize, refreshed beforeexpires_at - Mount failures and
PciFrameRequestErrorhandled (Events and Error Handling) -
destroy()called on every element when its view unmounts
Updated 10 days ago

