Encrypted PIN Transfer
This guide describes how to implement secure PIN transfer for your end-users.
Applicable only for the request that accepts the
Signing-Methodheader.
Encrypted PIN Transfer
This feature allows you to transfer your end-users signing methods (PIN, Emergency Code, Biometrics) in a secure and encrypted way so that it is not visible on your system in any way. Your system won't be able to view or read the end-user signing method's value.
This feature also ensures that the original end-user request body is not tampered with and helps avoid duplicate transactions.
Flowchart

Encrypted PIN Transfer Flowchart
View the TypeScript code example for Encrypted PIN Transfer.
1. Generate random AES 256-bit key
First, generate a random AES 256-bit key.
2. Generate a random iv vector (12 bytes)
Next, generate a random iv vector which should be 12 bytes.
3. Encrypt the AES 256-bit key
Next, encrypt the randomly generated AES 256-bit key with the AWS RSA-2048 public key that we expose in our publicly available endpoint:
Endpoint:
GET https://api-wallet.venly.io/api/securityResponse Body:
{
"success": true,
"result": {
"encryptionKeys": [
{
"id": "837943da-82aa-49c5-bab7-503010985ae9",
"keyspec": "RSA_2048",
"publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnDcmfHx3yZxwgZW7r+iUlUdI4a6Ot5WP8P4gzc/emfafLgOGQCoZue6n99BD6iRynnwqHUKd3fS50UX5vmZmXOJGHXlXCRsv5Z1/P32s/q5bLnTGpmzZOQXeiaOMbXgcOWcS0XhVGfioB3VsfURFBOU7okmMY0iAPPA7cdBK5fLTb0CYulMdIKfgWzeBBbqT0J6mRdUfbvXqA2gOLmaZRXKerdJUBbnNc3oOxgsk2noMlyOUId6SZsJYxQZRyjErBSjM+qNitEYLKO8tlxiPtLFWOGAa782nSMNJaLcdGWdz5TeADyvlJbbsvItA1lDWTbnJQyeN0bMDzL5XYcPTkQIDAQAB",
"encryptionAlgorithm": "RSAES_OAEP_SHA_256",
}
]
}
}4. Prepare the Request Body
Generate a SHA256 hash based on the request body.
Example Request Body:
{
"transactionRequest": {
"type" : "TRANSFER",
"secretType" : "MATIC",
"walletId": "56137f2d-b4e9-42de-943b-cee828eb222a",
"to" : "0x8D6331D6C5ba3306961F0b4Ea13ff13aa43560b9",
"value": 1
}
}Example SHA256 Hash:
9ad9900886a75c33c327168ed6b0f8892eeffab26369e74082bc1208876537625. Encrypt Signing-Method body with the Raw AES key
Next, encrypt the Signing-Method body with your raw AES key. The structure of the Signing-Method body is as follows:
| Param | Description | Required? |
|---|---|---|
id | The unique ID of the signing method. | ✅ |
value | The value of the signing method. | ✅ |
physicalDeviceId | The physicalDeviceId which is the unique ID of the user's device. (Applicable forBIOMETRIC only) | ❌ |
idempotencyKey | A unique UUID for every request. | ✅ |
signature.type | This will be sha256 | ✅ |
signature.value | SHA256 encoded hash of the JSON request body. - from Step#4 | ✅ |
{
id: string,
value: string,
idempotencyKey: string,
signature: {
type: 'sha256',
value: string
}
}Example Signing-Method body with PIN:
PIN:{
"id":"2bed9491-cdf2-454f-bdfa-716c94cbd922",
"value":"123456",
"idempotencyKey": "9ee45da6-193b-4b07-bdd5-92f5602bcf62",
"signature":{
"type":"sha256",
"value":"9ad9900886a75c33c327168ed6b0f8892eeffab26369e74082bc120887653762"
}
}
Example Signing-Method body with Emergency Code:
Emergency Code:{
"id":"a2ffea97-c199-4685-928d-b34b6f6b2133",
"value":"UvHSS5SCYek3U2W9h4gEUa9VZ",
"idempotencyKey": "d06e7754-4c27-4418-8f85-0eb4337a717b",
"signature":{
"type":"sha256",
"value":"54f6e79117c0dc43e1bba7e57303c81da3588703e068f723bb1a7a410c6e0230"
}
}
Example Signing-Method body with Biometric:
Biometric:{
"id":"64c10e41-114a-4ce7-9d02-7df48ba360e6",
"value":"2f08ae38-b3b9-4857-b46e-3b20e1a936f8",
"physicalDeviceId": "312b26a0-e6de-4d33-a011-c17cd5fb5e7f",
"idempotencyKey": "659386e2-4c5a-4c1f-80c7-9e30ff65265e",
"signature":{
"type":"sha256",
"value":"cd0d7f18ca78e1466dd593d88a21deabb80ee42069159e28ddfaa2ac0eb9de5c"
}
}
6. Prepare Encrypted-Signing-Method Header
Encrypted-Signing-Method Header6.1 Preparing JSON Body for Encrypted-Signing-Method
Encrypted-Signing-MethodNext, prepare the following JSON body.
| Param | Description |
|---|---|
encryption.type | This will be AES/GCM/NoPadding. |
encryption.key.encryptedValue | The AES key that is encrypted with RSA-2048 public key (GET /api/security) - fromStep#3 |
encryption.key.encryptionKeyId | The value corresponds to the result.encryptionKeys.id param in the response body of the GET /api/security endpoint - fromStep#3 |
encryption.iv | Your randomly generated iv vector (12 bytes). - fromStep#2 |
value | The body of the Signing-Method that is encrypted with the raw AES key. - fromStep#5 |
{
"encryption":{
"type":"AES/GCM/NoPadding",
"key":{
"encryptedValue": "base64string",
"encryptionKeyId": "<uuid>"
},
"iv":"base64string"
},
"value":"base64string"
}6.2 BASE64 Encode the Body
Next, you have to base64 encode the JSON body which is to be passed in the Encrypted-Signing-Method header.
7. Send the correct header
There are two ways to supply the signing method in the header:
7.1 For Encrypted Transfer (Encrypted-Signing-Method)
Encrypted-Signing-Method)You need to send the JSON base64 encoded body in the Encrypted-Signing-Method header from the previous step. The Encrypted-Signing-Method indicates that the signing method and the request body are encrypted.
7.2 For non-encrypted Transfer (Signing-Method)
Signing-Method)The Signing-Method header can be passed as usual for non-encrypted transfers.
Parameter | Param Type | Value | Description | Example Value |
|---|---|---|---|---|
| Header |
|
|
|
Encrypted Request Example:
Executing a Transaction
Headers
Encrypted-Signing-Method: <<BASE64 ENCODED JSON BODY from Step#6>>
Body
{
"transactionRequest": {
"type" : "TRANSFER",
"secretType" : "MATIC",
"walletId": "56137f2d-b4e9-42de-943b-cee828eb222a",
"to" : "0x8D6331D6C5ba3306961F0b4Ea13ff13aa43560b9",
"value": 1
}
}
Encrypted Request Body
This flow applies when you are creating a user, creating a signing method, or updating a signing method via the Wallet API, and you want the signing method value to be transmitted encrypted so that it is never visible on your system.
📘 Note: This uses the same AES/GCM encryption mechanism but the encrypted payload is passed in the request body instead of a header. The SHA256 signature check is not required for this flow.
The steps for generating the AES key, iv vector, and encrypting with the RSA-2048 public key are the same as Steps 1–3 in this guide.
Encrypted Payload Structure
For all three endpoints, the encrypted signing method follows this structure:
| Param | Description |
|---|---|
encryption.type | This will be AES/GCM/NoPadding. |
encryption.key.encryptedValue | The AES key encrypted with the RSA-2048 public key from GET /api/security. |
encryption.key.encryptionKeyId | The result.encryptionKeys.id from the GET /api/security response. |
encryption.iv | Your randomly generated iv vector (12 bytes). |
value | The AES-encrypted, base64-encoded request payload (see per-endpoint details below). |
{
"encryption": {
"type": "AES/GCM/NoPadding",
"key": {
"encryptedValue": "base64string",
"encryptionKeyId": "<uuid>"
},
"iv": "base64string"
},
"value": "base64(aesEncrypt(<requestPayload>))"
}Endpoint: Create User
Reference: POST /api/users
When creating a user with a signing method, pass the encrypted request body.
Plain (non-encrypted) request body:
{
"reference": "Test User Venly",
"signingMethod": {
"type": "PIN",
"value": "123456"
}
}Encrypted request body:
The value field contains the AES-encrypted, base64-encoded creation payload:
// Creation payload (before encryption):
{
"reference": "Test User Venly",
"signingMethod": {
"type": "PIN",
"value": "123456"
}
}// Full request body with encrypted request body:
{
"encryption": {
"type": "AES/GCM/NoPadding",
"key": {
"encryptedValue": "base64string",
"encryptionKeyId": "<uuid>"
},
"iv": "base64string"
},
"value": "base64(aesEncrypt(creationPayload))"
}Endpoint: Create Signing Method
Reference: POST /api/users/{userId}/signing-methods
\{userId\}: Theidof the user whose signing method is to be created.
When creating an additional signing method for an existing user, pass the encrypted payload as the request body.
Plain (non-encrypted) request body:
{
"type": "PIN",
"value": "123456"
}Encrypted request body:
The value field contains the AES-encrypted, base64-encoded creation payload:
// Creation payload (before encryption):
{
"type": "PIN",
"value": "123456"
}// Full encrypted request body:
{
"encryption": {
"type": "AES/GCM/NoPadding",
"key": {
"encryptedValue": "base64string",
"encryptionKeyId": "<uuid>"
},
"iv": "base64string"
},
"value": "base64(aesEncrypt(creationPayload))"
}Endpoint: Update Signing Method
Reference: PUT /api/users/{userId}/signing-methods/{signingMethodId}
\{userId\}: Theidof the user whose signing method is to be updated.
When updating an existing signing method, pass the encrypted payload directly as the request body.
Plain (non-encrypted) request body:
{
"value": "654321"
}Encrypted request body:
The value field contains the AES-encrypted, base64-encoded update payload:
// Update payload (before encryption):
{
"value": "654321"
}// Full encrypted request body:
{
"encryption": {
"type": "AES/GCM/NoPadding",
"key": {
"encryptedValue": "base64string",
"encryptionKeyId": "<uuid>"
},
"iv": "base64string"
},
"value": "base64(aesEncrypt(updatePayload))"
}Supported Signing Method Types
The type field in the creation payload supports all signing method types:
| Type | Description |
|---|---|
PIN | A numeric PIN set by the user. |
EMERGENCY_CODE | An alphanumeric emergency recovery code. |
BIOMETRIC | A biometric identifier tied to a physical device. |
Updated 9 days ago