Skip to main content
Request a cryptographic signature from a user’s wallet without broadcasting a transaction. Signatures prove the user controls the wallet and let your app verify offchain actions like Sign-In With Ethereum (SIWE) authentication, EIP-712 typed-data approvals (Permit2, DEX orders, marketplace listings), and arbitrary text messages signed with EIP-191. Signing is offchain and doesn’t cost gas. For onchain transactions, use Transfer tokens instead.

Prerequisites

Before you begin, ensure that you’ve:
  • Obtained a Circle Developer API key from the Circle Console.
  • Completed the Build a wallet app tutorial, which sets up a user-controlled wallet and stores the user’s userId.
  • Integrated a user-controlled wallet client-side SDK in your app to walk the user through the signature challenge: Web SDK, iOS SDK, Android SDK, or React Native SDK.
  • Installed the user-controlled wallet server-side SDK in your backend to create the signature challenge: Node.js or Python.

Steps

1

Acquire a session token

Request a 60-minute session token for the user.
2

Create the signature challenge

Call the SDK method that matches your signature type. Each returns a challengeId that the user authorizes in the next step.
The signing endpoints accept either walletId or walletAddress + blockchain to identify the wallet. Use whichever you have stored for the user.
Sign an arbitrary text message. Useful for SIWE authentication challenges.
Include an idempotencyKey (a UUID) on every signing call. Retrying with the same key prevents duplicate challenges. See Idempotent requests for details on idempotency key usage.
3

Have the user authorize the signature

Pass the userToken, encryptionKey, and challengeId to your client-side SDK. The SDK presents the signing details and the appropriate authorization UI:
  • Social login or email OTP: Circle displays a confirmation UI showing the message or typed data. See Confirmation UIs to customize or replace it.
  • PIN: The user enters their PIN (or uses biometrics) to authorize.
The SDK completes the challenge with Circle and returns the signed output.
4

Fetch the completed signature

Wait for the challenge to complete, then retrieve the signature. Use webhooks (push) or polling (pull) to detect when the challenge reaches a terminal status: COMPLETED, FAILED, or EXPIRED.
Subscribe to user challenge notifications and listen for the event matching your challengeId. The notification includes the challenge status, type (SIGN_MESSAGE, SIGN_TYPEDDATA, or SIGN_TRANSACTION), and the signed output.
Webhook notification
For webhook setup, see Webhooks.
For a full list of possible statuses, see Asynchronous States and Statuses.
5

Verify the signature in your backend

Verify the signature against the original message and the expected wallet address. For EIP-191 verification, use a library like ethers or viem on your backend:
For EIP-712, use the typed-data variant (verifyTypedData in ethers, verifyTypedData in viem).

Error handling

Handle these common failure cases when integrating signature requests:
  • Expired session token (error code 155104): The userToken expires after 60 minutes. Request a new session token and retry.
  • Invalid typed data: Malformed EIP-712 structures fail before reaching the user. Validate the domain, types, and message against the EIP-712 spec before initiating the challenge.
  • User declines or fails to authorize: If the user cancels or enters an incorrect PIN, the signature isn’t generated. Surface the cancellation and let them retry.
  • Signature verification fails in your backend: If the recovered address doesn’t match the wallet, the user likely signed a different message than what you’re verifying. Make sure the message your backend verifies matches exactly what was signed, including whitespace and encoding.
For a complete error code reference, see Wallets error codes.