COYNS DOCS← Back to Home

Authentication

Every authenticated request to the Coyns API must include Ed25519 signature headers. POST requests also require an OTP.

Required Headers

X-Agent-Id — your agent ID
X-Timestamp — Unix epoch seconds
X-Signature — Ed25519 signature (base64)
X-OTP — required for POST requests (use "000000" for staging)
X-Idempotency-Key — optional, for replay protection

Canonical String

The signature is computed over a canonical string built from the request:

method\n        (lowercase: "post", "get")
path\n          (e.g. "/v1/payments")
sha256(body)\n  (hex-encoded hash of request body)
timestamp\n     (same value as X-Timestamp)
idempotency_key (empty string if not provided)

The five parts are joined with newline characters. The resulting string is signed with your Ed25519 private key.

TypeScript Example

import { createHash } from "crypto";
import * as ed from "@noble/ed25519";
import { sha512 } from "@noble/hashes/sha512";

ed.etc.sha512Sync = (...m: Uint8Array[]) =>
  sha512(ed.etc.concatBytes(...m));

function signRequest(opts: {
  privateKey: Uint8Array;
  method: string;
  path: string;
  body: string;
  idempotencyKey?: string;
}) {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const bodyHash = createHash("sha256")
    .update(opts.body || "")
    .digest("hex");

  const canonical = [
    opts.method.toLowerCase(),
    opts.path,
    bodyHash,
    timestamp,
    opts.idempotencyKey || "",
  ].join("\n");

  const sig = ed.sign(Buffer.from(canonical), opts.privateKey);
  return {
    timestamp,
    signature: Buffer.from(sig).toString("base64"),
  };
}

function buildHeaders(opts: {
  agentId: string;
  privateKey: Uint8Array;
  method: string;
  path: string;
  body: string;
  idempotencyKey?: string;
  otp?: string;
}) {
  const { timestamp, signature } = signRequest(opts);
  return {
    "Content-Type": "application/json",
    "X-Agent-Id": opts.agentId,
    "X-Timestamp": timestamp,
    "X-Signature": signature,
    ...(opts.otp && { "X-OTP": opts.otp }),
    ...(opts.idempotencyKey && {
      "X-Idempotency-Key": opts.idempotencyKey,
    }),
  };
}

Staging OTP

In the staging environment, use 000000 as the OTP value for all POST requests.