Skip to main content

Custom JavaScript actions

Discover how to add custom JavaScript action to Vault

Custom actions are JavaScript functions loaded into Vault (using bundles) that can be invoked when needed using the Invoke Action API. In the function code you can interact with data stored in your Vault instance without exposing it outside of the Vault which helps you meet regulatory and security requirements.

The action JavaScript function receives the Invoke Action API request body as a parameter while the function returned value is returned as the API response body. Both the request and response body are treated in the function as simple JavaScript objects while Vault deserialized and serialized them from and into JSON in the API.

Custom actions JavaScript code is loaded into Vault in a bundle. This page describes how to manage and invoke custom actions. For information on creating a bundle, see Bundles.

For simple use cases Vault includes a built-in action to make an HTTP call.

Manage custom actions

The action code needs to be added with a bundle. See bundles and Add Bundle for more details.

Custom actions are added by the Add Action operation and deleted by the Delete Action operation. To update the action's JavaScript code, you update the bundle using the Update Bundle operation.

Write an action

A handler accepts a context argument and returns an object. The returned object is the response body in JSON format. See Bundle handlers for the full handler specification and details of global functions.

The action runs using a role configured by the Add Action operation, not the role of the user or JWT that invoked the action. This is done deliberatly to allow you to authorize Vault to access data on your behalf without exposing the data to the backend or requestor.

vault object methods

vault.crypto.decrypt

Used to decrypt objects. The function's signature is a compatible subset of the Decrypt operation request. Reason, Custom audit data and enforcement are the same of the caller's.

Signature: (params: DecryptInput) => Promise<DecryptOutputObject[]>;

Where types are defined as:

type DecryptInput = {
collection: string;
requestBody: DecryptionRequest[];
options?: string[];
}

type DecryptionRequest = {
encryptedObject: {
ciphertext: string;
scope?: string;
};
props: string[];
}

type DecryptOutputObject = {
fields: { [key:string]: unknown };
metadata?: {
expiration: string;
scope: string;
tags: string[];
type: "randomized" | "deterministic";
};
}

vault.tokens.tokenize

Used to tokenize objects. The function's signature is a subset of the Tokens operation request. Reason, custom audit data, and enforcement are the same as the caller's.

Signature: (params: TokenizeInputObject) => Promise<TokenizedOutputObject[]>;

Where types are defined as:

type TokenizeInputObject = {
collection: string;
requestBody: TokenizeRequest[];
expirationSecs?: number;
}

type TokenizeRequest = {
type: TokenType;
object: InputObject;
props?: Array<string>;
token_id?: string;
scope?: string;
tags?: Array<string>;
ensure_unique_tenant_tags?: Array<string>;
store_object?: boolean;
fpprops?: Array<string>;
fptemplate?: 'primary_account_number';
};

type InputObject = {
id: string;
} | {
fields: ObjectFields;
} | {
encrypted: EncryptedObjectInput;
} | {
request_index: number;
};

type ObjectFields = Record<string, any>;

type EncryptedObjectInput = {
ciphertext: string;
scope?: string;
};

type TokenizedOutputObject = {
token_id: string;
object_id?: string;
}

vault.tokens.detokenize

Used to detokenize objects. The function's signature is a subset of the Detokenize operation request. Reason, custom audit data, and enforcement are the same as the caller's.

Signature: (params: DetokenizeInput) => Promise<DetokenizedOutputObject[]>;

Where types are defined as:

type DetokenizeInput = {
collection: string;
tokenIds: string[];
objectIds: string[];
tags: string[];
props: string[];
}

type DetokenizedOutputObject = {
token_id: string;
fields: { [key:string]: unknown };
}

vault.deref

Dereference receives a key-value object with each string value being a vault global identifier.

It evaluates the global identifier and returns a new object with the same keys and the evaluated values of the global identifiers.

Signature: <P extends { [key:string]: string }>(params: P): Promise<{ [K in keyof P]: unknown }>;

For example:

const { maskedNumber, customer } = await vault.deref({
maskedNumber: 'pvlt:detoeknize:credit_cards:number.mask:bb5e17ce-38b1-4b3f-9b4b-40801f9672d1:',
customer: 'pvlt:read_object:customers::bb5e17ce-38b1-4b3f-9b4b-40801f9672d1:',
});

console.log(maskedNumber); // '************1234'
console.log(customer); // { id: 'bb5e17ce-38b1-4b3f-9b4b-40801f9672d1', name: 'John Doe' }

payments object methods

vault.google.processPaymentMethodToken

Used to process a Google API signed and encrypted PaymentMethodToken payload. See Payment data cryptography for more details.

Signature: (params: PaymentMethodToken) => Promise<ProcessedPaymentMethodToken>;

Where types are defined as:

type PaymentMethodToken = {
token: string;
privateKey: string;
merchantId: string;
testEnvironment: boolean;
}

type PrasedPaymentMethodToken = {
messageExpiration: string;
messageId: string;
paymentMethod: 'CARD';
paymentMethodDetails: PaymentMethodDetails;
}

type PaymentMethodDetails = {
pan: string;
expirationMonth: number;
expirationYear: number;
authMethod: 'PAN_ONLY' | 'CRYPTOGRAM_3DS';
cryptogram?: string;
eciIndicator?: string;
}

Example action: Decrypt an object and send it to a payment processor

const cardProcessorURL = 'https://example.com';

module.exports.get_cc_card = {
type: 'action',
description: 'Get credit card.',
async handler(context) {
const { body } = context;
// Validate the input
if (
body === null ||
typeof body !== 'object' ||
!('encryptedParams' in body) ||
typeof body.encryptedParams !== 'string' ||
!('reveal' in body) ||
typeof body.reveal !== 'boolean'
) {
return { ok: false, message: 'Invalid input. Expecting a string.' };
}

// Decrypt the params to get the card reference and the auth details.
const { params } = await vault.crypto.decrypt({ collection, requestBody: [
{encryptedObject: { ciphertext: body.encryptedParams }},
]});
const { cc_ref, service_auth } = params[0].fields;

try {
// Send API call to card processor API.
const response = await fetch(`${cardProcessorURL}/api/cards/${cc_ref}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${service_auth}`,
},
});

// Validate the response
if (!response.ok) {
return { ok: false, message: 'Failed to retrieve card' };
}

// Parse the response as JSON
const { cc_number, expiry_date, cvv } = await response.json();

// Return a success response
return {
ok: true,
card: body.reveal
? {
number: cc_number,
exp: expiry_date,
cvv,
}
: {
number: `•••• •••• •••• ${cc_number.slice(-4)}`,
exp: '••/••',
cvx2: '•••',
},
};
} catch (error) {
// Handle errors
console.log(error);
return { ok: false, message: 'Failed to retrieve card' };
}
},
};

Example action: Parse a Google Pay PaymentMethodToken payload and tokenize in Vault

exports.tokenizeGooglePay = {
type: "action",
description: "Tokenize a Google Pay payment method token",
async handler({ body, vault, payments }) {
const secretsCollection = "secrets";
const secretsProperty = "value";
const paymentMethodsCollection = "payment_methods";

// Validate the body input.
if (
body === null ||
typeof body !== "object" ||
!("gpayToken" in body) ||
typeof body.gpayToken !== "string" ||
!("privateKeyObjectId" in body) ||
typeof body.privateKeyObjectId !== "string"
) {
return { ok: false, message: "Invalid input" };
}

// Get the Google Pay private key from Vault.
const { gpayPrivateKey } = await vault.deref({
gpayPrivateKey: `pvlt:read_object:${secretsCollection}:${secretsProperty}:${body.privateKeyObjectId}:`,
});

// Process the Google Pay payment method token.
const { paymentMethodDetails } = await payments.google.processPaymentMethodToken({
token: body.gpayToken,
privateKey: gpayPrivateKey,
merchantId: "test",
testEnvironment: true,
});

// Tokenize the raw card details.
const tokens = await vault.tokens.tokenize({
collection: paymentMethodsCollection,
requestBody: [{
type: "pci",
object: {
fields: {
"number": paymentMethodDetails.pan,
"expiration": `${paymentMethodDetails.expirationMonth}/${paymentMethodDetails.expirationYear}`,
},
},
}],
})


// Return a success response
return { ok: true, token_id: tokens[0].token_id };
}
};