Skip to main content
The Ave Signing API allows applications to request cryptographic signatures from users using their Ave identity keys. This enables secure document signing, transaction approval, and authentication flows.

How It Works

1

Create signature request

Your server creates a signature request with the payload to sign.
2

User signs

Redirect the user to Ave where they review and sign the payload with their private key.
3

Retrieve signature

Poll the signature status or receive a callback when the user signs.
4

Verify signature

Verify the signature using the user’s public key.

Quick Start

Here’s a complete signing flow:
import {
  createSignatureRequest,
  buildSigningUrl,
  getSignatureStatus,
  verifySignature,
} from "@ave-id/sdk";

// 1. Create signature request (server-side)
const request = await createSignatureRequest(
  {
    clientId: "YOUR_CLIENT_ID",
    clientSecret: process.env.AVE_CLIENT_SECRET!,
  },
  {
    identityId: "user_identity_id",
    payload: "Sign this document",
    metadata: { documentId: "doc_123" },
  }
);

// 2. Redirect user to signing page
const signingUrl = buildSigningUrl({}, request.requestId);
window.location.href = signingUrl;

// 3. Check signature status
const result = await getSignatureStatus(
  { clientId: "YOUR_CLIENT_ID" },
  request.requestId
);

if (result.status === "signed" && result.signature) {
  // 4. Verify signature
  const verification = await verifySignature(
    {},
    {
      message: "Sign this document",
      signature: result.signature,
      publicKey: request.publicKey,
    }
  );

  console.log("Valid:", verification.valid);
}

API Reference

createSignatureRequest

Creates a new signature request. This must be called from your server with client credentials.
function createSignatureRequest(
  config: SigningConfig,
  params: {
    identityId: string;
    payload: string;
    metadata?: Record<string, unknown>;
    expiresInSeconds?: number;
  }
): Promise<SignatureRequest>
config
SigningConfig
required
Signing configuration
params
object
required
Returns: SignatureRequest
requestId
string
Unique identifier for this signature request
expiresAt
string
ISO 8601 timestamp when the request expires
publicKey
string
User’s public signing key (for verification)
Example:
const request = await createSignatureRequest(
  {
    clientId: "app_123",
    clientSecret: process.env.AVE_SECRET!,
  },
  {
    identityId: "identity_abc456",
    payload: JSON.stringify({
      action: "transfer",
      amount: 1000,
      to: "user_xyz",
    }),
    metadata: {
      transactionId: "txn_789",
      timestamp: new Date().toISOString(),
    },
    expiresInSeconds: 600, // 10 minutes
  }
);

console.log(request.requestId); // "req_abc123"

buildSigningUrl

Builds the URL to redirect users for signing.
function buildSigningUrl(
  config: { issuer?: string },
  requestId: string,
  options?: { embed?: boolean }
): string
config.issuer
string
default:"https://aveid.net"
Ave issuer URL
requestId
string
required
The request ID from createSignatureRequest
options.embed
boolean
default:false
If true, optimizes the UI for iframe embedding
Returns: Full signing URL Example:
const url = buildSigningUrl({}, "req_abc123");
// https://aveid.net/sign?requestId=req_abc123

// For iframe
const embedUrl = buildSigningUrl({}, "req_abc123", { embed: true });
// https://aveid.net/sign?requestId=req_abc123&embed=1

openSigningPopup

Opens a popup window for signing and returns a promise that resolves when complete.
function openSigningPopup(
  config: { issuer?: string },
  requestId: string
): Promise<{
  signed: boolean;
  signature?: string;
  publicKey?: string;
}>
config.issuer
string
default:"https://aveid.net"
Ave issuer URL
requestId
string
required
The request ID from createSignatureRequest
Returns: Promise that resolves with signature result
signed
boolean
Whether the user signed the request
signature
string
The cryptographic signature (if signed)
publicKey
string
User’s public key (if signed)
Example:
import { openSigningPopup } from "@ave-id/sdk";

async function requestSignature(requestId: string) {
  try {
    const result = await openSigningPopup({}, requestId);

    if (result.signed) {
      console.log("Signature:", result.signature);
      console.log("Public key:", result.publicKey);
      // Save signature to your database
    } else {
      console.log("User denied signature request");
    }
  } catch (error) {
    console.error("Popup blocked or closed:", error);
  }
}
Behavior:
  • Opens a centered popup window (500x600)
  • Listens for postMessage events from Ave
  • Resolves when user signs or denies
  • Resolves with signed: false if popup is closed
  • Rejects if popup is blocked by browser

getSignatureStatus

Checks the status of a signature request.
function getSignatureStatus(
  config: { clientId: string; issuer?: string },
  requestId: string
): Promise<SignatureResult>
config
object
required
requestId
string
required
The signature request ID
Returns: SignatureResult
status
'pending' | 'signed' | 'denied' | 'expired'
Current status of the request
signature
string
Signature (only present if status is signed)
resolvedAt
string
ISO 8601 timestamp when signed/denied (if resolved)
Example:
// Poll for signature
const checkStatus = async (requestId: string) => {
  const interval = setInterval(async () => {
    const result = await getSignatureStatus(
      { clientId: "app_123" },
      requestId
    );

    if (result.status === "signed") {
      clearInterval(interval);
      console.log("Signed!", result.signature);
    } else if (result.status === "denied") {
      clearInterval(interval);
      console.log("User denied signature");
    } else if (result.status === "expired") {
      clearInterval(interval);
      console.log("Request expired");
    }
  }, 2000); // Poll every 2 seconds
};

getPublicKey

Retrieves a user’s public signing key by their Ave handle.
function getPublicKey(
  config: { issuer?: string },
  handle: string
): Promise<{
  handle: string;
  publicKey: string;
  createdAt: string;
}>
config.issuer
string
default:"https://aveid.net"
Ave issuer URL
handle
string
required
Ave handle (with or without @ prefix)
Returns: Public key information Example:
const keyInfo = await getPublicKey({}, "@alice");
console.log(keyInfo.publicKey); // "04a1b2c3..."
console.log(keyInfo.createdAt); // "2024-01-15T10:30:00Z"

verifySignature

Verifies a signature using Ave’s verification API.
function verifySignature(
  config: { issuer?: string },
  params: {
    message: string;
    signature: string;
    publicKey: string;
  }
): Promise<{ valid: boolean; error?: string }>
config.issuer
string
default:"https://aveid.net"
Ave issuer URL
params
object
required
Returns: Verification result
valid
boolean
Whether the signature is valid
error
string
Error message (if verification failed)
Example:
const result = await verifySignature(
  {},
  {
    message: "Transfer $1000 to @bob",
    signature: "3045022100...",
    publicKey: "04a1b2c3...",
  }
);

if (result.valid) {
  console.log("Signature is valid!");
  // Process the signed action
} else {
  console.error("Invalid signature:", result.error);
}

Complete Example: Document Signing

Here’s a full implementation of document signing:
// components/DocumentSigner.tsx
import { useState } from "react";
import { openSigningPopup } from "@ave-id/sdk";

export function DocumentSigner({ documentId, content }: Props) {
  const [signing, setSigning] = useState(false);
  const [signature, setSignature] = useState<string | null>(null);

  const handleSign = async () => {
    setSigning(true);

    try {
      // Create signature request on backend
      const response = await fetch("/api/signatures/create", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ documentId, content }),
      });

      const { requestId } = await response.json();

      // Open signing popup
      const result = await openSigningPopup({}, requestId);

      if (result.signed) {
        // Store signature
        await fetch("/api/signatures/store", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            documentId,
            signature: result.signature,
            publicKey: result.publicKey,
          }),
        });

        setSignature(result.signature);
      } else {
        alert("Signature request was denied");
      }
    } catch (error) {
      console.error("Signing failed:", error);
    } finally {
      setSigning(false);
    }
  };

  return (
    <div>
      <h3>Document: {documentId}</h3>
      <pre>{content}</pre>

      {signature ? (
        <div>
          <p>✓ Signed</p>
          <code>{signature.slice(0, 20)}...</code>
        </div>
      ) : (
        <button onClick={handleSign} disabled={signing}>
          {signing ? "Signing..." : "Sign Document"}
        </button>
      )}
    </div>
  );
}

Use Cases

Document Signing

Legal documents, contracts, and agreements

Transaction Approval

Financial transactions and payment confirmations

Authentication

Passwordless login with cryptographic proof

Data Attestation

Prove ownership or authenticity of data

Security Considerations

Always verify signatures on your backend before taking action. Never trust client-side verification alone.
Cache user public keys in your database to avoid repeated lookups:
// First time: fetch and store
const keyInfo = await getPublicKey({}, handle);
await db.users.update(userId, { publicKey: keyInfo.publicKey });

// Later: use cached key
const user = await db.users.find(userId);
await verifySignature({}, {
  message,
  signature,
  publicKey: user.publicKey,
});
Choose expiration based on your use case:
  • Quick actions: 60-300 seconds
  • Document review: 600-1800 seconds (10-30 minutes)
  • Async workflows: 3600+ seconds (1+ hours)
Make payloads human-readable and include context:
const payload = JSON.stringify({
  action: "approve_transaction",
  amount: "$1,000.00",
  recipient: "@bob",
  timestamp: new Date().toISOString(),
  nonce: crypto.randomUUID(), // Prevent replay
}, null, 2);
Include nonces or timestamps to prevent replay attacks:
const nonce = crypto.randomUUID();
const payload = `${documentHash}:${nonce}:${Date.now()}`;

// Later, verify nonce hasn't been used
if (await db.nonces.exists(nonce)) {
  throw new Error("Signature replay detected");
}

Build docs developers (and LLMs) love