---
title: "JavaScript"
---
This guide shows you how to request and decrypt sensitive card details using Node.js. The request flow generates an RSA key pair in memory, Base64-encodes the public key in PEM format, and sends it in the `X-Client-Public-Key` header. The decryption flow uses Rivest-Shamir-Adleman Optimal Asymmetric Encryption Padding (RSA-OAEP) to unwrap the Advanced Encryption Standard (AES) key. Then Advanced Encryption Standard 256-bit Galois/Counter Mode (AES-256-GCM) is used to decrypt the card data.

## Prerequisites

Before you begin, you need:

* Node.js 18 or later installed on your system.

> **Note:** The code samples use the global `fetch`, which is available by default starting in Node.js 18.

* An API key for the Gravv API
* A card ID to retrieve sensitive details for

## Generate the RSA key pair

Generate a 2048-bit RSA key pair in memory using Node.js's built-in `crypto` module. Keep the private key in scope to decrypt the response:

```javascript
import crypto from "crypto";

const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", {
    modulusLength: 2048,
});
```

## Encode the public key

Export the public key as PEM (SubjectPublicKeyInfo), then Base64-encode the entire PEM string. This is the value to send in the `X-Client-Public-Key` header:

```javascript
const publicKeyPem = publicKey.export({ type: "spki", format: "pem" });
const base64PublicKey = Buffer.from(publicKeyPem, "utf8").toString("base64");
```

> **Do not** send only the modulus (`n` from a JSON Web Key), the raw DER bytes, or the PEM string without Base64-encoding it. The header value must Base64-decode to the full PEM text, including the `-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----` markers. The header is rejected if it contains newlines or spaces, which is why raw PEM cannot be sent directly.

## Request the encrypted card details

Send a GET request to the [View card sensitive details](/api-reference/cards/get-v1-cards-card-id-sensitive-details) endpoint with the Base64-encoded public key:

```javascript
const response = await fetch(
    `https://api.gravv.xyz/v1/cards/${cardId}/sensitive-details`,
    {
        method: "GET",
        headers: {
            "Api-Key": apiKey,
            "X-Client-Public-Key": base64PublicKey,
            "Content-Type": "application/json",
        },
    }
);

const { data: payload, error } = await response.json();
if (error) {
    throw new Error(`Gravv API error: ${JSON.stringify(error)}`);
}
```

The `payload` contains the `encrypted_key`, `nonce`, and `ciphertext` fields needed to decrypt the card details. The remaining steps use the in-memory `privateKey` you generated above.

## Understanding the encrypted response

The [View card sensitive details](/api-reference/cards/get-v1-cards-card-id-sensitive-details) and the [Get card PIN](/api-reference/cards/get-v1-cards-card-id-pin) endpoints return three Base64-encoded values:

| Field         | Description                                          |
| :------------ | :--------------------------------------------------- |
| encrypted_key | AES-256 key encrypted with your RSA public key       |
| nonce         | 12-byte initialization vector for AES-GCM decryption |
| ciphertext    | Encrypted card details                               |

## Decode the Base64 values

Convert all three Base64-encoded values from the API response into buffers:

```javascript
const encryptedKey = Buffer.from(payload.encrypted_key, "base64");
const nonce = Buffer.from(payload.nonce, "base64");
const ciphertext = Buffer.from(payload.ciphertext, "base64");
```

## Decrypt the AES key

Use your RSA private key to decrypt the AES key. The decryption uses RSA-OAEP padding with SHA-256. The `key` option accepts either a `crypto.KeyObject` (such as the in-memory `privateKey` generated above) or a PEM-encoded string:

```javascript
const aesKey = crypto.privateDecrypt(
    {
        key: privateKey,
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        oaepHash: "sha256",
    },
    encryptedKey
);
```

## Separate the authentication tag

The last 16 bytes of the ciphertext contain the GCM authentication tag. Separate this from the encrypted data:

```javascript
const authTag = ciphertext.slice(ciphertext.length - 16);
const encryptedData = ciphertext.slice(0, ciphertext.length - 16);
```

## Decrypt the card details

Create an AES-256-GCM decipher, set the authentication tag, and decrypt the card details:

```javascript
const decipher = crypto.createDecipheriv("aes-256-gcm", aesKey, nonce);
decipher.setAuthTag(authTag);

let decrypted = decipher.update(encryptedData).toString("utf8");
decrypted += decipher.final("utf8");
```

## Complete end-to-end code

The following implementation generates an RSA key pair, encodes the public key, requests sensitive card details, and decrypts the response in a single function. The private key is held only in memory:

```javascript
import crypto from "crypto";

export async function getCardSensitiveDetails({ apiKey, cardId, baseUrl = "https://api.gravv.xyz" }) {
    // Step 1: Generate RSA key pair in memory
    const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", {
        modulusLength: 2048,
    });

    // Step 2: Base64-encode the PEM-formatted public key
    const publicKeyPem = publicKey.export({ type: "spki", format: "pem" });
    const base64PublicKey = Buffer.from(publicKeyPem, "utf8").toString("base64");

    // Step 3: Request the encrypted card details
    const response = await fetch(`${baseUrl}/v1/cards/${cardId}/sensitive-details`, {
        method: "GET",
        headers: {
            "Api-Key": apiKey,
            "X-Client-Public-Key": base64PublicKey,
            "Content-Type": "application/json",
        },
    });

    const body = await response.json();
    if (!response.ok || body.error) {
        throw new Error(`Gravv API error: ${JSON.stringify(body.error ?? body)}`);
    }

    const payload = body.data;
    if (!payload?.encrypted_key || !payload?.ciphertext || !payload?.nonce) {
        throw new Error("Card sensitive details are not available. Please try again in a moment.");
    }

    // Step 4: Decode Base64 values
    const encryptedKey = Buffer.from(payload.encrypted_key, "base64");
    const nonce = Buffer.from(payload.nonce, "base64");
    const ciphertext = Buffer.from(payload.ciphertext, "base64");

    // Step 5: Decrypt the AES key using RSA-OAEP
    const aesKey = crypto.privateDecrypt(
        {
            key: privateKey,
            padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
            oaepHash: "sha256",
        },
        encryptedKey
    );

    // Step 6: Separate authentication tag from ciphertext
    const authTag = ciphertext.slice(ciphertext.length - 16);
    const encryptedData = ciphertext.slice(0, ciphertext.length - 16);

    // Step 7: Decrypt using AES-256-GCM
    const decipher = crypto.createDecipheriv("aes-256-gcm", aesKey, nonce);
    decipher.setAuthTag(authTag);

    let decrypted = decipher.update(encryptedData, null, "utf8");
    decrypted += decipher.final("utf8");

    return JSON.parse(decrypted);
}
```

## Usage example

The following example calls `getCardSensitiveDetails` with an API key and card ID loaded from environment variables and logs the decrypted card details to the console:

```javascript
const cardDetails = await getCardSensitiveDetails({
    apiKey: process.env.GRAVV_API_KEY,
    cardId: "dce0192a-9b3d-440b-9500-33dc9ac8dc20",
});

console.log("Card details:", cardDetails);
// { pan: "4111111111111111", cvv: "123" }
```

If you already have the encrypted payload and only need to decrypt it, call `crypto.privateDecrypt` and `crypto.createDecipheriv` as shown in the [Decrypt the AES key](#decrypt-the-aes-key) and [Decrypt the card details](#decrypt-the-card-details) sections with the PEM string in the `key` option.

