Skip to content

Authorization signatures

If your app has authorization keys enabled in the Dashboard, you must sign certain requests to the Privy API with an authorization key and include the produced signature (called an authorization signature) as a request header. The authorization signature ensures that your wallets only execute actions requested directly by your server.

Authorization signatures are always required on POST requests to /api/v1/wallets/<wallet_id>/rpc and are optionally required on POST requests to /api/v1/wallets if an authorization key has been added with wallet create/modify permissions.

Your app can choose not to register any authorization keys, in which case Privy will not require an authorization signature. However, authorization signatures are an important security measure and we strongly recommend having authorization keys for production apps.

Creating keys

You can create new authorization keys for your app in the Authorization keys page of the Privy Dashboard. When creating a new key, Privy will generate a P-256 keypair for your app directly on your device, and show you the private key.

  • The private key is generated on your device, and is only ever known to your app. Neither Privy nor the TEE ever sees the P-256 private key, and cannot sign payloads with it.
  • The public key is registered with the secure enclave that secures your wallets, and is used to verify signatures produced by your servers.

Generating signatures

TIP

If you're using Privy's Server SDK to use server wallets, simply pass your authorization key to the Privy client's constructor and the SDK will automatically include authorization signatures when required.

Generating an authorization signature for the request involves three steps:

  1. Generate a JSON payload containing the following fields. All fields are required unless otherwise specified.
FieldTypeDescription
version1Authorization signature version. Currently, 1 is the only version.
method'POST' | 'PUT' | 'PATCH' | 'DELETE'HTTP method for the request. Signatures are not required on 'GET' requests.
urlstringThe full URL for the request. Should not include a trailing slash.
bodyJSONJSON body for the request.
headersJSONJSON object containing any Privy-specific headers, e.g. those that are prefixed with 'privy-'. This should not include any other headers, such as authentication headers, content-type, or trace headers.
headers['privy-app-id']stringPrivy app ID header (required).
headers['privy-idempotency-key']stringPrivy idempotency key header (optional). If the request does not contain an idempotency key, leave this field out of the payload.
  1. Canonicalize the payload per RFC8785 and serialize it to a string. This GitHub repository links to various libraries for JSON canonicalization in different languages.
  2. Sign the serialized JSON with ECDSA P-256 using your app's private key and serialize it to a base64-encoded string.

See the code snippets below to serialize and sign requests with your app's private key.

ts
import canonicalize from 'canonicalize'; // Support JSON canonicalization
import crypto from 'crypto'; // Support P-256 signing

// Replace this with your private key from the Dashboard
const PRIVY_AUTHORIZATION_KEY = 'wallet-auth:insert-your-private-key-here';
...

function getAuthorizationSignature({url, body}: {url: string; body: object}) {
  const payload = {
    version: 1,
    method: 'POST',
    url,
    body,
    headers: {
    'privy-app-id': 'insert-your-app-id'
    // If your request includes an idempotency key, include that header here as well
    }
  };

  // JSON-canonicalize the payload and convert it to a buffer
  const serializedPayload = canonicalize(payload) as string;
  const serializedPayloadBuffer = Buffer.from(serializedPayload);

  // Replace this with your app's authorization key. We remove the 'wallet-auth:' prefix
  // from the key before using it to sign requests
  const privateKeyAsString = PRIVY_AUTHORIZATION_KEY.replace('wallet-auth:', '');

  // Convert your private key to PEM format, and instantiate a node crypto KeyObject for it
  const privateKeyAsPem = `-----BEGIN PRIVATE KEY-----\n${privateKeyAsString}\n-----END PRIVATE KEY-----`;
  const privateKey = crypto.createPrivateKey({
    key: privateKeyAsPem,
    format: 'pem',
  });

  // Sign the payload buffer with your private key and serialize the signature to a base64 string
  const signatureBuffer = crypto.sign('sha256', serializedPayloadBuffer, privateKey);
  const signature = signatureBuffer.toString('base64');
  return signature;
}

const authorizationSignature = getAuthorizationSignature({
  // Replace with your desired path, e.g. '/v1/wallets/<wallet_id>/rpc'
  url: 'https://api.privy.io/v1/wallets',
  // Replace with your desired body
  body: {
    chain_type: 'ethereum',
  },
});

Setting headers

Once you have signed your request with your authorization keypair, include the produced signature as the 'privy-authorization-signature' header for your request.