A POST request sent to generate a token to view a card's PAN.
WARNING: The card's PAN is a PCI sensitive field that should only be viewed on the client
device and never logged or stored in any form.
The response includes a callback URL and a token ID. The token is good for one use only.
Using the response object, the user should perform a POST request to the callback URL with a body of
{
tokenId: tokenId
}
to view the card's PAN.
Once the token is used in the POST request to the callback URL, a response similar to the following will be returned:
{
"creationTime": 1547573554173,
"modifiedTime": 1547573554173,
"id": "8202C315D8960020534E4D9E0C5509BE-1000057006",
"cardNumber": "2554663244155607944",
"panFirst6": "123409",
"panLast4": "8065",
"type": "phy",
"state": "created",
"sequenceNumber": 1,
"cardProfileName": "e6-personalized-card",
"shippingNumber": "shipping-number-22",
"pinFailCount": 0,
"reissue": true,
"expiry": "202706",
"customerNumber": "10000170010738",
"embossedName": "SIX/JOE",
"pan": "1234098745678065",
"cvv2": "290"
}
A wallet signature must be supplied in the request body to prove ownership of the
wallet that owns the card.
Example with Ethersjs:
const wallet = ethers.Wallet.createRandom();
const types = {
Authenticate: [
{ name: 'id', type: 'string' },
{ name: 'queriedAt', type: 'uint256' },
],
};
const domain = {
name: 'Kulipa Wallet Verification',
version: '1',
chainId: 42161, // the actual chain ID of the wallet network (e.g. 42161 for Arbitrum One)
verifyingContract: '0x...', // the user's withdrawal address
};
const message = {
id: 'crd-e259ee22-f857-444d-9c38-e0bf2ee669e3', // the card ID
queriedAt: Date.now(), // unix timestamp in milliseconds, validated within a short tolerance window of server time
};
const signature = await wallet.signTypedData(domain, types, message);
Example with Solana kit:
const keys = await generateKeyPair();
const address = await getAddressFromPublicKey(keys.publicKey);
const validTypedData = {
id: 'crd-e259ee22-f857-444d-9c38-e0bf2ee669e3', // the card ID
queriedAt: Date.now(), // unix timestamp in milliseconds, validated within a short tolerance window of server time
purpose: 'Card Sensitive Field Token',
organisation: 'Kulipa',
wallet: address,
};
const structCodec = getStructCodec([
['organisation', addCodecSizePrefix(getUtf8Codec(), getU32Codec())],
['purpose', addCodecSizePrefix(getUtf8Codec(), getU32Codec())],
['wallet', addCodecSizePrefix(getUtf8Codec(), getU32Codec())],
['id', addCodecSizePrefix(getUtf8Codec(), getU32Codec())],
['queriedAt', getU64Codec()],
]);
const encodedData = structCodec.encode(validTypedData);
const signedBytes = await signBytes(keys.privateKey, encodedData);
const signature = getBase58Decoder().decode(signedBytes);
| Time | Status | User Agent | |
|---|---|---|---|
Retrieving recent requests… | |||
400The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).
401Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated". That is, the client must authenticate itself to get the requested response.
403The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server.
429The user has sent too many requests in a given amount of time ("rate limiting").
500The server has encountered a situation it does not know how to handle.
