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:
| Field | Content |
|---|---|
piiSecuredInfo.securedPayload | The complete IVMS101 JSON, including the root-level travelRuleMetadata, encrypted for the counterparty VASP |
transmittedContent | Field 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.
| Field | Requirement |
|---|---|
hashAlgorithm | Use sha512 |
nullHashExample | SHA-512 of "," + validationSalt; the counterparty can identify null or empty hashes without exposing the salt outside the encrypted payload |
validationSalt | Add it to the root-level travelRuleMetadata inside the plaintext IVMS payload before encryption. Omit it from the unencrypted transmittedContent field |
manifest item
| Field | Requirement |
|---|---|
fieldPath | Dot-separated path from the IVMS101 root; array positions are numeric path segments |
fieldType | object, array, string, integer, boolean, or null |
isNull | true when the source is JSON null or an empty string; otherwise false |
index | Array index on the manifest item that represents the direct array element |
count | Number of elements when the item is an array |
contentHash | Salted 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:
- Finalize the
ivms101data that will be encrypted. - Check the root-level
travelRuleMetadata.validationSalt. If it already exists, use the same value. If it does not exist, generate a cryptographically secure randomvalidationSalt; UUID v4 is the recommended format, for example550e8400-e29b-41d4-a716-446655440000. - Calculate
nullHashExamplewith the selectedvalidationSalt, 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": {}
}
- Walk every non-root JSON node recursively, excluding the root-level
travelRuleMetadataobject from the manifest. - Add one manifest item for each object, array, and leaf node under
ivms101. - For each leaf value, convert the value to its JSON scalar string representation and calculate:
contentHash = lowercase_hex(SHA-512(value + "," + validationSalt))
- For JSON
nulland an empty string, hash an empty value:
nullHashExample = lowercase_hex(SHA-512("," + validationSalt))
- When constructing the API request or response, build the unencrypted sibling
transmittedContentfield withhashAlgorithm,nullHashExample, andmanifest. Do not includevalidationSaltin this API request/response field; it remains available only inside the encrypted plaintexttravelRuleMetadata. - Encrypt the same plaintext object—including
travelRuleMetadataandivms101—and put the encrypted result inpiiSecuredInfo.securedPayload. - Add the generated transmitted content to
transmittedContentat 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:
- Add
travelRuleMetadatato the plaintext IVMS101 JSON and generatetransmittedContentfrom it. - Encrypt that same plaintext, including
travelRuleMetadata, withEncryptionUtils.encrypt(...). - Put the library's encrypted
securedPayloadinpiiSecuredInfowithout Base64-encoding it a second time. - Put the generated transmitted content in the sibling
transmittedContentfield.
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
validationSaltonly inside the encrypted plaintexttravelRuleMetadata; do not expose it in the siblingtransmittedContentfield. - Never put plaintext PII in
fieldPath, metadata, logs, or exception messages. - Preserve array order and emit
indexandcountcorrectly. - Treat JSON
nulland an empty string asisNull: true. - Add
transmittedContentbesidepiiSecuredInfoin both the one-step request and the receiver PII response. - Keep the complete server-side copy according to your security, retention, and audit policies.