Skip to main content

Overview

There are a few options for how you can send card data to Knot when integrating the CardSwitcher product. The first option is to to send the card data encrypted in a JWE, the second option is to send the card data in JSON to a secure endpoint controlled by a vault provider, and the third is to rely on one of Knot’s direct processor integrations. No option is more or less secure than the other, all maintain strict handling of the card data to comply with PCI guidelines, and all are valid options for sending card data to Knot.

Send Encrypted Data Directly to Knot

This option is recommended.
In this option, you can encrypt the JSON payload of card data in a JWE format prior to sending it to Knot.
  1. Get a JWE public key from Knot’s Retrieve JWK endpoint.
  2. Encrypt the JSON string payload of the card data with the JWE public key (referenced here).
  3. Send the encrypted payload to Switch Card (JWE).
  4. Knot sends the encrypted data (the JWE) to a PCI-compliant vendor’s environment.
  5. Knot receives an alias associated with the encrypted card data.
Card data is never processed or stored outside PCI-compliant vendor environments. After card data is used for a card switch, it is explicitly deleted from the PCI-compliant vendor’s vault within milliseconds.

VGS JWE Encryption

If you already have a vault set up with Very Good Security (VGS), you can manipulate the payload and send the JWE directly from your VGS vault via an outbound route to the Switch Card (JWE) endpoint. Please see the VGS StarLarky code sample below for guidance or reach out to the Knot team.

Code Samples

Below are a number of code samples demonstrating how to structure and encrypt a JWE:
import { CompactEncrypt, importJWK, JWK } from 'jose';

const KNOT_BASE = 'https://development.knotapi.com';

function basicAuthHeader(clientId: string, secret: string): string {
    const creds = Buffer.from(`${clientId}:${secret}`, 'utf8').toString('base64');
    return `Basic ${creds}`;
}

/**
 * GetKey gets the JWK associated with your client ID from the Knot API.
 * The implementation follows the steps outlined in the KnotAPI documentation:
 * https://docs.knotapi.com/api-reference/products/card-switcher/retrieve-jwk
 */
export async function getKey(): Promise<JWK> {
    const clientId = process.env.KNOT_CLIENT_ID || '';
    const secret = process.env.KNOT_SECRET || '';

    if (!clientId || !secret) {
        throw new Error('KNOT_CLIENT_ID and KNOT_SECRET must be set in the environment.');
    }

    const resp = await fetch(`${KNOT_BASE}/jwe/key`, {
        method: 'GET',
        headers: {
            Authorization: basicAuthHeader(clientId, secret),
            Accept: 'application/json',
        },
    });

    if (!resp.ok) {
        const text = await resp.text().catch(() => '');
        throw new Error(`Failed to fetch JWK (${resp.status}): ${text}`);
    }

    const jwk = (await resp.json()) as JWK;
    return jwk;
}

/**
 * encryptData encrypts the card data using the provided JWK, the JWE must contain the JWK's "kid" and "alg" parameters to be considered valid.
 * The implementation follows the steps outlined in the KnotAPI documentation:
 * https://docs.knotapi.com/api-reference/products/card-switcher/retrieve-jwk#building-the-jwe
 * You can also opt to implement these RFC's manually instead of using a library:
 * https://datatracker.ietf.org/doc/html/rfc7516
 * https://datatracker.ietf.org/doc/html/rfc7517
 * https://datatracker.ietf.org/doc/html/rfc7518
 * https://datatracker.ietf.org/doc/html/rfc7638
 */
export async function encryptData(cardData: unknown, jwk: JWK): Promise<string> {
    const alg = jwk.alg;
    if (!alg) {
        throw new Error('JWK is missing "alg" (required).');
    }

    // Import the public key from JWK for encryption
    // jose infers the right key type/algorithm from JWK fields
    const publicKey = await importJWK(jwk, alg);

    const plaintext = new TextEncoder().encode(JSON.stringify(cardData));

    const jwe = await new CompactEncrypt(plaintext)
        .setProtectedHeader({
            alg,
            enc: 'A256GCM',
            ...(jwk.kid ? { kid: jwk.kid } : {}),
        })
        .encrypt(publicKey);

    return jwe;
}

/**
 * submitJWE Submits the JWE to the Knot API for an active task.
 * The implementation follows the steps outlined in the KnotAPI documentation:
 * https://docs.knotapi.com/api-reference/products/card-switcher/switch-card-jwe
 */
export async function submitJWE(taskId: string, jwe: string): Promise<void> {
    const clientId = process.env.KNOT_CLIENT_ID || '';
    const secret = process.env.KNOT_SECRET || '';
    if (!clientId || !secret) {
        throw new Error('KNOT_CLIENT_ID and KNOT_SECRET must be set in the environment.');
    }

    console.log(`Submitting JWE: ${jwe}`);

    const resp = await fetch(`${KNOT_BASE}/card`, {
        method: 'POST',
        headers: {
            Authorization: basicAuthHeader(clientId, secret),
            'Content-Type': 'application/json',
            Accept: 'application/json',
        },
        body: JSON.stringify({ task_id: taskId, jwe }),
    });

    const text = await resp.text().catch(() => '');
    // Try parse JSON if possible for error_message
    let parsed: Record<string, unknown> | null = null;
    try { parsed = text ? JSON.parse(text) : null; } catch {}

    if (!resp.ok) {
        const msg = parsed?.error_message ?? (text || `HTTP ${resp.status}`);
        throw new Error(String(msg));
    }

    const errorMessage = (parsed?.error_message as string | undefined) ?? undefined;
    if (errorMessage) {
        throw new Error(errorMessage);
    }
}

async function main() {
    /**
     * Hardcoded card data for demonstration purposes.
     * You should use your own card data.
     * See the KnotAPI documentation for more information:
     * https://docs.knotapi.com/api-reference/products/card-switcher/retrieve-jwk#building-the-jwe
     */
    const cardData = {
        user: {
            name: {
                first_name: 'Ada',      // Max length: 255
                last_name: 'Lovelace',  // Max length: 255
            },
            address: {
                street: '100 Main Street', // Max length: 46
                street2: '#100',           // Max length: 46
                city: 'NEW YORK',          // Max length: 32
                region: 'NY',              // Must be an ISO 3166-2 sub-division code
                postal_code: '12345',      // Min length: 5, Max length: 10
                country: 'US',             // Must be an ISO 3166-1 alpha-2 code
            },
            phone_number: '+11234567890', // Must be in E.164 format
        },
        card: {
            number: '4242424242424242',   // Card number
            expiration: '08/2030',        // MM/YYYY or MM/YY format
            cvv: '012',                   // Max length: 4
        },
    };

    const jwk = await getKey();
    const jwe = await encryptData(cardData, jwk);

    await submitJWE('123456', jwe);
}

if (require.main === module) {
    main().catch((err) => {
        console.error(err);
        process.exit(1);
    });
}

Send Data to Secure Vault Provider

This option is often chosen by those integrating with Knot that already have a vault set up with a PCI-compliant vendor such as Very Good Security (VGS) or Basis Theory.
  1. Set up a route to Knot’s Switch Card endpoint from the vault that stores card data at your PCI-compliant vendor.
  2. When you receive the AUTHENTICATED webhook, make a request to the vault.
  3. The vault provider will send the necessary card data to Knot’s Switch Card endpoint in JSON.
  4. Knot receives the card data to the aforementioned endpoint. This endpoint is controlled by Knot’s PCI-compliant vendor which stores the data and proxies it to Knot via an alias.
Card data is never processed or stored outside PCI-compliant vendor environments. After card data is used for a card switch, it is explicitly deleted from the PCI-compliant vendor’s vault within milliseconds. You can also request to enable Mutual Transport Layer Security (mTLS) for the Switch Card endpoint as an additional security measure if desired.

VGS 1-Click Route Setup

Knot partners with VGS (a PCI-compliant vendor) to streamline the process of sending card data in a PCI-compliant manner as part of your integration with Knot. Within your VGS account online, you can setup an outbound route to Knot’s Switch Card endpoint. VGS specifies how to set up an outbound connection to a 3rd party (in this case Knot) here. Doing so will allow you to automatically route card data stored in your VGS vault to Knot. To make this process even easier, in the “Addons” section of your vault in your VGS account online, you will find a set of “route templates.” You can search for and select the “KnotAPI” route template to get started.

Direct Processor Integrations

Unit

With this option, you can allow Knot to retrieve the card data directly from Unit if you use their software as your issuer processor. More on this integration here.

I2C

With this option, you can allow Knot to retrieve the card data directly from Unit if you use their software as your issuer processor. More on this integration here.