Attest
After the device is claimed, the POS proves its identity by providing HMAC signature headers. On success, it receives a short-lived attestation token used to access the keys, config, and activation endpoints. This is the fifth step in the POS activation flow.
Purpose
- Verify the device's identity using a cryptographic signature (X-Key-Id + X-Signature)
- Confirm the device has a valid identity key registered in the system
- Issue a short-lived attestation token (valid for 5 minutes) cached in Redis
- The attestation token is required for the next three steps (keys, config, activate)
Authentication
POS Activation Auth + Signature — device identification headers plus HMAC signature headers for cryptographic identity proof.
Device Identification Headers
| Header | Required | Format | Description |
|---|---|---|---|
X-Device-Id | Yes | POS-{8_HEX_CHARS} | Unique identifier for the physical POS device. Must match a pre-provisioned device. Example: POS-8F3A2C91 |
User-Agent | Yes | Viopay-POS/{version} ({OS}; {model}) | POS app version string. Must start with Viopay-POS/. Example: Viopay-POS/1.4.2 (Android 12; PAX-A920) |
X-Client-Type | Yes | pos | Client type identifier. Always pos for POS devices. |
Signature Headers
| Header | Required | Format | Description |
|---|---|---|---|
X-Key-Id | Yes | Free-form string | Identifier of the device identity key used to generate the signature. This key must be registered and active (not revoked) in the merchant_device_identity_keys table for this device. Example: device-id-key-2025-01 |
X-Timestamp | Yes | RFC3339 | Current time in UTC. Must be within 5 minutes of server time (past) and no more than 1 minute in the future. Used to prevent replay attacks. Example: 2025-12-13T14:25:30Z |
X-Nonce | Yes | Alphanumeric string | A unique random value generated fresh for every request. Combined with the timestamp, this prevents replay attacks. Should be at least 12 characters. Example: a1b2c3d4e5f6 |
X-Signature | Yes | v1={base64_hmac} | HMAC-SHA256 signature. Compute by signing the concatenation of device_id, key_id, timestamp, and nonce (joined with \n) using the device's HMAC secret. Base64-encode the result and prefix with v1=. Example: v1=SGVsbG8gV29ybGQ= |
See the Headers Reference for complete details on all POS headers.
Request
No request body is required. Authentication is entirely header-based.
Response
200 OK
{
"status": "attested",
"attestation_token": "att_f83kLmP9s2ab",
"expires_in_sec": 300
}
| Field | Type | Description |
|---|---|---|
status | string | Always "attested" on success |
attestation_token | string | Token to use for keys, config, and activation (prefix: att_) |
expires_in_sec | integer | Seconds until the token expires (always 300) |
The attestation token expires in 5 minutes. You must complete the keys, config, and activation steps within this window. If it expires, call /pos/attest again to get a new token.
400 Bad Request
{
"error": {
"code": "4000",
"message": "Missing required headers",
"details": {
"required_headers": ["X-Device-Id", "X-Key-Id", "X-Timestamp", "X-Nonce", "X-Signature"]
},
"trace_id": "abc123..."
}
}
404 Not Found — Device or Key Not Found
{
"error": {
"code": "4004",
"message": "Key ID not found or not active for this device",
"details": {
"key_id": "device-id-key-2025-01",
"device_id": "POS-8F3A2C91"
},
"trace_id": "abc123..."
}
}
Code Examples
- cURL
- Python
- Ruby
- Go
- C#
- PHP
curl -X POST https://api.viopay.io/pos/attest \
-H "X-Device-Id: POS-8F3A2C91" \
-H "X-Key-Id: device-id-key-2025-01" \
-H "X-Timestamp: 2025-12-13T14:25:30Z" \
-H "X-Nonce: a1b2c3d4e5f6" \
-H "X-Signature: v1=SGVsbG8gV29ybGQ=" \
-H "User-Agent: Viopay-POS/1.4.2 (Android 12; PAX-A920)" \
-H "X-Client-Type: pos" \
-H "Content-Type: application/json"
import requests
import hmac
import hashlib
import base64
from datetime import datetime, timezone
import uuid
device_id = "POS-8F3A2C91"
key_id = "device-id-key-2025-01"
secret = b"your-device-hmac-secret"
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
nonce = uuid.uuid4().hex[:12]
# Build signature (simplified for attestation)
sig_string = f"{device_id}\n{key_id}\n{timestamp}\n{nonce}"
signature = base64.b64encode(
hmac.new(secret, sig_string.encode(), hashlib.sha256).digest()
).decode()
url = "https://api.viopay.io/pos/attest"
headers = {
"X-Device-Id": device_id,
"X-Key-Id": key_id,
"X-Timestamp": timestamp,
"X-Nonce": nonce,
"X-Signature": f"v1={signature}",
"User-Agent": "Viopay-POS/1.4.2 (Android 12; PAX-A920)",
"X-Client-Type": "pos",
"Content-Type": "application/json",
}
response = requests.post(url, headers=headers)
data = response.json()
print(f"Status: {data['status']}")
print(f"Token: {data['attestation_token']}")
print(f"Expires in: {data['expires_in_sec']}s")
require 'net/http'
require 'json'
require 'uri'
require 'openssl'
require 'base64'
require 'securerandom'
device_id = "POS-8F3A2C91"
key_id = "device-id-key-2025-01"
secret = "your-device-hmac-secret"
timestamp = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
nonce = SecureRandom.hex(6)
sig_string = "#{device_id}\n#{key_id}\n#{timestamp}\n#{nonce}"
signature = Base64.strict_encode64(
OpenSSL::HMAC.digest("SHA256", secret, sig_string)
)
uri = URI("https://api.viopay.io/pos/attest")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request["X-Device-Id"] = device_id
request["X-Key-Id"] = key_id
request["X-Timestamp"] = timestamp
request["X-Nonce"] = nonce
request["X-Signature"] = "v1=#{signature}"
request["User-Agent"] = "Viopay-POS/1.4.2 (Android 12; PAX-A920)"
request["X-Client-Type"] = "pos"
response = http.request(request)
data = JSON.parse(response.body)
puts "Token: #{data['attestation_token']}"
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
type AttestResponse struct {
Status string `json:"status"`
AttestationToken string `json:"attestation_token"`
ExpiresInSec int `json:"expires_in_sec"`
}
func main() {
deviceID := "POS-8F3A2C91"
keyID := "device-id-key-2025-01"
secret := []byte("your-device-hmac-secret")
timestamp := time.Now().UTC().Format(time.RFC3339)
nonce := "a1b2c3d4e5f6"
sigString := fmt.Sprintf("%s\n%s\n%s\n%s", deviceID, keyID, timestamp, nonce)
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(sigString))
signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
req, _ := http.NewRequest("POST", "https://api.viopay.io/pos/attest", nil)
req.Header.Set("X-Device-Id", deviceID)
req.Header.Set("X-Key-Id", keyID)
req.Header.Set("X-Timestamp", timestamp)
req.Header.Set("X-Nonce", nonce)
req.Header.Set("X-Signature", "v1="+signature)
req.Header.Set("User-Agent", "Viopay-POS/1.4.2 (Android 12; PAX-A920)")
req.Header.Set("X-Client-Type", "pos")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var result AttestResponse
json.Unmarshal(body, &result)
fmt.Printf("Token: %s\n", result.AttestationToken)
fmt.Printf("Expires in: %d seconds\n", result.ExpiresInSec)
}
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
var deviceId = "POS-8F3A2C91";
var keyId = "device-id-key-2025-01";
var secret = Encoding.UTF8.GetBytes("your-device-hmac-secret");
var timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
var nonce = Guid.NewGuid().ToString("N")[..12];
var sigString = $"{deviceId}\n{keyId}\n{timestamp}\n{nonce}";
using var hmac = new HMACSHA256(secret);
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(sigString)));
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.viopay.io/pos/attest");
request.Headers.Add("X-Device-Id", deviceId);
request.Headers.Add("X-Key-Id", keyId);
request.Headers.Add("X-Timestamp", timestamp);
request.Headers.Add("X-Nonce", nonce);
request.Headers.Add("X-Signature", $"v1={signature}");
request.Headers.Add("User-Agent", "Viopay-POS/1.4.2 (Android 12; PAX-A920)");
request.Headers.Add("X-Client-Type", "pos");
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
<?php
$deviceId = "POS-8F3A2C91";
$keyId = "device-id-key-2025-01";
$secret = "your-device-hmac-secret";
$timestamp = gmdate("Y-m-d\TH:i:s\Z");
$nonce = bin2hex(random_bytes(6));
$sigString = implode("\n", [$deviceId, $keyId, $timestamp, $nonce]);
$signature = base64_encode(hash_hmac("sha256", $sigString, $secret, true));
$ch = curl_init("https://api.viopay.io/pos/attest");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Device-Id: $deviceId",
"X-Key-Id: $keyId",
"X-Timestamp: $timestamp",
"X-Nonce: $nonce",
"X-Signature: v1=$signature",
"User-Agent: Viopay-POS/1.4.2 (Android 12; PAX-A920)",
"X-Client-Type: pos",
],
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
echo "Token: " . $data["attestation_token"] . "\n";
What's Next?
Use the attestation token to download encryption keys:
Next step → Get Keys