The OpenKey widget lets you add “Connect with OpenKey” and “Sign with OpenKey” functionality to any web application. The widget runs as a popup or iframe overlay, handles the full authentication and signing flow, and returns results to your app via postMessage.
Installation
Install the OpenKey SDK:
Quick Example
import { OpenKey } from '@openkey/sdk' ;
const openkey = new OpenKey ({
appName: 'My App' ,
});
// Connect: user selects or creates a key
const { address , keyId } = await openkey . connect ();
console . log ( 'Connected:' , address );
// Sign a message
const { signature } = await openkey . signMessage ({
message: 'Hello from My App!' ,
keyId ,
});
console . log ( 'Signature:' , signature );
SDK Configuration
const openkey = new OpenKey ({
// Display name shown in the widget (default: current hostname)
appName: 'My App' ,
// OpenKey host URL (default: https://openkey.so)
host: 'https://openkey.so' ,
// Display mode: 'iframe' | 'popup' | 'redirect'
// Default: 'iframe'
mode: 'popup' ,
});
Iframe mode (default) provides a clean inline UX. The SDK automatically falls back to popup mode if the iframe is blocked by CSP. Set mode: 'popup' to always use a popup window.
Connect Flow
The connect flow lets users authenticate with OpenKey and select (or generate) an Ethereum key to share with your app.
const { address , keyId , keyType } = await openkey . connect ();
What happens when connect() is called:
The returned AuthResult contains:
Field Type Description addressstringEthereum address of the selected key keyIdstringKey identifier (use this for signing) keyType'MANAGED' | 'EXTERNAL'Whether key is TEE-managed or a linked external wallet
Link External Wallet
If the user already has an external wallet (like MetaMask), they can link it to their OpenKey account:
const { address , keyId } = await openkey . linkWallet ();
This opens the widget with the wallet linking flow instead of key selection.
Sign Flow
Once connected, request a message signature from the user.
const { signature , address } = await openkey . signMessage ({
message: 'Please sign to verify ownership' ,
keyId: 'key-id-from-connect' ,
});
The widget shows the message to the user for confirmation before signing.
Sign Typed Data (EIP-712)
For structured data signing:
const { signature , address } = await openkey . signTypedData ({
domain: {
name: 'My App' ,
version: '1' ,
chainId: 1 ,
},
types: {
Action: [
{ name: 'description' , type: 'string' },
{ name: 'timestamp' , type: 'uint256' },
],
},
primaryType: 'Action' ,
message: {
description: 'Transfer 10 USDC' ,
timestamp: Math . floor ( Date . now () / 1000 ),
},
keyId: 'key-id-from-connect' ,
});
Error Handling
All SDK methods throw an OpenKeyError on failure:
try {
const result = await openkey . connect ();
} catch ( error ) {
switch ( error . code ) {
case 'USER_CANCELLED' :
console . log ( 'User closed the widget' );
break ;
case 'POPUP_BLOCKED' :
console . log ( 'Popup was blocked - prompt user to allow popups' );
break ;
case 'TIMEOUT' :
console . log ( 'Flow timed out (5 minute limit)' );
break ;
case 'NO_KEY' :
console . log ( 'User has no keys' );
break ;
case 'UNAUTHORIZED' :
console . log ( 'Auth failed' );
break ;
}
}
Error Code Description USER_CANCELLEDUser closed the popup or clicked cancel POPUP_BLOCKEDBrowser blocked the popup window TIMEOUTFlow exceeded 5 minutes without completing NO_KEYUser has no Ethereum keys UNAUTHORIZEDAuthentication failed STATE_MISMATCHOAuth state mismatch (possible CSRF)
Message Protocol
If you need to integrate without the SDK (for example, in a framework that does not support npm packages), you can communicate with the widget directly using postMessage.
Open the widget URL in a popup or iframe:
// Connect flow
const popup = window . open (
'https://openkey.so/widget/connect?origin=' + encodeURIComponent ( window . location . origin ),
'openkey' ,
'width=400,height=600,popup=true'
);
// Sign flow
const popup = window . open (
'https://openkey.so/widget/sign?origin=' + encodeURIComponent ( window . location . origin ),
'openkey' ,
'width=400,height=600,popup=true'
);
// Sign typed data flow
const popup = window . open (
'https://openkey.so/widget/sign-typed-data?origin=' + encodeURIComponent ( window . location . origin ),
'openkey' ,
'width=400,height=600,popup=true'
);
Message Types
The widget communicates via these message types:
Widget to Parent:
Type Direction Fields Description openkey:readyWidget to Parent — Widget loaded, send your request openkey:auth:responseWidget to Parent success, address, keyId or errorConnect result openkey:sign:responseWidget to Parent success, signature, address or errorSign result openkey:signTypedData:responseWidget to Parent success, signature, address or errorTyped data sign result openkey:closeWidget to Parent — User closed the widget
Parent to Widget:
Type Direction Fields Description openkey:auth:requestParent to Widget appNameRequest key selection openkey:sign:requestParent to Widget message, keyId?Request message signature openkey:signTypedData:requestParent to Widget data (EIP-712 object)Request typed data signature
Manual Integration Example
const OPENKEY_ORIGIN = 'https://openkey.so' ;
function connectWithOpenKey () {
return new Promise (( resolve , reject ) => {
const popup = window . open (
` ${ OPENKEY_ORIGIN } /widget/connect?origin= ${ encodeURIComponent ( location . origin ) } ` ,
'openkey' ,
'width=400,height=600,popup=true'
);
function handleMessage ( event ) {
if ( event . origin !== OPENKEY_ORIGIN ) return ;
if ( event . data . type === 'openkey:ready' ) {
popup . postMessage (
{ type: 'openkey:auth:request' , appName: 'My App' },
OPENKEY_ORIGIN
);
}
if ( event . data . type === 'openkey:auth:response' ) {
window . removeEventListener ( 'message' , handleMessage );
popup . close ();
if ( event . data . success ) {
resolve ({ address: event . data . address , keyId: event . data . keyId });
} else {
reject ( event . data . error );
}
}
}
window . addEventListener ( 'message' , handleMessage );
});
}
Check Connection Status
Check if the user already has an active OpenKey session:
const connected = await openkey . isConnected ();
if ( connected ) {
// User is already authenticated with OpenKey
}
Cleanup
When you are done, call disconnect() to close any open popups or iframes:
Next Steps
OAuth Provider Use OAuth 2.1 for server-side token-based integration.
TinyCloud Integration Use OpenKey as the wallet provider for TinyCloud SDK.