Skip to main content
Search

Transmitted Content

transmittedContent is a server-side manifest generated from the plaintext IVMS101 payload before the payload is encrypted. Before generating the manifest, add a root-level travelRuleMetadata object to the plaintext IVMS payload. The encrypted payload therefore carries the salt and hash metadata required to validate the transmitted content, while the unencrypted API field does not expose the salt.

It is separate from piiSecuredInfo.securedPayload:

FieldContent
piiSecuredInfo.securedPayloadThe complete IVMS101 JSON, including the root-level travelRuleMetadata, encrypted for the counterparty VASP
transmittedContentField paths, data types, array metadata, null indicators, and salted SHA-512 hashes of IVMS leaf values

Generate both values from the same finalized plaintext IVMS101 object on your backend. The object encrypted into securedPayload must be the same object used to generate the manifest and must include travelRuleMetadata. Do not generate them in a browser or mobile application.

Where to add it

Initiator VASP

Add transmittedContent as a top-level field in the POST /api/verify/v2/one_step request body. It is a sibling of piiSecuredInfo, not a child of it.

{
"requestId": "YOUR-VASP-UUID",
"verifyDirection": 2,
"piiSecuredInfo": {
"piiSpecVersion": "ivms101-2020",
"secretAlgorithm": "ed25519_curve25519",
"securedPayload": "BASE64_ENCRYPTED_IVMS101",
...
},
...
"transmittedContent": {
"travelRuleMetadata": {
"hashAlgorithm": "sha512",
"nullHashExample": "HASH_OF_EMPTY_VALUE_WITH_THE_PRIVATE_SALT"
},
"manifest": []
}
}

Receiver VASP

When your PII verification callback handler returns the receiver's encrypted IVMS101 payload, generate a new receiver-side travelRuleMetadata, add it to the response plaintext, generate a new manifest from that plaintext, and add the resulting transmitted content to data.transmittedContent. It is a sibling of data.piiSecuredInfo.

{
"data": {
...
"transmittedContent": {
"travelRuleMetadata": {
"hashAlgorithm": "sha512",
"nullHashExample": "HASH_OF_EMPTY_VALUE_WITH_THE_PRIVATE_SALT"
},
"manifest": []
}
},
"verifyMessage": "Verification Success",
"verifyStatus": 100000
}

The Initiator VASP may receive this receiver-side value in the synchronous /one_step response or in the PII verification result callback, depending on the verification flow.

Payload structure

{
"travelRuleMetadata": {
"hashAlgorithm": "sha512",
"nullHashExample": "..."
},
"manifest": [
{
"fieldPath": "ivms101.Originator.originatorPersons.0.naturalPerson.name.nameIdentifier.0.primaryIdentifier",
"fieldType": "string",
"isNull": false,
"contentHash": "..."
},
...
]
}

travelRuleMetadata

The complete metadata is added at the root of the plaintext IVMS payload before encryption:

{
"travelRuleMetadata": {
"validationSalt": "550e8400-e29b-41d4-a716-446655440000",
"hashAlgorithm": "sha512",
"nullHashExample": "..."
},
"ivms101": {
"...": "..."
}
}

The sibling API transmittedContent.travelRuleMetadata contains the same hashAlgorithm and nullHashExample, but omits validationSalt. The counterparty obtains the salt only after decrypting piiSecuredInfo.securedPayload.

FieldRequirement
hashAlgorithmUse sha512
nullHashExampleSHA-512 of "," + validationSalt; the counterparty can identify null or empty hashes without exposing the salt outside the encrypted payload
validationSaltAdd it to the root-level travelRuleMetadata inside the plaintext IVMS payload before encryption. Omit it from the unencrypted transmittedContent field

manifest item

FieldRequirement
fieldPathDot-separated path from the IVMS101 root; array positions are numeric path segments
fieldTypeobject, array, string, integer, boolean, or null
isNulltrue when the source is JSON null or an empty string; otherwise false
indexArray index on the manifest item that represents the direct array element
countNumber of elements when the item is an array
contentHashSalted SHA-512 hash for a leaf value; omit it for object and array container items

Generation algorithm

Use the same algorithm for the Initiator and Receiver VASP:

  1. Finalize the ivms101 data that will be encrypted.
  2. Check the root-level travelRuleMetadata.validationSalt. If it already exists, use the same value. If it does not exist, generate a cryptographically secure random validationSalt; UUID v4 is the recommended format, for example 550e8400-e29b-41d4-a716-446655440000.
  3. Calculate nullHashExample with the selected validationSalt, then add or update the following object at the root of the plaintext payload:
{
"travelRuleMetadata": {
"validationSalt": "550e8400-e29b-41d4-a716-446655440000",
"hashAlgorithm": "sha512",
"nullHashExample": "LOWERCASE_SHA512_HASH"
},
"ivms101": {}
}
  1. Walk every non-root JSON node recursively, excluding the root-level travelRuleMetadata object from the manifest.
  2. Add one manifest item for each object, array, and leaf node under ivms101.
  3. For each leaf value, convert the value to its JSON scalar string representation and calculate:
contentHash = lowercase_hex(SHA-512(value + "," + validationSalt))
  1. For JSON null and an empty string, hash an empty value:
nullHashExample = lowercase_hex(SHA-512("," + validationSalt))
  1. When constructing the API request or response, build the unencrypted sibling transmittedContent field with hashAlgorithm, nullHashExample, and manifest. Do not include validationSalt in this API request/response field; it remains available only inside the encrypted plaintext travelRuleMetadata.
  2. Encrypt the same plaintext object—including travelRuleMetadata and ivms101—and put the encrypted result in piiSecuredInfo.securedPayload.
  3. Add the generated transmitted content to transmittedContent at the controller request/response boundary described above.

null and an empty string intentionally produce the same hash and both set isNull to true.

Reference JavaScript implementation

The following server-side Node.js implementation follows the algorithm above. It adds travelRuleMetadata to the plaintext object, generates the manifest, and returns both the plaintext to encrypt and the sibling transmittedContent payload.

import crypto from "node:crypto";

const HASH_ALGORITHM = "sha512";

function sha512(content, validationSalt) {
return crypto
.createHash("sha512")
.update(`${content ?? ""},${validationSalt}`, "utf8")
.digest("hex");
}

function fieldType(value) {
if (value === null) return "null";
if (Array.isArray(value)) return "array";
if (typeof value === "object") return "object";
if (typeof value === "boolean") return "boolean";
if (typeof value === "number" && Number.isInteger(value)) return "integer";
return "string";
}

function walk(value, fieldPath, manifest, arrayIndex, validationSalt) {
if (fieldPath) {
const item = {
fieldPath,
fieldType: fieldType(value),
isNull: value === null || value === ""
};

if (arrayIndex !== undefined) item.index = arrayIndex;
if (Array.isArray(value)) item.count = value.length;

const isContainer = value !== null && typeof value === "object";
if (!isContainer) item.contentHash = sha512(value, validationSalt);

manifest.push(item);
}

if (Array.isArray(value)) {
value.forEach((child, index) => {
walk(child, `${fieldPath}.${index}`, manifest, index, validationSalt);
});
return;
}

if (value !== null && typeof value === "object") {
Object.entries(value).forEach(([key, child]) => {
if (!fieldPath && key === "travelRuleMetadata") return;
const childPath = fieldPath ? `${fieldPath}.${key}` : key;
walk(child, childPath, manifest, undefined, validationSalt);
});
}
}

export function buildTransmittedContent(plaintextIvms101) {
const source = typeof plaintextIvms101 === "string"
? JSON.parse(plaintextIvms101)
: plaintextIvms101;

const existingMetadata = source.travelRuleMetadata ?? {};
const validationSalt = existingMetadata.validationSalt
|| crypto.randomUUID();
const nullHashExample = sha512(null, validationSalt);
source.travelRuleMetadata = {
...existingMetadata,
validationSalt,
hashAlgorithm: HASH_ALGORITHM,
nullHashExample
};

const manifest = [];
walk(source, "", manifest, undefined, validationSalt);

return {
plaintextIvms101: source,
transmittedContent: {
travelRuleMetadata: {
hashAlgorithm: HASH_ALGORITHM,
nullHashExample
},
manifest
},
privateManifest: {
travelRuleMetadata: source.travelRuleMetadata,
manifest
}
};
}

Call it before encryption:

const prepared = buildTransmittedContent(plaintextIvms101);
await savePrivateManifest(requestId, prepared.privateManifest);

const piiSecuredInfo = await encryptIvms101(
JSON.stringify(prepared.plaintextIvms101)
);

const requestBody = {
...oneStepFields,
piiSecuredInfo,
transmittedContent: prepared.transmittedContent
};

Reproducible example

The following small example uses a fixed salt only to make the output reproducible. Generate a new random salt in production.

Demo Salt:

0123456789abcdef0123456789abcdef

The plaintext encrypted for this example includes:

{
"travelRuleMetadata": {
"validationSalt": "0123456789abcdef0123456789abcdef",
"hashAlgorithm": "sha512",
"nullHashExample": "b075467248c9506346c23726edac96eaef2b83a04e6d5520975c2d97ca7cec43a5aa4f2109652ef8da008e75b249445dda95ce32081905deac5c5b42f858a53a"
},
"ivms101": {
"Originator": {
"originatorPersons": [
{
"naturalPerson": {
"name": "Alice Example",
"nationalIdentifierType": "MISC",
"customerIdentification": ""
}
}
]
}
}
}

Transmitted content output

{
"travelRuleMetadata": {
"hashAlgorithm": "sha512",
"nullHashExample": "b075467248c9506346c23726edac96eaef2b83a04e6d5520975c2d97ca7cec43a5aa4f2109652ef8da008e75b249445dda95ce32081905deac5c5b42f858a53a"
},
"manifest": [
{
"fieldPath": "ivms101",
"fieldType": "object",
"isNull": false
},
{
"fieldPath": "ivms101.Originator",
"fieldType": "object",
"isNull": false
},
{
"fieldPath": "ivms101.Originator.originatorPersons",
"fieldType": "array",
"isNull": false,
"count": 1
},
{
"fieldPath": "ivms101.Originator.originatorPersons.0",
"fieldType": "object",
"isNull": false,
"index": 0
},
{
"fieldPath": "ivms101.Originator.originatorPersons.0.naturalPerson",
"fieldType": "object",
"isNull": false
},
{
"fieldPath": "ivms101.Originator.originatorPersons.0.naturalPerson.name",
"fieldType": "string",
"isNull": false,
"contentHash": "ae102619bf1ff01da9faf6105180398db4ef1dd62ddc06f53332c9884691216a4912e64bb538e59a491e7e785b633b57560ebb89f6bb4e92eead7dc05b06288e"
},
{
"fieldPath": "ivms101.Originator.originatorPersons.0.naturalPerson.nationalIdentifierType",
"fieldType": "string",
"isNull": false,
"contentHash": "7d0f144777ffef7d751a0dbf809405836b10a8c25e27a66e20553854b5e1ba34fa96ad5f06043f74aa479cc647edca08cf993b5c8f2cf71be13d898b4a1146ae"
},
{
"fieldPath": "ivms101.Originator.originatorPersons.0.naturalPerson.customerIdentification",
"fieldType": "string",
"isNull": true,
"contentHash": "b075467248c9506346c23726edac96eaef2b83a04e6d5520975c2d97ca7cec43a5aa4f2109652ef8da008e75b249445dda95ce32081905deac5c5b42f858a53a"
}
]
}

Java encryption library

Java customers can use the Global Travel Rule compliance-tools library to generate keys and encrypt or decrypt piiSecuredInfo.securedPayload.

The manifest generation and PII encryption are separate operations:

  1. Add travelRuleMetadata to the plaintext IVMS101 JSON and generate transmittedContent from it.
  2. Encrypt that same plaintext, including travelRuleMetadata, with EncryptionUtils.encrypt(...).
  3. Put the library's encrypted securedPayload in piiSecuredInfo without Base64-encoding it a second time.
  4. Put the generated transmitted content in the sibling transmittedContent field.

For Maven or Gradle coordinates and a complete EncryptionUtils example, refer to the library README. Also see Cipher Curve25519 Guideline and IVMS 101 Guidelines.

Validation checklist

  • Generate the manifest only on a trusted backend.
  • Use the same finalized plaintext IVMS101 object for hashing and encryption.
  • Use SHA-512 and a cryptographically secure random salt.
  • Put validationSalt only inside the encrypted plaintext travelRuleMetadata; do not expose it in the sibling transmittedContent field.
  • Never put plaintext PII in fieldPath, metadata, logs, or exception messages.
  • Preserve array order and emit index and count correctly.
  • Treat JSON null and an empty string as isNull: true.
  • Add transmittedContent beside piiSecuredInfo in both the one-step request and the receiver PII response.
  • Keep the complete server-side copy according to your security, retention, and audit policies.