Skip to main content
The privy-request-expiry header allows your app to set a deadline for when an API request must be processed. Privy rejects requests where the expiry timestamp has passed, helping prevent replay attacks and the delayed presentation of previously signed requests.

Required headers

When using request expiry with the REST API, include the following header with your request:
privy-request-expiry
string
A Unix timestamp in milliseconds representing the deadline by which the request must be processed (e.g., 1773679531000).

When is it necessary?

The privy-request-expiry header is optional for all endpoints where authorization signatures are accepted, but strongly recommended for:
  • Requests that include authorization signatures, to limit the window in which a signed request can be used
  • State-changing operations where delayed execution could be problematic
  • Security-sensitive operations where replay attacks are a concern

How request expiry works

1

Set the expiry

When making a request, include the privy-request-expiry header with a Unix timestamp in milliseconds representing the deadline for the request.
2

Include in signature payload

If the request requires an authorization signature, the privy-request-expiry header must be included in the signature payload under the headers field. The value signed must match the header value sent with the request.
3

Server-side validation

When Privy receives the request, it checks the privy-request-expiry value against the current server time. If the expiry timestamp is in the past, the request is rejected with a request_expired error.
The expiry value must be a Unix timestamp in milliseconds, not seconds. Using seconds will result in a timestamp that appears to be far in the past, and the request will be rejected.

Request expiry in Privy SDKs

If you are using any of Privy’s SDKs below, a default expiry of 15 minutes is set if you don’t specify one.
If you’d like to override the default value, you can configure the default expiry globally when constructing the client, or override it on a per-request basis:
import {PrivyClient} from '@privy-io/node';

// Configure a custom default expiry globally (in milliseconds)
const privy = new PrivyClient({
  appId: 'your-app-id',
  appSecret: 'your-app-secret',
  defaultRequestExpiryMs: 10 * 60 * 1000 // 10 minutes
});

const walletId = 'your-wallet-id';

// Uses the configured default expiry (10 minutes)
const responseWithDefaultExpiry = await privy.wallets().ethereum().signMessage(walletId, {
  message: 'Hello, world!'
});

// Override the expiry for a specific request
const responseWith5MinExpiry = await privy
  .wallets()
  .ethereum()
  .signMessage(walletId, {
    message: 'Hello, world!',
    request_expiry: privy.getRequestExpiry(5 * 60 * 1000) // 5 minutes
  });

Including in authorization signatures

When a request includes both a privy-request-expiry header and an authorization signature, the expiry must be included in the signature payload. This ensures that the expiry cannot be tampered with after signing.
{
  "version": 1,
  "method": "POST",
  "url": "https://api.privy.io/api/v1/wallets/<wallet_id>/rpc",
  "body": {
    "method": "personal_sign",
    "params": {
      "message": "Hello, world!"
    }
  },
  "headers": {
    "privy-app-id": "insert-your-app-id",
    "privy-request-expiry": "1773679531000"
  }
}
See authorization signatures for the full signature payload specification.

Error handling

If a request is received after its expiry timestamp, Privy returns a request_expired error. See the API error codes page for details and troubleshooting steps.