After announcing a transaction to the Symbol network, it remains unconfirmed until it is included in a block.
Monitoring status changes is essential for building responsive applications that can react to transaction
confirmation or failure.
This tutorial shows how to monitor a transaction's status as it moves from unconfirmed to confirmed.
Polling is not recommended for production
This tutorial uses polling to check the transaction status.
Polling is used here for illustration purposes, but it is not the recommended approach for production applications.
WebSockets provide a more responsive solution without the overhead of
repeated API calls.
importjsonimportosimporttimeimporturllib.request# ConfigurationNODE_URL=os.getenv("NODE_URL","https://reference.symboltest.net:3001")print(f'Using node {NODE_URL}')# Transaction hash to monitortransaction_hash=os.getenv("TRANSACTION_HASH","2B6D3B5232E06B9D32682F518C765301FCF9716BFA1EEEF9523653406E04C7EA")print(f"Monitoring transaction: {transaction_hash}")defwait_for_transaction_confirmation(tx_hash,max_attempts=60,wait_seconds=2):""" Poll the transaction status endpoint until the transaction is confirmed. Args: tx_hash: The hash of the transaction to monitor max_attempts: Maximum number of polling attempts for confirmation wait_seconds: Seconds to wait between attempts Returns: True if transaction was confirmed """status_path=f"/transactionStatus/{tx_hash}"print("\nWaiting for transaction confirmation")print(f"Polling {status_path}")forattemptinrange(1,max_attempts+1):try:# Query the transaction status endpointurl=f"{NODE_URL}{status_path}"withurllib.request.urlopen(url)asresponse:response_json=json.loads(response.read().decode())# Parse the responsestatus_group=response_json["group"]status_code=response_json["code"]status_hash=response_json["hash"]status_deadline=response_json["deadline"]print(f" Attempt {attempt}:")print(f" Status: {status_group}")print(f" Code: {status_code}")print(f" Hash: {status_hash}")print(f" Deadline: {status_deadline}")# Check if the transaction has been confirmedifstatus_group=="confirmed":print("\nTransaction confirmed!")returnTrue# Check if the transaction failedifstatus_group=="failed":print(f"\nTransaction failed with code: {status_code}")raiseRuntimeError(f"Transaction failed: {status_code}")excepturllib.error.HTTPErroraserr:iferr.status==404:print(f" Attempt {attempt}: Transaction status not ""yet available")else:raise# Wait before next attempt (except on last attempt)ifattempt<max_attempts:time.sleep(wait_seconds)print(f"\nTransaction not confirmed after {max_attempts} attempts")raiseRuntimeError(f"Transaction {transaction_hash} not confirmed in time")# Monitor the transaction until it's confirmedwait_for_transaction_confirmation(transaction_hash)
// ConfigurationconstNODE_URL=process.env.NODE_URL||'https://reference.symboltest.net:3001';console.log('Using node',NODE_URL);// Transaction hash to monitor.consttransactionHash=process.env.TRANSACTION_HASH||'2B6D3B5232E06B9D32682F518C765301FCF9716BFA1EEEF9523653406E04C7EA';console.log(`Monitoring transaction: ${transactionHash}`);/** * Poll the transaction status endpoint until it is confirmed. * @param {string} txHash - The hash of the transaction to monitor * @param {number} maxAttempts - Maximum number of polling attempts * for confirmation * @param {number} waitSeconds - Seconds to wait between attempts * @returns {boolean} True if transaction was confirmed */asyncfunctionwaitForTransactionConfirmation(txHash,maxAttempts=60,waitSeconds=2){conststatusPath=`/transactionStatus/${txHash}`;console.log('\nWaiting for transaction confirmation');console.log(`Polling ${statusPath}`);for(letattempt=1;attempt<=maxAttempts;attempt++){try{// Query the transaction status endpointconststatusResponse=awaitfetch(`${NODE_URL}${statusPath}`);if(!statusResponse.ok){conststatus=statusResponse.status;conststatusText=statusResponse.statusText;consterror=newError(`HTTP ${status}: ${statusText}`);error.status=statusResponse.status;throwerror;}conststatusJSON=awaitstatusResponse.json();// Parse the responseconststatusGroup=statusJSON.group;conststatusCode=statusJSON.code;conststatusHash=statusJSON.hash;conststatusDeadline=statusJSON.deadline;console.log(` Attempt ${attempt}:`);console.log(` Status: ${statusGroup}`);console.log(` Code: ${statusCode}`);console.log(` Hash: ${statusHash}`);console.log(` Deadline: ${statusDeadline}`);// Check if the transaction has been confirmedif('confirmed'===statusGroup){console.log('\nTransaction confirmed!');returntrue;}// Check if the transaction failedif('failed'===statusGroup){console.log(`\nTransaction failed with code: ${statusCode}`);thrownewError(`Transaction failed: ${statusCode}`);}}catch(error){if(404===error.status){console.log(` Attempt ${attempt}: Transaction status not yet `+'available');}else{throwerror;}}// Wait before next attempt (except on last attempt)if(attempt<maxAttempts){awaitnewPromise(resolve=>{setTimeout(resolve,waitSeconds*1000);});}}console.log(`\nTransaction not confirmed after ${maxAttempts} attempts`);thrownewError(`Transaction ${txHash} not confirmed in time`);}// Monitor the transaction until it's confirmedawaitwaitForTransactionConfirmation(transactionHash);
//JAVA 21+//DEPS org.symbol:symbol-sdk:3.3.1importjava.io.IOException;importjava.net.URI;importjava.net.http.HttpClient;importjava.net.http.HttpRequest;importjava.net.http.HttpResponse;importjava.net.http.HttpResponse.BodyHandlers;importcom.fasterxml.jackson.databind.JsonNode;importcom.fasterxml.jackson.databind.ObjectMapper;// ConfigurationpublicfinalclassMonitoringStatus{privatestaticfinalObjectMapperJSON_MAPPER=newObjectMapper();privatestaticfinalHttpClientHTTP_CLIENT=HttpClient.newHttpClient();privatestaticfinalStringNODE_URL=System.getenv().getOrDefault("NODE_URL","https://reference.symboltest.net:3001");publicstaticvoidmain(finalString[]args){try{newMonitoringStatus().run();}catch(finalExceptionex){System.out.println(null==ex.getMessage()?ex.toString():ex.getMessage());}}privatevoidrun()throwsIOException,InterruptedException{System.out.printf("Using node %s%n",NODE_URL);// Transaction hash to monitor.finalStringtransactionHash=System.getenv().getOrDefault("TRANSACTION_HASH","2B6D3B5232E06B9D32682F518C765301FCF9716BFA1EEEF95236534"+"06E04C7EA");System.out.printf("Monitoring transaction: %s%n",transactionHash);// Monitor the transaction until it's confirmedwaitForTransactionConfirmation(transactionHash,60,2);}/** * Poll the transaction status endpoint until it is confirmed. * @param txHash The hash of the transaction to monitor * @param maxAttempts Maximum number of polling attempts * for confirmation * @param waitSeconds Seconds to wait between attempts * @return True if transaction was confirmed */privatebooleanwaitForTransactionConfirmation(finalStringtxHash,finalintmaxAttempts,finalintwaitSeconds)throwsIOException,InterruptedException{finalStringstatusPath="/transactionStatus/"+txHash;System.out.println("\nWaiting for transaction confirmation");System.out.printf("Polling %s%n",statusPath);for(intattempt=1;attempt<=maxAttempts;++attempt){// Query the transaction status endpointfinalHttpRequeststatusRequest=HttpRequest.newBuilder(URI.create(NODE_URL+statusPath)).GET().build();finalHttpResponse<String>statusResponse=HTTP_CLIENT.send(statusRequest,BodyHandlers.ofString());if(404==statusResponse.statusCode()){System.out.printf(" Attempt %d: Transaction status not yet available%n",attempt);}else{if(statusResponse.statusCode()/100!=2)thrownewIOException("HTTP "+statusResponse.statusCode());finalJsonNodestatusJSON=JSON_MAPPER.readTree(statusResponse.body());// Parse the responsefinalStringstatusGroup=statusJSON.get("group").asText();finalStringstatusCode=statusJSON.get("code").asText();finalStringstatusHash=statusJSON.get("hash").asText();finalStringstatusDeadline=statusJSON.get("deadline").asText();System.out.printf(" Attempt %d:%n",attempt);System.out.printf(" Status: %s%n",statusGroup);System.out.printf(" Code: %s%n",statusCode);System.out.printf(" Hash: %s%n",statusHash);System.out.printf(" Deadline: %s%n",statusDeadline);// Check if the transaction has been confirmedif("confirmed".equals(statusGroup)){System.out.println("\nTransaction confirmed!");returntrue;}// Check if the transaction failedif("failed".equals(statusGroup)){System.out.printf("%nTransaction failed with code: %s%n",statusCode);thrownewIOException("Transaction failed: "+statusCode);}}// Wait before next attempt (except on last attempt)if(attempt<maxAttempts)Thread.sleep(waitSeconds*1000L);}System.out.printf("%nTransaction not confirmed after %d attempts%n",maxAttempts);thrownewIOException("Transaction "+txHash+" not confirmed in time");}}
To monitor a transaction, you need its hash, which is generated after signing.
The hash uniquely identifies the transaction on the Symbol network.
This tutorial uses a sample transaction hash to demonstrate the monitoring.
You can provide your own hash by setting the TRANSACTION_HASH environment variable when running the code.
In practice, you would obtain this hash immediately after signing a transaction (see the
Transfer tutorial for an example) and use it to track its status.
defwait_for_transaction_confirmation(tx_hash,max_attempts=60,wait_seconds=2):""" Poll the transaction status endpoint until the transaction is confirmed. Args: tx_hash: The hash of the transaction to monitor max_attempts: Maximum number of polling attempts for confirmation wait_seconds: Seconds to wait between attempts Returns: True if transaction was confirmed """status_path=f"/transactionStatus/{tx_hash}"print("\nWaiting for transaction confirmation")print(f"Polling {status_path}")forattemptinrange(1,max_attempts+1):try:# Query the transaction status endpointurl=f"{NODE_URL}{status_path}"withurllib.request.urlopen(url)asresponse:response_json=json.loads(response.read().decode())# Parse the responsestatus_group=response_json["group"]status_code=response_json["code"]status_hash=response_json["hash"]status_deadline=response_json["deadline"]print(f" Attempt {attempt}:")print(f" Status: {status_group}")print(f" Code: {status_code}")print(f" Hash: {status_hash}")print(f" Deadline: {status_deadline}")
/** * Poll the transaction status endpoint until it is confirmed. * @param {string} txHash - The hash of the transaction to monitor * @param {number} maxAttempts - Maximum number of polling attempts * for confirmation * @param {number} waitSeconds - Seconds to wait between attempts * @returns {boolean} True if transaction was confirmed */asyncfunctionwaitForTransactionConfirmation(txHash,maxAttempts=60,waitSeconds=2){conststatusPath=`/transactionStatus/${txHash}`;console.log('\nWaiting for transaction confirmation');console.log(`Polling ${statusPath}`);for(letattempt=1;attempt<=maxAttempts;attempt++){try{// Query the transaction status endpointconststatusResponse=awaitfetch(`${NODE_URL}${statusPath}`);if(!statusResponse.ok){conststatus=statusResponse.status;conststatusText=statusResponse.statusText;consterror=newError(`HTTP ${status}: ${statusText}`);error.status=statusResponse.status;throwerror;}conststatusJSON=awaitstatusResponse.json();// Parse the responseconststatusGroup=statusJSON.group;conststatusCode=statusJSON.code;conststatusHash=statusJSON.hash;conststatusDeadline=statusJSON.deadline;console.log(` Attempt ${attempt}:`);console.log(` Status: ${statusGroup}`);console.log(` Code: ${statusCode}`);console.log(` Hash: ${statusHash}`);console.log(` Deadline: ${statusDeadline}`);
/** * Poll the transaction status endpoint until it is confirmed. * @param txHash The hash of the transaction to monitor * @param maxAttempts Maximum number of polling attempts * for confirmation * @param waitSeconds Seconds to wait between attempts * @return True if transaction was confirmed */privatebooleanwaitForTransactionConfirmation(finalStringtxHash,finalintmaxAttempts,finalintwaitSeconds)throwsIOException,InterruptedException{finalStringstatusPath="/transactionStatus/"+txHash;System.out.println("\nWaiting for transaction confirmation");System.out.printf("Polling %s%n",statusPath);for(intattempt=1;attempt<=maxAttempts;++attempt){// Query the transaction status endpointfinalHttpRequeststatusRequest=HttpRequest.newBuilder(URI.create(NODE_URL+statusPath)).GET().build();finalHttpResponse<String>statusResponse=HTTP_CLIENT.send(statusRequest,BodyHandlers.ofString());if(404==statusResponse.statusCode()){System.out.printf(" Attempt %d: Transaction status not yet available%n",attempt);}else{if(statusResponse.statusCode()/100!=2)thrownewIOException("HTTP "+statusResponse.statusCode());finalJsonNodestatusJSON=JSON_MAPPER.readTree(statusResponse.body());// Parse the responsefinalStringstatusGroup=statusJSON.get("group").asText();finalStringstatusCode=statusJSON.get("code").asText();finalStringstatusHash=statusJSON.get("hash").asText();finalStringstatusDeadline=statusJSON.get("deadline").asText();System.out.printf(" Attempt %d:%n",attempt);System.out.printf(" Status: %s%n",statusGroup);System.out.printf(" Code: %s%n",statusCode);System.out.printf(" Hash: %s%n",statusHash);System.out.printf(" Deadline: %s%n",statusDeadline);
The function is the core of this tutorial.
It monitors a transaction until it is confirmed or fails.
It uses a for loop to check the transaction status up to 60 times by default (2 minutes with 2-second intervals
between attempts).
This loop structure ensures that monitoring will eventually stop, even if the transaction never confirms.
On each attempt, the function queries the /transactionStatus/{hash}GET endpoint, which returns information
about the transaction's current state.
The response includes:
Group: The transaction's current status group. Possible values:
Group
Meaning
unconfirmed
The transaction is in the unconfirmed pool waiting to be included in a block.
confirmed
The transaction has been included in a block.
failed
The transaction failed validation and has been rejected.
Code: A status code providing more details (for example, Success or specific error codes).
See the TransactionStatusEnum schema for all possible
values.
Hash: The transaction hash being monitored.
Deadline: The transaction's deadline in network time.
The function displays all these fields on each polling attempt so you can see how the transaction progresses through
states.
// Check if the transaction has been confirmedif("confirmed".equals(statusGroup)){System.out.println("\nTransaction confirmed!");returntrue;}
After parsing the response, the function checks the group field.
If it is confirmed, the transaction was successfully included in a block through harvesting, and the function
returns successfully.
Confirmed transactions can still be reversed
A confirmed transaction has been included in a block, but it is not yet irreversible.
Only finalization makes a transaction final, when the block containing it is finalized by the network.
Until then, rollbacks remain possible.
# Check if the transaction failedifstatus_group=="failed":print(f"\nTransaction failed with code: {status_code}")raiseRuntimeError(f"Transaction failed: {status_code}")
// Check if the transaction failedif('failed'===statusGroup){console.log(`\nTransaction failed with code: ${statusCode}`);thrownewError(`Transaction failed: ${statusCode}`);}
// Check if the transaction failedif("failed".equals(statusGroup)){System.out.printf("%nTransaction failed with code: %s%n",statusCode);thrownewIOException("Transaction failed: "+statusCode);}
If the transaction status group is failed, the function raises an error with the status code.
Common reasons include insufficient balance, invalid signatures, or deadline expiration.
Failed transactions are rejected during validation and will not be included in a block.
if(404==statusResponse.statusCode()){System.out.printf(" Attempt %d: Transaction status not yet available%n",attempt);}
If the endpoint returns HTTP 404, the transaction status is not yet available.
This can happen immediately after announcing a transaction, before the node processes it, or if the hash is invalid.
The function handles this case by logging the attempt and continuing to poll.
For any other error (such as connectivity issues or failed transactions), the function re-raises the exception
immediately.
// Wait before next attempt (except on last attempt)if(attempt<maxAttempts)Thread.sleep(waitSeconds*1000L);
Between polling attempts, the function waits for a configurable delay (default: 2 seconds).
This prevents overwhelming the node with requests and allows time for network processing.
Using node https://reference.symboltest.net:3001
Monitoring transaction: 2B6D3B5232E06B9D32682F518C765301FCF9716BFA1EEEF9523653406E04C7EA
Waiting for transaction confirmation
Polling /transactionStatus/2B6D3B5232E06B9D32682F518C765301FCF9716BFA1EEEF9523653406E04C7EA
Attempt 1: Transaction status not yet available
Attempt 2: Transaction status not yet available
Attempt 3:
Status: unconfirmed
Code: Success
Hash: 2B6D3B5232E06B9D32682F518C765301FCF9716BFA1EEEF9523653406E04C7EA
Deadline: 47578965854
Attempt 4:
Status: unconfirmed
Code: Success
Hash: 2B6D3B5232E06B9D32682F518C765301FCF9716BFA1EEEF9523653406E04C7EA
Deadline: 47578965854
Attempt 5:
Status: confirmed
Code: Success
Hash: 2B6D3B5232E06B9D32682F518C765301FCF9716BFA1EEEF9523653406E04C7EA
Deadline: 47578965854
Transaction confirmed!
Some highlights from the output:
Transaction hash (line 2): The hash of the transaction to monitor, which uniquely identifies it on the network.
Polling start (line 4): Polling begins on the /transactionStatus/{hash}GET endpoint.
During the first attempts (lines 6-7) the node has not yet started processing the transaction.
Unconfirmed status (lines 8-17): The transaction enters the unconfirmed pool and waits to be included in a
block.
Confirmation (line 19): The status changes to confirmed, meaning the transaction has been included in a block.
The number of attempts and timing vary depending on network conditions and block production rate.
On the Symbol network, blocks are typically produced every 30 seconds, so you may see several unconfirmed status
responses before the transaction is confirmed.
Once confirmed, you can get additional details such as the block height where the transaction was included by querying
/transactions/confirmed/{transactionId}GET with the transaction hash.
To see the transaction from the network's perspective, visit the
Symbol Testnet Explorer and search for the transaction hash.
For production applications, consider these improvements:
Wait for finalization: Verify that the block containing the transaction has been finalized
to ensure it is truly irreversible.
See Querying Chain and Finalization Height.
Query multiple nodes: Check status and finalization across several nodes for greater reliability and
protection against single-node issues.
Use WebSockets: Replace polling with WebSocket subscriptions for real-time updates without repeated API calls.
See the Listening to Transaction Flow WebSocket tutorial.