Keys
Downloads the encryption key set for the POS device. Keys are wrapped in TR31 format with AES-256 encryption. The device must install these keys before it can process payment transactions. This is the sixth step in the POS activation flow.
Key hierarchy on device
TMK → KEK → MSK
└→ TPK / TDK / MAC
↓
PIN / data / MAC crypto at runtime
| Layer | Keys delivered | Wrapped under (export) |
|---|---|---|
| Terminal | TMK | ZMK (host-side; device uses pre-provisioned ZMK to unwrap TMK) |
| Key encryption | KEK | TMK |
| Data session | MSK | KEK |
| Transaction | TPK, TDK, MAC | KEK |
Purpose
- Download a set of terminal encryption keys (TMK, KEK, MSK, TPK, TDK, MAC)
- Keys follow the hierarchy: TMK → KEK → MSK (data) + transaction keys (TPK/TDK/MAC wrapped under KEK)
- Key Check Values (KCVs) are provided for verification after installation
- The key set has an issuance date and expiration date
Authentication
POS Attestation Auth — requires the attestation token received from POST /pos/attest.
| Header | Required | Format | Description |
|---|---|---|---|
Authorization | Yes | Attestation {token} | The attestation token from the attest step. Uses the Attestation scheme (not Bearer). The token starts with att_, is cached in Redis, and is valid for 5 minutes from issuance. Example: Attestation att_f83kLmP9s2ab |
X-Device-Id | Yes | POS-{8_HEX_CHARS} | The same device ID used during attestation. The server verifies that this device ID matches the one bound to the attestation token. Example: POS-8F3A2C91 |
See the Headers Reference for complete details.
Request
No request body is required.
Response
200 OK
{
"key_set": {
"key_set_id": "ks-2025-01",
"issued_at": "2025-12-13T14:25:30Z",
"expires_at": "2026-12-13T14:25:30Z"
},
"transport": {
"format": "TR31",
"encryption": "AES-256",
"wrapped_under": "kek"
},
"keys": {
"tmk": {
"key_id": "tmk-2025-01",
"key_block": "BASE64_TR31_TMK",
"usage": "key-encryption"
},
"kek": {
"key_id": "kek-2025-01",
"key_block": "BASE64_TR31_KEK",
"usage": "key-encryption"
},
"msk": {
"key_id": "msk-2025-01",
"key_block": "BASE64_TR31_MSK",
"usage": "data-encryption"
},
"tpk": {
"key_id": "tpk-2025-01",
"key_block": "BASE64_TR31_TPK",
"usage": "pin-encryption"
},
"tdk": {
"key_id": "tdk-2025-01",
"key_block": "BASE64_TR31_TDK",
"usage": "data-encryption"
},
"mac": {
"key_id": "mac-2025-01",
"key_block": "BASE64_TR31_MAC",
"usage": "message-authentication"
}
},
"verification": {
"kcv": {
"tmk": "A1B2C3",
"kek": "B2C3D4",
"msk": "C3D4E5",
"tpk": "D4E5F6",
"tdk": "112233",
"mac": "778899"
}
}
}
Key Types
| Key | Full Name | Purpose |
|---|---|---|
tmk | Terminal Master Key | Top-level terminal key; wraps KEK for transport |
kek | Key Encryption Key | Wraps MSK and protects key delivery to the terminal |
msk | Master Session Key | Encrypts sensitive API payloads (config, activate, charge) on the device; API decrypts server-side |
tpk | Terminal PIN Key | Encrypts PIN blocks during transactions |
tdk | Terminal Data Key | Legacy/auxiliary data encryption key |
mac | MAC Key | Generates message authentication codes for HMAC signing |
Terminal install order
- Unwrap TMK (using pre-provisioned ZMK or host-injected wrapping key)
- Unwrap KEK with TMK, then MSK with KEK
- Unwrap TPK, TDK, MAC with KEK
- Use MSK for field-level data encrypt (send ciphertext to API), TPK for PIN, MAC for message authentication
MSK-protected API payloads
After keys are installed, use MSK (AES-256-CBC, hex IV + hex ciphertext) for these endpoints:
| Endpoint | Direction | Notes |
|---|---|---|
POST /pos/config | Response only | Decrypt envelope → core config JSON |
POST /pos/terminal-parameters | Response only | MSK envelope; inner gzip JSON (TR31-DATA or TR31-DATA-CHUNKED) |
POST /pos/activate | Request + response | Encrypt readiness JSON; decrypt activation result |
POST /pos/charge | Request + response | HMAC signs the envelope body |
POST /pos/charge/:id/refund | Request + response | Same envelope format |
POST /pos/charge/:id/cancel | Response only | Empty HMAC body |
Envelope format:
{
"format": "TR31-DATA",
"ciphertext": "A1B2C3...",
"initialization_vector": "00010203...",
"mode": "CBC"
}
Full walkthrough: End-to-End Example.
Transport Fields
| Field | Description |
|---|---|
format | Key block format — always TR31 |
encryption | Encryption algorithm — always AES-256 |
wrapped_under | Wrapping key for MSK and transaction keys — kek |
Verification
The kcv (Key Check Values) are used to verify keys were installed correctly on the device. After decrypting each key, encrypt a block of zeros and compare the first 3 bytes with the KCV.
401 Unauthorized
{
"error": {
"code": "4001",
"message": "Invalid or expired attestation token",
"trace_id": "abc123..."
}
}
Code Examples
- cURL
- Python
- Ruby
- Go
- C#
- PHP
- Java
curl -X POST https://api.viopay.io/pos/keys \
-H "Authorization: Attestation att_f83kLmP9s2ab" \
-H "X-Device-Id: POS-8F3A2C91" \
-H "Content-Type: application/json"
import requests
url = "https://api.viopay.io/pos/keys"
headers = {
"Authorization": "Attestation att_f83kLmP9s2ab",
"X-Device-Id": "POS-8F3A2C91",
"Content-Type": "application/json",
}
response = requests.post(url, headers=headers)
data = response.json()
key_set = data["key_set"]
print(f"Key Set ID: {key_set['key_set_id']}")
print(f"Expires: {key_set['expires_at']}")
for key_name in ("tmk", "kek", "msk", "tpk", "tdk", "mac"):
key_info = data["keys"][key_name]
print(f"{key_name.upper()}: {key_info['key_id']} ({key_info['usage']})")
require 'net/http'
require 'json'
require 'uri'
uri = URI("https://api.viopay.io/pos/keys")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Attestation att_f83kLmP9s2ab"
request["X-Device-Id"] = "POS-8F3A2C91"
request["Content-Type"] = "application/json"
response = http.request(request)
data = JSON.parse(response.body)
puts "Key Set: #{data['key_set']['key_set_id']}"
%w[tmk kek msk tpk tdk mac].each do |name|
info = data["keys"][name]
puts "#{name.upcase}: #{info['key_id']} (#{info['usage']})"
end
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.viopay.io/pos/keys", nil)
req.Header.Set("Authorization", "Attestation att_f83kLmP9s2ab")
req.Header.Set("X-Device-Id", "POS-8F3A2C91")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var result map[string]interface{}
json.Unmarshal(body, &result)
formatted, _ := json.MarshalIndent(result, "", " ")
fmt.Println(string(formatted))
}
using System.Net.Http;
using System.Text.Json;
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.viopay.io/pos/keys");
request.Headers.Add("Authorization", "Attestation att_f83kLmP9s2ab");
request.Headers.Add("X-Device-Id", "POS-8F3A2C91");
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize<JsonElement>(body);
Console.WriteLine($"Key Set: {data.GetProperty("key_set").GetProperty("key_set_id")}");
foreach (var keyName in new[] { "tmk", "kek", "msk", "tpk", "tdk", "mac" })
{
var key = data.GetProperty("keys").GetProperty(keyName);
Console.WriteLine($"{keyName.ToUpper()}: {key.GetProperty("key_id")} ({key.GetProperty("usage")})");
}
<?php
$ch = curl_init("https://api.viopay.io/pos/keys");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Attestation att_f83kLmP9s2ab",
"X-Device-Id: POS-8F3A2C91",
"Content-Type: application/json",
],
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
echo "Key Set: " . $data["key_set"]["key_set_id"] . "\n";
foreach ($data["keys"] as $name => $info) {
echo strtoupper($name) . ": " . $info["key_id"] . " (" . $info["usage"] . ")\n";
}
import java.net.http.*;
import java.net.URI;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.viopay.io/pos/keys"))
.POST(HttpRequest.BodyPublishers.noBody())
.header("Authorization", "Attestation att_f83kLmP9s2ab")
.header("X-Device-Id", "POS-8F3A2C91")
.header("Content-Type", "application/json")
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
What's Next?
After installing the keys, download the device configuration:
Next step → Get Config