Overview
The WebSocket API provides real-time updates for transaction status. Merchants can connect to receive instant notifications when proposals are settled.
WebSocket connections do not require authentication but are scoped to a specific transaction ID.
Connection
WebSocket URL
wss://<hostname>/ws/transactions/:txId
For local development:
ws://localhost:8000/ws/transactions/:txId
Path Parameters
Transaction ID (UUID) to monitor
All messages are JSON-encoded.
Incoming Messages
Messages sent from the server to the client.
Status Message
Current status: pending, settled, expired, or cancelled
Sui transaction digest (if settled)
Example :
{
"type" : "status" ,
"transactionId" : "550e8400-e29b-41d4-a716-446655440000" ,
"status" : "pending" ,
"suiTxDigest" : null
}
Settlement Message
Message type (settlement)
The buyer’s stealth address is NOT included in settlement messages to preserve privacy.
Example :
{
"type" : "settlement" ,
"transactionId" : "550e8400-e29b-41d4-a716-446655440000" ,
"status" : "settled" ,
"suiTxDigest" : "9x4Kb2FgH8..."
}
Error Message
Example :
{
"type" : "error" ,
"message" : "Transaction not found"
}
Outgoing Messages
Clients do not send messages to the server. The WebSocket is read-only.
Usage Examples
JavaScript/TypeScript
Basic Connection
React Hook
Auto-Reconnect
const txId = '550e8400-e29b-41d4-a716-446655440000' ;
const ws = new WebSocket ( `wss://api.identipay.com/ws/transactions/ ${ txId } ` );
ws . onopen = () => {
console . log ( 'Connected to WebSocket' );
};
ws . onmessage = ( event ) => {
const message = JSON . parse ( event . data );
if ( message . type === 'status' ) {
console . log ( 'Current status:' , message . status );
} else if ( message . type === 'settlement' ) {
console . log ( 'Payment settled!' , message . suiTxDigest );
// Update UI, notify merchant, etc.
} else if ( message . type === 'error' ) {
console . error ( 'Error:' , message . message );
}
};
ws . onerror = ( error ) => {
console . error ( 'WebSocket error:' , error );
};
ws . onclose = () => {
console . log ( 'WebSocket closed' );
};
Python
import asyncio
import json
import websockets
async def monitor_transaction ( tx_id : str ):
uri = f "wss://api.identipay.com/ws/transactions/ { tx_id } "
async with websockets.connect(uri) as websocket:
print ( f "Connected to { uri } " )
async for message in websocket:
data = json.loads(message)
if data[ 'type' ] == 'status' :
print ( f "Status: { data[ 'status' ] } " )
elif data[ 'type' ] == 'settlement' :
print ( f "Settled! Digest: { data[ 'suiTxDigest' ] } " )
break # Close after settlement
elif data[ 'type' ] == 'error' :
print ( f "Error: { data[ 'message' ] } " )
break
# Run
asyncio.run(monitor_transaction( '550e8400-e29b-41d4-a716-446655440000' ))
Implementation Details
Server-Side Architecture
The WebSocket handler (see ws/status.ts:17-42) maintains a map of connections:
// Map of transactionId -> set of WebSocket connections
const connections = new Map < string , Set < WsConnection >>();
export function handleWsConnection (
txId : string ,
ws : WsConnection ,
db : Db ,
onCleanup ?: () => void
) {
if ( ! connections . has ( txId )) {
connections . set ( txId , new Set ());
}
connections . get ( txId ) ! . add ( ws );
// Send current status on connect
sendCurrentStatus ( txId , ws , db );
// Return cleanup function
return () => {
const set = connections . get ( txId );
if ( set ) {
set . delete ( ws );
if ( set . size === 0 ) {
connections . delete ( txId );
}
}
onCleanup ?.();
};
}
Settlement Broadcasting
When the settlement indexer detects a payment (see ws/status.ts:81-103):
export function pushSettlementUpdate (
txId : string ,
status : string ,
suiTxDigest : string
) {
const set = connections . get ( txId );
if ( ! set ) return ;
const message = JSON . stringify ({
type: 'settlement' ,
transactionId: txId ,
status ,
suiTxDigest
});
for ( const ws of set ) {
try {
ws . send ( message );
} catch {
set . delete ( ws );
}
}
}
Initial Status
On connection, the server immediately sends the current status:
async function sendCurrentStatus ( txId : string , ws : WsConnection , db : Db ) {
const [ proposal ] = await db
. select ()
. from ( proposals )
. where ( eq ( proposals . transactionId , txId ));
if ( proposal ) {
ws . send ( JSON . stringify ({
type: 'status' ,
transactionId: proposal . transactionId ,
status: proposal . status ,
suiTxDigest: proposal . suiTxDigest
}));
} else {
ws . send ( JSON . stringify ({
type: 'error' ,
message: 'Transaction not found'
}));
}
}
Connection Lifecycle
Connect : Client opens WebSocket connection with transaction ID
Initial Status : Server sends current proposal status
Monitor : Server monitors for settlement events
Broadcast : When settled, server pushes update to all connected clients
Close : Client or server closes connection
Cleanup : Server removes connection from tracking map
Privacy Considerations
Settlement messages do NOT include the buyer’s stealth address.
Merchants receive:
Transaction ID
Settlement status
Sui transaction digest
Merchants do NOT receive:
Buyer’s wallet address
Buyer’s stealth address
Buyer’s identity information
This preserves buyer privacy while allowing merchants to confirm payment.
Connection Limits
Currently, there are no enforced connection limits. Future versions may limit:
Connections per transaction
Connections per IP
Total concurrent connections
Monitoring
The server tracks active connections:
export function getActiveConnectionCount () : number {
let count = 0 ;
for ( const set of connections . values ()) {
count += set . size ;
}
return count ;
}
This can be used for monitoring and alerting.
Best Practices
Connect to WebSocket only after creating/resolving a proposal
Implement auto-reconnect with exponential backoff
Close connection after receiving settlement notification
Fall back to polling /transactions/:txId/status if WebSocket fails
WebSocket connections consume server resources. Close unused connections.
Fallback: Polling
If WebSocket is unavailable, poll the status endpoint:
async function pollTransactionStatus (
txId : string ,
apiKey : string ,
onUpdate : ( status : any ) => void
) {
const intervalId = setInterval ( async () => {
try {
const response = await fetch (
`https://api.identipay.com/api/identipay/v1/transactions/ ${ txId } /status` ,
{
headers: { 'Authorization' : `Bearer ${ apiKey } ` }
}
);
const status = await response . json ();
onUpdate ( status );
// Stop polling if settled/expired/cancelled
if ( status . status !== 'pending' ) {
clearInterval ( intervalId );
}
} catch ( error ) {
console . error ( 'Polling error:' , error );
}
}, 3000 ); // Poll every 3 seconds
return () => clearInterval ( intervalId );
}