Polymarket Session Keys: How to Let a Bot Trade Without Your Key

What a Polymarket session key can and cannot do, who can use one, how to set it up in TypeScript or Python, and the traps we hit putting it into a live app.

By
Whalidate Research
Published
Reading time
12 min read

A Polymarket session key is a second signing key that the owner of a Polymarket account authorizes to trade for that account. Under Polymarket's rules it can place and cancel orders and cannot withdraw. A key that may trade for 180 days may also trade badly, and only a revocation signed by the owner ends it sooner. Session keys are in beta: this guide was checked against Polymarket's documentation and SDK source on 1 November 2026, so re-read the documentation before you build on it.

What is a Polymarket session key?

A separate signer that the owner of a Deposit Wallet permits to trade for that wallet, so a bot can trade while the owner's key stays with the owner.

A Polymarket account is a smart-contract wallet on Polygon, controlled by an owner key. Every account wallet deployed on or after 4 May 2026 is a Deposit Wallet, Polymarket's current type, which keeps a list of session signers: extra keys the owner has permitted to trade, each with an expiry.

For a trader, a session key is a power of attorney for trades only, and it runs out on a fixed date.

For a developer, it separates three secrets:

Secret What it does Where it lives
The owner's private key Signs in to Polymarket and signs the permission; can do anything with the account With the owner, in a wallet app such as MetaMask or Rabby
The Builder API key Identifies the app that submits the permission to Polymarket On the app's server, outside the trading process
The session key Signs orders In the trading process, and nowhere else

The bot needs only the third. NautilusTrader's Polymarket integration states the same rule: owner and Builder credentials stay outside the trading runtime.

What can a session key do, and what can't it?

Place and cancel orders on the venues it was scoped to. Under Polymarket's rules it cannot withdraw, see other signers' orders or extend its own term.

Action Session key
Place orders on the order book (CLOB) Yes, with scope CLOB or ALL
Cancel orders Yes, its own
Trade Combos through RFQs The scope COMBOSRFQ exists, but both official SDKs currently refuse Combos with a session key
Trade on venues Polymarket adds later Only with scope ALL
Withdraw or transfer funds No, by Polymarket's rule
See the owner's orders, or another session key's No. It gets only the orders, trades and notifications of its own activity
Extend or renew itself No. A new term needs a new permission, signed by the owner

The scope is set when the key is authorized. ALL is wider and covers CLOB, Combos and any venue Polymarket adds later; CLOB is narrower. Pick by the job. Both official SDKs send ALL unless you pass scopes, and ALL must stand alone.

The owner cannot fetch the orders its session keys submitted either, so an app that trades through a key keeps its own record of them.

Why not just give a bot your private key?

Because a leaked owner key loses the account, while a leaked session key, under Polymarket's rules, can trade until it is revoked or expires but cannot withdraw.

The owner's private key can do everything the account can, withdrawals and the management of session keys included. If a bot holding it is compromised, the money can be withdrawn, and a transfer on the blockchain cannot be reversed. With a session key, the owner's key signs the permission once, in an administration step, and is put away. The Builder key submits that permission from the app's server and never reaches the bot either.

On 29 October 2026, a script that polymarket.com loaded from an outside provider was replaced with code that drained some signed-in users (what happened). It ran in the browser tab with the powers of the user's own session, so session keys change nothing about an attack of that kind. The incident does show the question to ask before any program gets access to an account: which powers, and for how long.

What happens if a session key leaks?

Whoever holds it can trade with the whole balance until the key is revoked or expires, up to 180 days. The plan is a revocation signed by the owner, and it should exist before the key does.

Polymarket's documentation states the rule for withdrawals: a session key cannot withdraw funds from the Deposit Wallet it is authorized for.

"Cannot withdraw" is not "cannot lose". A key that can trade can trade badly on purpose: sell your positions at any price, or buy shares that are about to be worth nothing from a seller in on it. The permission carries no amount limit. In our experience every budget or loss limit is enforced by the app that holds the key, not by Polymarket, so a key that has left the app has left its limits behind. It stays valid for up to 180 days; a leak on day ten leaves 170 days of trading access.

The plan, in order:

  1. The owner signs a revocation and an app with a Builder API key submits it: revokeSessionKey() in the SDK, or the relayer's revocations endpoint. Decide in advance which integration will submit it.
  2. Revocation stops the key and cancels its open orders, but it completes only once those orders are cancelled and the on-chain transaction is confirmed, which can take several minutes after the call returns. On the API route, poll until the relayer reports STATE_CONFIRMED.
  3. Positions stay in the account: they belong to the wallet, not to the key. Any stop-loss or take-profit the bot was running stops with the key, so check the open positions yourself.
  4. If the bot is to continue, it gets a new key with a new permission.

A key that sat in version control, in a chat or in an old backup is leaked, whether or not anyone has used it.

Who can use session keys?

Accounts on a Deposit Wallet, through an app or code that holds a Builder API key, while the feature is in beta.

  • Session keys work only with Deposit Wallets, the type of every account wallet deployed on or after 4 May 2026. Older accounts are on a Proxy Wallet (e-mail or Google sign-in) or a Safe Wallet (sign-in with a wallet app such as MetaMask). Polymarket plans a migration flow for them; it is not available yet.
  • Authorizing a key requires a Builder API key. Any polymarket.com account can create one under Settings → Builders, and during the beta Polymarket enables session keys for it on request: write to builder@polymarket.com or ask in the Builder Program's Telegram group. So the authorization comes from an app, or your own code, that holds such a key.
  • Josh Stevens (VP, Engineering DeFi at Polymarket) announced the beta on 27 August 2026: a few builders testing, every builder soon, Polymarket's own interface later. The documentation still carries the beta note on 1 November 2026.

How do you integrate them?

Six steps: generate a key, store it, authorize it with the owner's client, confirm it is listed, trade with it, revoke it when done.

The samples are copied from Polymarket's documentation and use the official SDKs: @polymarket/client for TypeScript, polymarket-client (imported as polymarket) for Python. Each step links its section of the documentation.

Step 1. Generate a key. A fresh EVM keypair: the session key is an ordinary externally owned account.

import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";

const sessionKeyPrivateKey = generatePrivateKey();
const { address: sessionKeyAddress } =
  privateKeyToAccount(sessionKeyPrivateKey);
from eth_account import Account

session_key = Account.create()
session_key_private_key = "0x" + session_key.key.hex().removeprefix("0x")
session_key_address = session_key.address

Source: Authorize a Session Key.

Step 2. Store it. The private key goes into a secrets manager, and only its address goes to the next step. It never goes into a log, a repository or a message.

Step 3. Authorize it. The owner's SecureClient, created with the owner's private key and the Builder API credentials, calls authorizeSessionKey() with the session key's address. The SDK also confirms the key with the owner's CLOB credentials.

import { createSecureClient } from "@polymarket/client";
import { builderApiKey } from "@polymarket/client/node";
import { privateKey } from "@polymarket/client/viem";

const secureClient = await createSecureClient({
  signer: privateKey(process.env.POLYMARKET_PRIVATE_KEY),
  wallet: process.env.POLYMARKET_DEPOSIT_WALLET!,
  apiKey: builderApiKey({
    key: process.env.POLYMARKET_BUILDER_API_KEY!,
    secret: process.env.POLYMARKET_BUILDER_SECRET!,
    passphrase: process.env.POLYMARKET_BUILDER_PASSPHRASE!,
  }),
});

const authorization = await secureClient.authorizeSessionKey({
  address: sessionKeyAddress,
});
import os

from polymarket import AsyncSecureClient, BuilderApiKey

secure_client = await AsyncSecureClient.create(
    private_key=os.environ["POLYMARKET_PRIVATE_KEY"],
    wallet=os.environ["POLYMARKET_DEPOSIT_WALLET"],
    api_key=BuilderApiKey(
        key=os.environ["POLYMARKET_BUILDER_API_KEY"],
        secret=os.environ["POLYMARKET_BUILDER_SECRET"],
        passphrase=os.environ["POLYMARKET_BUILDER_PASSPHRASE"],
    ),
)

authorization = await secure_client.authorize_session_key(
    address=session_key_address,
)

To limit the venues, pass scopes (TypeScript shown):

import { SessionKeyKnownScope } from "@polymarket/client";

const scopedAuthorization = await secureClient.authorizeSessionKey({
  address: sessionKeyAddress,
  scopes: [SessionKeyKnownScope.CLOB, SessionKeyKnownScope.COMBOSRFQ],
});

Source: Authorize a Session Key; the SDK's session-keys decorator and the Python integration test.

The SDK path needs the owner's private key in the code. When the owner is a user with a wallet app, take the API route the documentation also describes: the app builds the EIP-712 Batch typed data (domain DepositWallet, version 1, chain 137) around one authorizeSessionSigner(sessionSigner, validUntil) call with the wallet's current nonce, the user signs it in the wallet app, and the app submits the signature with Builder authentication and polls until STATE_CONFIRMED. This is the route we took.

Step 4. Confirm it is listed. The authorization is usable only once the signer appears in the wallet's list, with the expected scopes and expiry.

const sessionKeys = await secureClient.fetchSessionKeys();

// sessionKeys: SessionKey[]
session_keys = await secure_client.fetch_session_keys()

Each entry has the key's address, its scopes and its expiry (validUntil in TypeScript, valid_until in Python). Source: Fetch Session Keys.

Step 5. Trade with it. The trading process creates its own client from the session key and the Deposit Wallet address and places orders as usual.

import { createSecureClient } from "@polymarket/client";
import { privateKey } from "@polymarket/client/viem";

const sessionPrivateKey = process.env.POLYMARKET_SESSION_PRIVATE_KEY!;
const depositWallet = process.env.POLYMARKET_DEPOSIT_WALLET!;

const sessionClient = await createSecureClient({
  signer: privateKey(sessionPrivateKey),
  wallet: depositWallet,
});
import { OrderSide } from "@polymarket/client";

const response = await sessionClient.placeLimitOrder({
  assetId,
  side: OrderSide.BUY,
  price: "0.52",
  size: "10",
});
import os

from polymarket import AsyncSecureClient

session_client = await AsyncSecureClient.create(
    private_key=os.environ["POLYMARKET_SESSION_PRIVATE_KEY"],
    wallet=os.environ["POLYMARKET_DEPOSIT_WALLET"],
)
response = await session_client.place_limit_order(
    asset_id=asset_id,
    side="BUY",
    price="0.52",
    size="10",
)

Source: Place an Order; the SDK's own session-keys integration test does the same.

Step 6. Revoke it. On the owner's client, with the session key's address. The call returns once the key is no longer active in the wallet registry; the order cancellations and the on-chain transaction finish afterwards.

await secureClient.revokeSessionKey({
  address: sessionKeyAddress,
});
await secure_client.revoke_session_key(
    address=session_key_address,
)

Source: Revoke a Session Key.

What trips people up?

Most of it comes before the signature: the wallet type, the owner, the approvals and the expiry. The rest is bookkeeping after it.

Trap What to do Source
The account is not on a Deposit Wallet, or the address is not its owner Check before asking for a signature: contract code exists at the wallet's address, and its owner() is the address that will sign. Our first three connection attempts failed here Our path
The session key cannot grant the exchange approvals (allowances) the wallet needs to trade Check them before connecting. An account that has traded on polymarket.com has them; a new one may not, and only the owner can grant them Our path
validUntil is rejected The documentation's text asks for the current whole Unix time plus 180 days on a raw relayer call; its own example and both SDKs send 4,315 hours, five hours less. Compute it as the documentation's example does, or let the SDK set it, and read the real expiry from fetchSessionKeys() The docs, the SDK source
The owner did something in the account between the request and the signature The wallet's nonce has moved and the signature is stale: rebuild the request and ask for a new signature. The relayer also wants at least 10 seconds of the deadline left Our path, the docs
The key's orders are rejected right after authorization Parts of Polymarket's registry can keep the old signer set for about 65 seconds. Retry for up to 75 seconds, as Polymarket's own integration test does The SDK source
The key sees only its own orders, and the owner cannot see the key's Keep your own ledger of every order each key places, with its order id The docs
The owner trades in the same account Positions share one balance. Count as yours only the fills of your own orders, and never sell shares you did not buy Our path
Renewal There is no extension. Authorize a new key rather than the old one again, so a stray copy of the old key dies when it expires Our path
Revocation cancels the key's open orders, but positions remain Decide beforehand who manages the positions after a revocation or an expiry Our explanation

A checklist for coding agents

Paste this into the instructions of whatever writes the integration.

  • Never put the owner's private key or the Builder API key into the trading process. They belong to the step that authorizes or revokes a session key.
  • Keep the session key in a secrets manager. Never write it to logs, config files, tickets or chat.
  • Before asking for a signature, check that the wallet is a Deposit Wallet and that its owner() is the address that will sign.
  • Check the trading approvals before connecting; the session key cannot grant them.
  • Let the SDK set the expiry; on a raw relayer call, compute validUntil as the documentation's example does. Read the real expiry from fetchSessionKeys().
  • Trade only after the signer appears in the session-signers list, and retry rejected orders for up to 75 seconds after that. Treat STATE_FAILED and STATE_INVALID as final.
  • Record every order the key places. The owner cannot query them.
  • Put amount and loss limits in your code. The permission has none.
  • Write the revocation plan before the key exists: who signs, which Builder key submits, who looks after open positions.
  • Re-read docs.polymarket.com/trading/session-keys before shipping. The feature is in beta.

How Whalidate uses session keys

You connect your own Polymarket account with one signature, and your strategy trades there. Deposits and withdrawals stay on Polymarket.

We built session keys into Whalidate. When you take a strategy live, you choose where it trades: a trading wallet the service creates for you, or your own Polymarket account. The two sit side by side, as our Terms describe. With your own account, you sign one permission in your wallet app (MetaMask, Rabby and the like), and your money stays in that account: deposits and withdrawals happen on Polymarket. Under Polymarket's rules the key cannot withdraw.

The permission lasts up to 180 days. You can end it from the app, and ending it means signing the revocation, because that is what stops the key on Polymarket's side. The live budget and loss limits you set are rules Whalidate applies to your strategy. The permission itself has no amount limit, and your own trades in the same account are outside those rules. The Risk Disclosure says what can go wrong.

Our connection asks for the scope ALL, the wider of the two described above.

We don't connect accounts that sign in to Polymarket with e-mail or Google: they have no wallet app to sign the permission with. The last question below has the details.

Questions people ask

Can a Polymarket session key withdraw money?

Not under Polymarket's rules: its documentation says a session key cannot withdraw funds from the Deposit Wallet it is authorized for. It can place and cancel orders, so a leaked key can still lose money by trading badly until it is revoked or expires. Deposits and withdrawals stay with the account's owner, on Polymarket.

How long does a Polymarket session key last?

Up to 180 days from the authorization, by Polymarket's rule; the official SDKs set 4,315 hours, five hours short of that. A shorter term cannot be configured, and there is no extension: a new permission needs a new signature from the owner. The only way to end access earlier is to revoke the key. The feature is in beta, so check the documentation for the current term.

How do you revoke a Polymarket session key?

The owner of the Deposit Wallet signs a revocation and an app with a Builder API key submits it to Polymarket's relayer; in the SDK that is revokeSessionKey() on the owner's SecureClient. Revocation stops the key from trading and cancels its open orders. Polymarket says completion can take several minutes. Positions stay in the account.

Do session keys work with an e-mail or Google sign-in?

Older e-mail and Google accounts are on a Proxy Wallet, and Polymarket's documentation says session keys work only with Deposit Wallets. Newer ones have a Deposit Wallet, but the permission is signed by the wallet's owner in a wallet app such as MetaMask or Rabby, and accounts that sign in with e-mail or Google don't use one. So Whalidate does not connect them. That is a limit of our integration; the documentation says nothing either way.

Sources

Keep reading