Ed25519 Signed Requests Specification
Cryptographic specification for authenticating backend requests using Ed25519 signatures.
https://taqlyn.com/shop/checkout?cartId=cart_9841&promo=WELCOME20&aff=partner_8Privileged endpoints in the Taqlyn REST API require request signing with an Ed25519 private key. This prevents unauthorized short-link generation, protects against replay attacks, and ensures request integrity across the wire.
Official Server SDKs compute these headers automatically. If you are implementing a custom HTTP client or proxy, follow this exact specification.
Try signed sandbox calls in the browser from the Interactive API Reference (server SDK routes only).
#Required Headers
Every signed request must include:
| Header | Description | Example |
|---|---|---|
X-Taqlyn-Client-Id | Active application client ID. | app_test_8f92ab140cd3 |
X-Taqlyn-Timestamp | Current Unix time in seconds. | 1725537600 |
X-Taqlyn-Signature | Standard (or URL-safe) base64 of the raw 64-byte Ed25519 signature. | 6e98b2c41804... |
#Canonical Message Construction
Before signing, build the canonical message as six newline-separated fields (no trailing newline). This matches CanonicalRequestMessage in the API:
taqlyn-v1
{METHOD}
{PATH}
{unixTimestamp}
{clientID}
{hex(sha256(body))}taqlyn-v1: Fixed protocol version prefix (first line).METHOD: Uppercase HTTP verb (e.g.POST,PATCH,DELETE).PATH: Request path only — no query string (e.g./v1/short-links).unixTimestamp: Decimal Unix seconds — the same value sent inX-Taqlyn-Timestamp.clientID: Theapp_test_*/app_live_*client ID (same asX-Taqlyn-Client-Id).hex(sha256(body)): Hex-encoded SHA-256 of the raw request body bytes. Empty body uses SHA-256 of zero bytes (e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855).
#Example Canonical String
taqlyn-v1
POST
/v1/short-links
1725537600
app_test_8f92ab140cd3
a8910ce2b834910f...#Signing the Canonical String
Sign the UTF-8 bytes of the canonical string with your Ed25519 private key (PKCS#8 PEM). Encode the 64-byte signature as standard base64 (URL-safe and raw-URL-safe are also accepted by the API).
#Reference Implementation: TypeScript / Node.js
import { createHash, sign } from "node:crypto";
export function computeSignedHeaders(opts: {
clientId: string;
privateKeyPem: string;
method: string;
path: string;
body: string | Buffer;
}): Record<string, string> {
const timestamp = Math.floor(Date.now() / 1000).toString();
const bodyHash = createHash("sha256")
.update(opts.body || "")
.digest("hex");
const canonical = [
"taqlyn-v1",
opts.method.toUpperCase(),
opts.path,
timestamp,
opts.clientId,
bodyHash,
].join("\n");
const signature = sign(null, Buffer.from(canonical, "utf8"), opts.privateKeyPem).toString(
"base64",
);
return {
"X-Taqlyn-Client-Id": opts.clientId,
"X-Taqlyn-Timestamp": timestamp,
"X-Taqlyn-Signature": signature,
};
}#Reference Implementation: Go
package main
import (
"crypto/ed25519"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"strconv"
"strings"
"time"
)
func SignRequest(clientID string, privKey ed25519.PrivateKey, method, path string, body []byte) (map[string]string, error) {
ts := time.Now().Unix()
sum := sha256.Sum256(body)
canonical := fmt.Sprintf(
"taqlyn-v1\n%s\n%s\n%d\n%s\n%s",
strings.ToUpper(method),
path,
ts,
clientID,
hex.EncodeToString(sum[:]),
)
sig := ed25519.Sign(privKey, []byte(canonical))
return map[string]string{
"X-Taqlyn-Client-Id": clientID,
"X-Taqlyn-Timestamp": strconv.FormatInt(ts, 10),
"X-Taqlyn-Signature": base64.StdEncoding.EncodeToString(sig),
}, nil
}#Security Tolerances & Verification
- Timestamp Drift Limit: The API rejects requests where
abs(now - X-Taqlyn-Timestamp) > 300seconds (401). - Signature Algorithm: Only Ed25519 is accepted. RSA or ECDSA keys are rejected.
- Environment isolation: Sandbox (
app_test_*) and production (app_live_*) keypairs never cross-validate.