Documentation Index
Fetch the complete documentation index at: https://docs.privy.io/llms.txt
Use this file to discover all available pages before exploring further.
If your integration cannot leverage the automatic signing capabilities of Privy’s SDKs, Privy also offers utility functions for formatting and signing requests.
You can use these functions to sign requests, even without initializing an instance of the Privy SDK, and then can manually include the returned signature in requests to the Privy API.
Client-side SDKs
There are two ways to sign requests on the client:
- Sign a server-formatted binary payload (recommended) — Your server formats the request into canonical bytes using the server SDK, sends the bytes to the client, and the client signs them directly. This gives the server full control over payload construction and allows server-side changes without requiring client SDK updates.
- Sign a structured payload — The client constructs the payload locally and signs it. This approach is simpler for prototyping but couples the client to the payload format.
In this approach, your server serializes the API request into canonical bytes using Privy’s server SDK. The client receives these bytes and signs them directly, without needing to understand the payload structure.
This is the recommended approach because:
- Your server has full control over constructing the correct payload
- Server-side updates to the payload format do not require client SDK changes
- The client does not need to construct or serialize the payload itself
Format the payload on your server
Use the server SDK’s formatRequestForAuthorizationSignature function to serialize the request
into bytes. See Formatting requests below for the full reference.import {formatRequestForAuthorizationSignature} from '@privy-io/node';
const serializedPayload = formatRequestForAuthorizationSignature({
version: 1,
url: 'https://api.privy.io/v1/wallets/<wallet-id>/rpc',
method: 'POST',
headers: {'privy-app-id': '<app-id>'},
body: {
method: 'personal_sign',
params: {message: 'Hello from Privy!', encoding: 'utf-8'}
}
});
// Send the bytes to your client as base64
const payloadBase64 = Buffer.from(serializedPayload).toString('base64');
Send the binary payload to your client
Send the base64-encoded bytes from your server to the client (e.g., as a JSON response field).
Sign the binary payload on the client
Decode the base64 payload and pass the raw bytes to generateAuthorizationSignature. React
React Native
Swift
Android
Unity
import {useAuthorizationSignature} from '@privy-io/react-auth';
const {generateAuthorizationSignature} = useAuthorizationSignature();
// Decode the base64 payload received from the server
const payloadBytes = Uint8Array.from(atob(payloadBase64), (c) => c.charCodeAt(0));
// Sign the binary payload
const {signature} = await generateAuthorizationSignature(payloadBytes);
import {useAuthorizationSignature} from '@privy-io/expo';
import {Buffer} from 'buffer';
const {generateAuthorizationSignature} = useAuthorizationSignature();
// Decode the base64 payload received from the server
const payloadBytes = new Uint8Array(Buffer.from(payloadBase64, 'base64'));
// Sign the binary payload
const {signature} = await generateAuthorizationSignature(payloadBytes);
guard let user = try await privy.getUser() else { return }
// Decode the base64 payload received from the server
guard let payloadData = Data(base64Encoded: payloadBase64) else { return }
// Sign the binary payload
let signature = try await user.generateAuthorizationSignature(payload: payloadData)
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
val user = privy.getUser() ?: return
// Decode the base64 payload received from the server
@OptIn(ExperimentalEncodingApi::class)
val payloadBytes = Base64.decode(payloadBase64)
// Sign the binary payload
user.generateAuthorizationSignature(payloadBytes)
.onSuccess { signature ->
// Use signature in API request headers as 'privy-authorization-signature'
}
.onFailure { error ->
// Handle error
}
using System;
using Privy.Auth.Models;
IPrivyUser user = await PrivyManager.Instance.GetUser();
// Decode the base64 payload received from the server
byte[] payloadBytes = Convert.FromBase64String(payloadBase64);
// Sign the binary payload
string signature = await user.GenerateAuthorizationSignature(payloadBytes);
Send the signature to your backend
Return the base64-encoded signature string to your server.
Send the request to the Privy API
From your server, include the signature when making the request to the Privy API. You can pass
it via the Node SDK’s authorization_context.signatures field, or include it directly as the
privy-authorization-signature header.import {PrivyClient} from '@privy-io/node';
const client = new PrivyClient({appId: '<app-id>', appSecret: '<app-secret>'});
// Pass the client-generated signature in authorization_context
const response = await client.wallets().rpc('<wallet-id>', {
method: 'personal_sign',
params: {message: 'Hello from Privy!', encoding: 'utf-8'},
authorization_context: {signatures: [signature]}
});
fetch('https://api.privy.io/v1/wallets/<wallet-id>/rpc', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${btoa('<app-id>:<app-secret>')}`,
'privy-app-id': '<app-id>',
'privy-authorization-signature': signature
},
body: JSON.stringify({
method: 'personal_sign',
params: {message: 'Hello from Privy!', encoding: 'utf-8'}
})
});
Signing a structured payload
Alternatively, the client can construct the signature payload locally and sign it directly. The SDK canonicalizes the payload to JSON (RFC 8785) before signing.
1. Construct your signature payload
Given your desired request to the Privy API, build a JSON payload with the following fields. Your application will sign this entire payload to authorize the request to the Privy API.
| Field | Type | Description | | | |
|---|
version | 1 | Authorization 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. | | | |
url | string | The full URL for the request. Should not include a trailing slash. | | | |
body | JSON | JSON body for the request. | | | |
headers | JSON | JSON 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'] | string | Privy app ID header (required). | | | |
headers['privy-idempotency-key'] | string | Privy idempotency key header (optional). If the request does not contain an idempotency key, leave this field out of the payload. | | | |
headers['privy-request-expiry'] | string | Privy request expiry header (optional). If the request does not contain an expiry header, leave this field out of the payload. | | | |
As an example, you might build a payload for an Ethereum personal_sign RPC request like so:
TypeScript
Swift
Android
Flutter
Unity
const signaturePayload = {
version: 1,
url: 'https://api.privy.io/v1/wallets/<wallet-id>/rpc',
method: 'POST',
headers: {
'privy-app-id': '<app-id>'
},
body: {
method: 'personal_sign',
params: {
message: 'Hello from Privy!',
encoding: 'utf-8'
}
}
} as const;
import PrivySDK
// Define structs for the request body (must conform to Encodable)
struct PersonalSignParams: Encodable {
let message: String
let encoding: String
}
struct PersonalSignRpcRequest: Encodable {
let method: String
let params: PersonalSignParams
}
// Create the RPC request body
let rpcRequest = PersonalSignRpcRequest(
method: "personal_sign",
params: PersonalSignParams(message: "Hello from Privy!", encoding: "utf-8")
)
// Create the signature payload using WalletApiPayload
let signaturePayload = WalletApiPayload(
version: 1,
url: "https://api.privy.io/v1/wallets/<wallet-id>/rpc",
method: "POST",
headers: ["privy-app-id": "<app-id>"],
body: rpcRequest
)
WalletApiPayload parameters
The payload version. Currently, only version 1 is supported.
The full URL of the API endpoint, including protocol and domain. Should not include a trailing
slash.
The HTTP method for the request (e.g., "POST", "PUT", "PATCH", "DELETE").
Privy-specific headers (those prefixed with privy-). This should not include authentication
headers, content-type, or trace headers. The privy-app-id header is always required.
The request body to be serialized and included in the signature. Must conform to Encodable.
The request body type and all nested types must be annotated with @Serializable from
kotlinx.serialization.
import io.privy.wallet.walletApi.WalletApiPayload
import kotlinx.serialization.Serializable
// Define data classes for the request body (must be @Serializable)
@Serializable
data class PersonalSignParams(
val message: String,
val encoding: String
)
@Serializable
data class PersonalSignRpcRequest(
val method: String,
val params: PersonalSignParams
)
// Create the RPC request body
val rpcRequest = PersonalSignRpcRequest(
method = "personal_sign",
params = PersonalSignParams(message = "Hello from Privy!", encoding = "utf-8")
)
// Create the signature payload using WalletApiPayload
val signaturePayload = WalletApiPayload(
version = 1,
url = "https://api.privy.io/v1/wallets/<wallet-id>/rpc",
method = "POST",
headers = mapOf("privy-app-id" to "<app-id>"),
body = rpcRequest
)
WalletApiPayload parameters
The payload version. Currently, only version 1 is supported.
The full URL of the API endpoint, including protocol and domain. Should not include a trailing
slash.
The HTTP method for the request (e.g., "POST", "PUT", "PATCH", "DELETE").
Privy-specific headers (those prefixed with privy-). This should not include authentication
headers, content-type, or trace headers. The privy-app-id header is always required.
The request body to be serialized and included in the signature. Must be annotated with
@Serializable.
In Flutter, the body is passed as a Map<String, dynamic> — no special serialization annotations are needed.import 'package:privy_flutter/privy_flutter.dart';
// Create the RPC request body as a Map
final rpcRequestBody = {
'method': 'personal_sign',
'params': {
'message': 'Hello from Privy!',
'encoding': 'utf-8',
},
};
// Create the signature payload
final signaturePayload = WalletApiPayload(
version: 1,
url: 'https://api.privy.io/v1/wallets/<wallet-id>/rpc',
method: 'POST',
headers: {'privy-app-id': '<app-id>'},
body: rpcRequestBody,
);
WalletApiPayload parameters
The payload version. Currently, only version 1 is supported.
The full URL of the API endpoint, including protocol and domain. Should not include a trailing
slash.
The HTTP method for the request (e.g., "POST", "PUT", "PATCH", "DELETE").
Privy-specific headers (those prefixed with privy-). This should not include authentication
headers, content-type, or trace headers. The privy-app-id header is always required.
body
Map<String, dynamic>
required
The request body as a JSON-serializable map. Serialized automatically before signing.
In Unity, the body is passed as an object — any JSON-serializable type works (Newtonsoft.Json handles serialization).using System.Collections.Generic;
using Privy.Wallets;
// Create the signature payload
var signaturePayload = new WalletApiPayload
{
Version = 1,
Url = "https://api.privy.io/v1/wallets/<wallet-id>/rpc",
Method = "POST",
Headers = new Dictionary<string, string> { { "privy-app-id", "<app-id>" } },
Body = new
{
method = "personal_sign",
@params = new { message = "Hello from Privy!", encoding = "utf-8" }
}
};
WalletApiPayload parameters
The payload version. Currently, only version 1 is supported.
The full URL of the API endpoint, including protocol and domain. Should not include a trailing
slash.
The HTTP method for the request (e.g., "POST", "PUT", "PATCH", "DELETE").
Privy-specific headers (those prefixed with privy-). This should not include authentication
headers, content-type, or trace headers. The privy-app-id header is always required.
The request body. Must be JSON-serializable via Newtonsoft.Json.
2. Sign your request
Next, use the SDK’s generateAuthorizationSignature method to sign the request. Pass the payload from step (1) as a parameter to this method.
The method will sign the request with the current authenticated user’s signing key, and return the base64-encoded signature.
React
React Native
Swift
Android
Flutter
Unity
import {useAuthorizationSignature} from '@privy-io/react-auth';
const {generateAuthorizationSignature} = useAuthorizationSignature();
// Sign the request using the current authenticated user's signing key.
// The `signaturePayload` here refers to the JSON payload constructed in step (1).
const authorizationSignature = await generateAuthorizationSignature(signaturePayload);
import {useAuthorizationSignature} from '@privy-io/expo';
const {generateAuthorizationSignature} = useAuthorizationSignature();
// Sign the request using the current authenticated user's signing key.
// The `signaturePayload` here refers to the JSON payload constructed in step (1).
const authorizationSignature = await generateAuthorizationSignature(signaturePayload);
Use the generateAuthorizationSignature method on the PrivyUser object to sign the request.Usage
// Get the authenticated user
guard let user = try await privy.getUser() else {
// User is not authenticated
return
}
// Sign the request using the payload from step (1)
do {
let authorizationSignature = try await user.generateAuthorizationSignature(payload: signaturePayload)
// Use signature in API request headers as 'privy-authorization-signature'
} catch {
// Handle error
}
Returns
The cryptographic signature as a base64-encoded String that can be included in Privy API
requests as the privy-authorization-signature header.
Use the generateAuthorizationSignature method on the PrivyUser object to sign the request.Usage
val user = privy.getUser() ?: return
// Sign the request using the payload from step (1)
user.generateAuthorizationSignature(signaturePayload)
.onSuccess { authorizationSignature ->
// Use signature in API request headers as 'privy-authorization-signature'
}
.onFailure { error ->
// Handle error
}
Returns
The cryptographic signature as a base64-encoded String that can be included in Privy API
requests as the privy-authorization-signature header.
Use the generateAuthorizationSignature method on the PrivyUser object to sign the request.Usage
final user = await privy.getUser();
if (user == null) return;
// Sign the request using the payload from step (1)
final result = await user.generateAuthorizationSignature(signaturePayload);
result.fold(
onSuccess: (signature) {
// Use signature in API request headers as 'privy-authorization-signature'
print('Signature: $signature');
},
onFailure: (error) {
// Handle error
print('Error: $error');
},
);
Returns
signature
String (inside Result<String>)
The cryptographic signature as a base64-encoded String that can be included in Privy API
requests as the privy-authorization-signature header.
Use the GenerateAuthorizationSignature method on the IPrivyUser object to sign the request.Usage
using Privy.Core;
using Privy.Auth.Models;
IPrivyUser user = await PrivyManager.Instance.GetUser();
// Sign the request using the payload from step (1)
string signature = await user.GenerateAuthorizationSignature(signaturePayload);
// Use signature in API request headers as 'privy-authorization-signature'
Returns
The cryptographic signature as a base64-encoded string that can be included in Privy API
requests as the privy-authorization-signature header.
3. Send the request and signature to your backend
Next, make a request from your frontend to your backend including the request you intend to make to the Privy API and the corresponding signature from step (2). Your backend will proxy this request to the Privy API.
4. Send the request to the Privy API
Finally, make your request to the Privy API and include the signature in the privy-authorization-signature header for your request. As an example, in NodeJS, you can make the request like so:
fetch('https://api.privy.io/v1/wallets/<wallet-id>/rpc', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${btoa('<app-id>:<app-secret>')}`,
'privy-app-id': '<app-id>',
'privy-authorization-signature': '<base64-encoded-signature>'
},
body: JSON.stringify({
method: 'personal_sign',
params: {
message: 'Hello from Privy!',
encoding: 'utf-8'
}
})
})
.then((res) => console.log(res))
.catch((err) => console.error(err));
Server-side SDKs
Privy’s server SDKs offer two utilities for signing requests:
- Formatting requests for authorization signatures. This accepts your desired request to the Privy API and formats it into the required signature payload to be signed.
- This utility is particularly helpful if your application signs requests via a separate service, e.g. an isolated KMS. Your primary server can format your request and generate the signature payload and call out to your signing service with the payload.
- Generating authorization signatures. This accepts a formatted signature payload and signs it with your provided signing key.
- This utility is particularly useful within a specific signing service. Within your signing service, you can import this function and use it to sign requests, and return the signature to your primary service.
Both the formatting and signing functions of Privy’s SDKs require a JSON input with the following fields:
| Field | Type | Description | | | |
|---|
version | 1 | Authorization 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. | | | |
url | string | The full URL for the request. Should not include a trailing slash. | | | |
body | JSON | JSON body for the request. | | | |
headers | JSON | JSON 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'] | string | Privy app ID header (required). | | | |
headers['privy-idempotency-key'] | string | Privy idempotency key header (optional). If the request does not contain an idempotency key, leave this field out of the payload. | | | |
headers['privy-request-expiry'] | string | Privy request expiry header (optional). If the request does not contain an expiry header, leave this field out of the payload. | | | |
Use the SDK’s formatting function to generate your signature payload. As a parameter to this function, pass the JSON object as defined above.
import {
formatRequestForAuthorizationSignature,
type WalletApiRequestSignatureInput
} from '@privy-io/node';
// Replace this with your desired request to the Privy API, including
// url, method, headers, and body.
const input: WalletApiRequestSignatureInput = {
version: 1,
url: 'https://api.privy.io/v1/wallets/<insert-wallet-id>/rpc',
method: 'POST',
headers: {
'privy-app-id': '<insert-app-id>'
},
body: {
method: 'personal_sign',
params: {
message: 'Hello from Privy!',
encoding: 'utf-8'
}
}
};
const serializedPayload = formatRequestForAuthorizationSignature(input);
You can then take the returned serialized payload and call out to a signing service to generate a P256 signature over the payload.
Signing requests
To directly produce a signature over a request, use the SDK’s generate authorization signature method. As a parameter to this method, pass the JSON object as defined above.
import {generateAuthorizationSignature, type WalletApiRequestSignatureInput} from '@privy-io/node';
// Replace this with your desired request to the Privy API, including
// url, method, headers, and body.
const input: WalletApiRequestSignatureInput = {
version: 1,
url: 'https://api.privy.io/v1/wallets/<insert-wallet-id>/rpc',
method: 'POST',
headers: {
'privy-app-id': '<insert-app-id>'
},
body: {
method: 'personal_sign',
params: {
message: 'Hello from Privy!',
encoding: 'utf-8'
}
}
};
// Pass your base64 encoded authorization private key as `authorizationPrivateKey`
const signature = generateAuthorizationSignature({
input,
authorizationPrivateKey: 'insert-private-key'
});
This will return a base64-encoded signature over the payload you defined. Include this signature as the privy-authorization-signature header when making the request to the Privy API.