Deployment Identity

Every Kyper deployment gets a cryptographic identity, enabling federation, license verification, and instance coordination.

Overview

When Kyper provisions a deployment, it generates a unique Ed25519 keypair and a platform-signed attestation token (JWT). These are injected as environment variables, giving your app a provable identity without any configuration.

This enables two powerful patterns:

  • Attestation verification — Prove a request comes from a legitimate, paid Kyper deployment. Verify the JWT against Kyper's public key (JWKS). Zero API calls needed.
  • Per-request signing — Each deployment can sign outbound requests with its private key. Recipients verify using the public key embedded in the attestation token.

Environment variables

Three reserved environment variables are automatically injected into every deployment:

Variable Description
KYPER_IDENTITY_KEY Ed25519 private key (PEM). Use this to sign outbound requests.
KYPER_IDENTITY_PUBLIC_KEY Ed25519 public key (PEM). Convenience copy — also embedded in the attestation token.
KYPER_ATTESTATION_TOKEN Platform-signed JWT proving this is a legitimate Kyper deployment.

Attestation token (JWT)

The attestation token is a JWT signed by Kyper's platform RSA key. It contains deployment metadata and the deployment's public key. To verify, fetch Kyper's JWKS and validate the signature.

Claims:

json
{
  "iss": "https://kyper.shop",
  "sub": "deployment:42",
  "app_slug": "my-app",
  "app_name": "My App",
  "deployment_id": 42,
  "consumer_id": 7,
  "tier": "pro",
  "public_key": "-----BEGIN PUBLIC KEY-----\n...",
  "iat": 1711152000,
  "exp": 1742688000
}

JWKS endpoint: GET https://kyper.shop/api/v1/identity/jwks (public, no authentication required)

Verification examples

These examples show how to verify that a request comes from a legitimate Kyper deployment by validating the attestation token.

Ruby

ruby
require "jwt"
require "net/http"
require "json"

# Fetch Kyper's JWKS (cache this — it rarely changes)
jwks_uri = URI("https://kyper.shop/api/v1/identity/jwks")
jwks = JSON.parse(Net::HTTP.get(jwks_uri))

# Extract the RSA public key from JWKS
jwk = jwks["keys"].first
n = OpenSSL::BN.new(Base64.urlsafe_decode64(jwk["n"]), 2)
e = OpenSSL::BN.new(Base64.urlsafe_decode64(jwk["e"]), 2)
key = OpenSSL::PKey::RSA.new.tap { |k| k.set_key(n, e, nil) }

# Verify the attestation token from an incoming request
token = request.headers["X-Kyper-Attestation"]
claims = JWT.decode(token, key, true, algorithm: "RS256").first

puts claims["app_slug"]       # => "my-app"
puts claims["deployment_id"]  # => 42
puts claims["tier"]           # => "pro"

Node.js

javascript
import jwt from "jsonwebtoken";
import jwksClient from "jwks-rsa";

const client = jwksClient({ jwksUri: "https://kyper.shop/api/v1/identity/jwks" });

function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    callback(err, key?.getPublicKey());
  });
}

// Verify the attestation token
const token = req.headers["x-kyper-attestation"];
jwt.verify(token, getKey, { algorithms: ["RS256"] }, (err, claims) => {
  if (err) throw new Error("Invalid attestation token");
  console.log(claims.app_slug);       // "my-app"
  console.log(claims.deployment_id);  // 42
});

Go

go
import (
    "github.com/golang-jwt/jwt/v5"
    "github.com/MicahParks/keyfunc/v3"
)

// Create JWKS client (cache automatically)
jwks, _ := keyfunc.NewDefault([]string{"https://kyper.shop/api/v1/identity/jwks"})

// Verify the attestation token
token := r.Header.Get("X-Kyper-Attestation")
parsed, err := jwt.Parse(token, jwks.KeyFunc,
    jwt.WithValidMethods([]string{"RS256"}))
if err != nil {
    // Invalid token
}
claims := parsed.Claims.(jwt.MapClaims)
fmt.Println(claims["app_slug"])       // "my-app"
fmt.Println(claims["deployment_id"])  // 42

Python

python
import jwt
import requests

# Fetch Kyper JWKS (cache this)
jwks = requests.get("https://kyper.shop/api/v1/identity/jwks").json()
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(jwks["keys"][0])

# Verify the attestation token
token = request.headers.get("X-Kyper-Attestation")
claims = jwt.decode(token, public_key, algorithms=["RS256"])

print(claims["app_slug"])       # "my-app"
print(claims["deployment_id"])  # 42

Per-request signing

For instance-to-instance communication (federation), each deployment can sign requests with its Ed25519 private key. The recipient verifies using the public key from the sender's attestation token.

Signing (sender)

ruby
# In your Kyper-deployed app
key = OpenSSL::PKey::Ed25519.new(ENV["KYPER_IDENTITY_KEY"])

# Sign the request body
body = { action: "sync", data: "..." }.to_json
signature = Base64.strict_encode64(key.sign(nil, body))

# Send with attestation + signature
http.post(peer_url,
  body: body,
  headers: {
    "X-Kyper-Attestation" => ENV["KYPER_ATTESTATION_TOKEN"],
    "X-Kyper-Signature" => signature,
    "Content-Type" => "application/json"
  }
)

Verifying (recipient)

ruby
# First, verify the attestation token (see examples above)
claims = JWT.decode(token, kyper_public_key, true, algorithm: "RS256").first

# Extract the sender's public key from the attestation claims
sender_key = OpenSSL::PKey::Ed25519.new(claims["public_key"])

# Verify the request signature
signature = Base64.strict_decode64(request.headers["X-Kyper-Signature"])
sender_key.verify(nil, signature, request.raw_post)
# Raises OpenSSL::PKey::PKeyError if invalid

Key rotation

Identity keys persist across redeployments to avoid breaking federation. To rotate a deployment's keypair:

bash
# Rotate via API (developer authentication required)
curl -X POST https://kyper.shop/api/v1/apps/my-app/deployments/42/rotate_identity \
  -H "Authorization: Bearer YOUR_API_TOKEN"

This generates a new keypair, re-signs the attestation token, and triggers an environment reload. The deployment will receive the new credentials within seconds.

API endpoints

Endpoint Auth Description
GET /api/v1/identity/jwks Public Kyper platform JWKS for verifying attestation tokens
GET /api/v1/apps/:slug/deployment_keys Bearer List all deployment public keys for your app
GET /api/v1/apps/:slug/deployments/:id/identity Bearer Get a single deployment's identity details
POST /api/v1/apps/:slug/deployments/:id/rotate_identity Bearer Rotate a deployment's keypair