Introduction
Have you ever sent cryptocurrency and wondered why the receiving platform hasn't updated your balance yet?
This situation is fairly common. A transaction may appear in your wallet, but that doesn't necessarily mean the receiving application has finished processing it. Sometimes the transaction is still waiting for confirmation. In other cases, the blockchain has already recorded it, but the application hasn't updated its internal records yet.
For developers working with crypto payments, checking a transaction's status is an important part of building a reliable payment system.
In this article, we'll build a simple crypto transaction status checker using C#. We'll use HttpClient to communicate with a blockchain explorer API and System.Text.Json to read the response. Along the way, we'll look at how to handle missing transactions, API errors, and confirmation counts.
The goal is to understand the basic implementation and how it could be extended into a real application.
1. What Happens After a Crypto Transaction Is Sent?
Before getting into the code, let's look at what happens when someone sends cryptocurrency.
Once a wallet broadcasts a transaction, it enters the blockchain network. Depending on the network and its current conditions, the transaction might be included in a block quickly or remain pending for some time.
After the transaction is included in a block, additional confirmations or finality signals may provide greater confidence that the transaction has been accepted.
This is why a transaction hash alone isn't enough to establish that a payment is complete.
A transaction checker should account for a few possible situations:
Pending: The transaction has been submitted but hasn't yet reached the required confirmation state.
Confirmed: The transaction has been included in a block, or has reached the relevant confirmation state reported by the API.
Failed: The network or API reports that the transaction execution failed.
Not found: The explorer hasn't found the transaction. It might not have been broadcast, or the explorer may not have indexed it yet.
The exact meaning of these statuses depends on the blockchain and the API. Keeping that distinction in mind will help us design the application correctly.
2. How Should We Structure the Application?
For this example, we'll keep things simple and use a console application.
We'll divide the work into three parts:
Input: The user provides a transaction hash.
API communication: The application requests transaction details from a blockchain explorer.
Response handling: C# reads the JSON response and displays the relevant information.
The flow looks like this:
Transaction Hash
|
v
Validate Input
|
v
Call Blockchain Explorer API
|
v
Receive JSON Response
|
v
Parse Transaction Details
|
v
Display Status
Keeping these responsibilities separate makes the code easier to understand and maintain. Later, the same logic could be used in an ASP.NET Core application or a background payment-monitoring service.
3. Create the C# Project
First, create a new console application using the .NET CLI:
dotnet new console -n CryptoTransactionChecker
cd CryptoTransactionChecker
We'll use two built-in .NET features:
No additional package is required for these basic features in a standard modern .NET project.
4. Create a Model for Transaction Details
Working directly with raw JSON throughout an application can become difficult to manage. A small model gives us a consistent way to represent the information we want to display.
Create a file named TransactionInfo.cs:
public class TransactionInfo
{
public string TransactionHash { get; set; } = "";
public string Status { get; set; } = "Unknown";
public string BlockNumber { get; set; } = "";
public int Confirmations { get; set; }
}
This class contains four properties.
TransactionHash stores the transaction identifier.
Status represents the status reported by the API.
BlockNumber identifies the block containing the transaction, when available.
Confirmations stores the confirmation count provided or calculated from network data.
One thing to remember: blockchain explorers don't all return the same JSON structure. This model represents the information our application needs, not a universal format used by every explorer.
We'll need to map the selected API's response to this model.
5. Connect to a Blockchain Explorer API
Now let's add the code that communicates with the explorer.
We'll use dependency injection for HttpClient through the constructor. This keeps the HTTP communication separate from the rest of the application and makes the client easier to test.
Create a file named BlockchainApiClient.cs:
using System.Net.Http;
using System.Text.Json;
public class BlockchainApiClient
{
private readonly HttpClient _httpClient;
public BlockchainApiClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<TransactionInfo?> GetTransactionAsync(
string apiUrl,
string transactionHash)
{
if (string.IsNullOrWhiteSpace(transactionHash))
{
throw new ArgumentException(
"Transaction hash cannot be empty.",
nameof(transactionHash));
}
using HttpResponseMessage response =
await _httpClient.GetAsync(apiUrl);
response.EnsureSuccessStatusCode();
await using var stream =
await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync<TransactionInfo>(
stream,
new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
}
}
Here's what happens when this method runs.
First, it checks that the transaction hash isn't empty. It then sends an HTTP GET request, checks the HTTP response status, and attempts to deserialize the response into a TransactionInfo object.
There is an important limitation in this example: apiUrl must already point to the correct transaction endpoint, and the API response must match the model's structure.
Most real explorers return additional fields or nest transaction details inside other JSON objects. In that case, you'll need a response DTO that matches the provider's schema and a mapping step to convert it into TransactionInfo.
The transactionHash is validated here but isn't automatically added to the URL. The calling code must construct the correct endpoint using the explorer's documented URL format.
6. Display the Transaction Information
Once we have the transaction details, displaying them is straightforward.
Add this method to the application:
static void DisplayTransaction(TransactionInfo? transaction)
{
if (transaction is null)
{
Console.WriteLine("No transaction data was returned.");
return;
}
Console.WriteLine(
$"Transaction Hash: {transaction.TransactionHash}");
Console.WriteLine(
$"Status: {transaction.Status}");
Console.WriteLine(
$"Block Number: {transaction.BlockNumber}");
Console.WriteLine(
$"Confirmations: {transaction.Confirmations}");
}
The method first checks whether the API returned a transaction object. If it did, the application displays the available information.
Separating this method from the API client is useful because the display logic doesn't need to know how the HTTP request works. If we later replace the console interface with a web page, we can reuse the transaction retrieval logic.
7. Handle API Errors and Missing Transactions
An API request doesn't always succeed. The explorer might be temporarily unavailable, a request might be rejected, or the response might contain unexpected JSON.
We should handle these cases without automatically treating every error as a failed blockchain transaction.
Here's an example of how the client can be called:
using System.Net.Http;
using var httpClient = new HttpClient();
var client = new BlockchainApiClient(httpClient);
try
{
TransactionInfo? transaction =
await client.GetTransactionAsync(
apiUrl: "REPLACE_WITH_DOCUMENTED_API_ENDPOINT",
transactionHash: "YOUR_TRANSACTION_HASH");
DisplayTransaction(transaction);
}
catch (HttpRequestException ex)
{
Console.WriteLine($"API request failed: {ex.Message}");
}
catch (System.Text.Json.JsonException)
{
Console.WriteLine("The API returned an unexpected JSON response.");
}
catch (TaskCanceledException)
{
Console.WriteLine("The request timed out or was cancelled.");
}
The endpoint and transaction hash are placeholders. Replace them with values that match the selected explorer's documentation before running the application.
This example handles a few common failures, but a production implementation should also consider API rate limits, authentication errors, invalid transaction hashes, and temporary service outages.
It's also worth distinguishing between an API error and a transaction failure. If the explorer times out, we don't know that the transaction failed; we only know that we couldn't retrieve its status at that moment.
8. Why Confirmation Counts Matter
Suppose an explorer reports that a transaction has been included in a block. Is that enough for an application to treat the payment as complete?
The answer depends on the blockchain and the application's requirements.
Some networks use confirmation depth to increase confidence in a transaction's finality. Others expose finality signals through their consensus mechanisms. The right approach depends on the network, the transaction, and the risk involved.
There isn't one confirmation count that works for every cryptocurrency.
For payment applications, confirmation status is only one part of the verification process. The application should also check that the transaction:
Belongs to the expected blockchain network.
Transfers the expected cryptocurrency or token.
Sends funds to the correct recipient address.
Contains the required amount.
Has the appropriate execution status.
Meets the application's confirmation or finality requirements.
These checks matter because a transaction can be present on a blockchain without representing the payment the application was expecting.
9. Making the Application More Reliable
The basic example demonstrates how to retrieve and display transaction information. Before using this approach in a real payment system, there are a few additional details worth addressing.
Validate transaction hashes. Check the expected format for the selected network before making an API request.
Use HTTPS. Protect API communication and avoid sending credentials over insecure connections.
Handle rate limits. Follow the provider's limits and use controlled retries when temporary failures occur.
Protect API keys. Store credentials in environment variables or a secrets manager instead of hardcoding them in source files.
Check the network. A transaction identifier alone doesn't prove that the transaction occurred on the intended network.
Prevent duplicate processing. If a confirmed transaction triggers a balance update, store its processing state and make the operation idempotent. Repeated API responses should not credit the same payment more than once.
Treat API results carefully. Follow the explorer's documented response semantics instead of assuming that every field has the same meaning across networks.
These additions become particularly important when transaction monitoring is connected to account balances or payment records.
10. Where Can We Use a Transaction Checker?
Once the basic client is working with a real explorer API, it can be extended for several purposes.
For example, it could be part of a cryptocurrency payment dashboard, a wallet transaction history page, a payment reconciliation tool, or a backend service that monitors incoming deposits.
It could also help a support team investigate delayed payments by retrieving the transaction's current on-chain status and checking the relevant details.
In each case, the application should report what the available blockchain data actually establishes. Receiving a transaction hash isn't, by itself, proof that the expected payment has been completed.
Conclusion
Building a crypto transaction status checker is a useful way to work with HTTP requests, JSON deserialization, and asynchronous programming in C#.
The basic implementation is relatively small, but reliable transaction monitoring involves more than retrieving a transaction hash and displaying a status. Developers also need to account for network-specific response formats, confirmation rules, recipient addresses, token amounts, API failures, and duplicate processing.
By separating API communication from data mapping and presentation, we can build a foundation that's easier to test, maintain, and extend.
The next step is to connect the client to a specific blockchain explorer, implement its documented response format, and test how the application behaves when a transaction is pending, confirmed, failed, or not yet found.
Join the conversation! Your thoughts help the community grow.