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
Create signature request
Your server creates a signature request with the payload to sign.
User signs
Redirect the user to Ave where they review and sign the payload with their private key.
Retrieve signature
Poll the signature status or receive a callback when the user signs.
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 >
Signing configuration Your application’s client ID
Your application’s client secret (server-side only)
issuer
string
default: "https://aveid.net"
Ave issuer URL
The Ave identity ID of the user who should sign (from user profile)
The message/document to be signed
Optional metadata to store with the request
Request expiration time (default: 5 minutes)
Returns: SignatureRequest
Unique identifier for this signature request
ISO 8601 timestamp when the request expires
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
The request ID from createSignatureRequest
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
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
The request ID from createSignatureRequest
Returns: Promise that resolves with signature result
Whether the user signed the request
The cryptographic signature (if signed)
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 >
Your application’s client ID
issuer
string
default: "https://aveid.net"
Ave issuer URL
Returns: SignatureResult
status
'pending' | 'signed' | 'denied' | 'expired'
Current status of the request
Signature (only present if status is signed)
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
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
The original message that was signed
Returns: Verification result
Whether the signature is valid
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:
Frontend
Backend API
Verification
// 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.
Store public keys securely
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 ,
});
Set appropriate expiration times
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)
Include context in payload
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" );
}