Skip to main content
Ave Signing lets your app ask a user to cryptographically sign a payload using their identity key. The signature proves the user chose to approve a specific operation at a specific point in time — and it can be independently verified by any party that has the user’s public key. A signature request has a lifecycle: it is created as pending, the user acts (signs or denies) in the signing UI, and the status transitions to signed, denied, or expired.

When to use signing

Signing is appropriate when you need non-repudiable user approval — situations where “the user clicked a button” is not strong enough evidence. Examples:
  • Approving a high-value transaction
  • Consenting to a legal document or contract
  • Authorizing an irreversible data change
  • Confirming a sensitive account action

Full flow

1

Create a signature request

Call your server, which calls POST /api/signing/request using your app credentials plus the target identity and the payload to be signed.
// Server-side
const response = await fetch("https://api.aveid.net/api/signing/request", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    clientId: process.env.AVE_CLIENT_ID,
    clientSecret: process.env.AVE_CLIENT_SECRET,
    identityId: "user-identity-uuid",
    payload: "I approve the transfer of $500 to account ending 4242",
    metadata: { action: "transfer", amount: 500, currency: "USD" },
    expiresInSeconds: 300,
  }),
});

const { requestId, expiresAt, publicKey } = await response.json();
identityId is the Ave identity UUID (the sub from the user’s id_token). payload is the string the user will see and sign — keep it human-readable. The identity must have a signing key set up, or the request will fail.
2

Present the signing UI to the user

Show the Ave signing interface to the user. They will see the payload and choose to sign or deny.
import { openAveSigningSheet } from "@ave-id/embed";

const result = await openAveSigningSheet({
  requestId,
  issuer: "https://aveid.net",
});

if (result?.type === "signed") {
  // User signed — verify server-side before taking action
} else if (result?.type === "denied") {
  // User explicitly denied
} else {
  // User closed without acting
}
Alternatively, use openAveSigningPopup for a popup window, or build a custom polling UX without the embed library.
3

Poll for request status

If you need to track status independently of the embed callback (for example in a server polling loop or when the user is on a separate device), poll the status endpoint:
async function pollSigningStatus(requestId: string, clientId: string): Promise<string> {
  while (true) {
    const res = await fetch(
      `https://api.aveid.net/api/signing/request/${requestId}/status?clientId=${clientId}`
    );
    const { status } = await res.json();

    if (status === "signed" || status === "denied" || status === "expired") {
      return status;
    }

    await new Promise(r => setTimeout(r, 2000));
  }
}
Add a poll timeout aligned with expiresInSeconds. Do not poll indefinitely — if the request expires, the status endpoint will return expired automatically.
4

Verify the signature before taking action

After receiving a signed status, verify the signature server-side before executing any side effects.
// Server-side
const verifyRes = await fetch("https://api.aveid.net/api/signing/verify", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    requestId,
    // Or include message, signature, publicKey manually
  }),
});

const { valid } = await verifyRes.json();
if (!valid) throw new Error("Signature verification failed");

// Now safe to execute the approved action
You can also verify locally using the publicKey returned when you created the request and standard Ed25519 verification. This removes a round-trip to Ave’s server and works offline.

Create request body

clientId
string
required
Your app’s client ID.
clientSecret
string
required
Your app’s client secret. This call must be server-side only.
identityId
string
required
UUID of the Ave identity that will sign the request. This is the sub claim from the user’s id_token.
payload
string
required
The string the user will see and sign. Maximum 10,000 characters. Keep it human-readable and descriptive — the user should understand what they are approving.
metadata
object
Optional arbitrary JSON metadata. Not shown to the user directly but attached to the request record.
expiresInSeconds
number
default:"300"
How long the request stays open before automatically expiring. Minimum 60, maximum 3600 (1 hour).

Create request response

requestId
string
UUID identifying this signing request. Pass this to the status endpoint and the embed UI.
expiresAt
string
ISO 8601 timestamp when the request expires.
publicKey
string
The Ed25519 public key for this identity, in base64. Store it if you want to verify signatures locally.

Request states

StateMeaning
pendingCreated, waiting for user action
signedUser signed the payload
deniedUser explicitly denied
expiredRequest timed out before user acted
The server transitions pending requests to expired automatically when the status endpoint is queried after the expiry time.

Verification

POST /api/signing/verify validates a message/signature/publicKey tuple:
{
  "message": "I approve the transfer of $500 to account ending 4242",
  "signature": "base64-encoded-ed25519-signature",
  "publicKey": "base64-encoded-ed25519-public-key"
}
Response:
{ "valid": true }
Or on failure:
{ "valid": false, "error": "Invalid signature format" }

Practical lifecycle

1

Create and persist locally

Create the request via your server and immediately save it to your own database with pending status. Record requestId, expiresAt, and publicKey.
2

Surface the UI with a countdown

Start a visible countdown for the user aligned with expiresInSeconds. Users expect to understand how long they have.
3

Poll or use embed callback

Use the embed callback for interactive flows where the user is on the same device. Use server-side polling for flows where the user might be on a different device (for example, a QR-code-triggered signing).
4

Transition local state

On signed, denied, or expired, transition your local record and update the UI. Do not execute side effects until after signature verification.
5

Verify then act

After confirming signed, call the verify endpoint (or verify locally with Ed25519). Only then execute the approved action.

Edge cases

If the target identity has not set up a signing key, the create request call returns an error. Handle this by prompting the user to add a signing key in their Ave account before requesting a signature.
The status endpoint will return expired. Clear the request from your UI and offer the user a way to restart the flow.
denied is a valid terminal state. Treat it as the user explicitly rejecting the action. Do not retry automatically.
If you query the status for a requestId that does not belong to your clientId, you will receive a 403. Store requestId server-side in your own session and never expose it to untrusted clients.
Last modified on March 3, 2026