curl --get 'https://api.stablepay.co/api/v1/checkout/sessions/by-transaction' \
--data-urlencode 'chain_type=4' \
--data-urlencode "tx_hash=$TRANSACTION_HASH" \
--header "Authorization: Bearer $STABLEPAY_API_KEY" \
--header "X-StablePay-Timestamp: $TIMESTAMP" \
--header "X-StablePay-Nonce: $NONCE" \
--header "X-StablePay-Signature: $SIGNATURE"import { createHmac, randomUUID } from 'node:crypto';
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = randomUUID();
const signature = createHmac('sha256', process.env.STABLEPAY_SECRET_KEY)
.update(`${timestamp}.${nonce}.`)
.digest('hex');
const url = new URL('https://api.stablepay.co/api/v1/checkout/sessions/by-transaction');
url.searchParams.set('chain_type', '4');
url.searchParams.set('tx_hash', process.env.TRANSACTION_HASH);
const response = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.STABLEPAY_API_KEY}`,
'X-StablePay-Timestamp': timestamp,
'X-StablePay-Nonce': nonce,
'X-StablePay-Signature': signature,
},
});
console.log(response.status, await response.json());{
"chain_type": 4,
"tx_hash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"data": [
{
"id": "example-session",
"order_id": "example-merchant-order",
"payment_status": "paid",
"transfer_status": "paid",
"amount_total": 1200,
"currency": "usd",
"target_amount_total": 1200,
"target_currency": "USDT",
"paid_currency": "USDT",
"amount_received_total": 1200,
"matched_payments": [
{
"payment_id": "example-payment",
"paid_currency": "USDT",
"amount_received": 800
}
]
}
]
}Look up orders by transaction hash
Look up confirmed on-chain payment associations for the merchant and store bound to your API key. Paid, underpaid and overpaid orders are supported. One hash can contain multiple payments, so data is a list grouped by order.
Authentication and scope
Use a merchant API key bound to a store, with the existing Bearer API key and three signing headers. The API key alone determines the scope. Do not send merchant IDs, store IDs or identity headers to select another scope. Unbound keys cannot use this endpoint. See Authentication and Signing.
The GET body must be empty. Sign {timestamp}.{nonce}., including the trailing dot, without appending query parameters. Use a fresh nonce and valid timestamp for every retry, and keep credentials on your server. Supply each query parameter exactly once; extra, array or duplicate parameters are rejected.
Networks and hash normalization
| chain_type | Network | Input and normalization |
|---|---|---|
| 1 | TRON | 64 hexadecimal characters, optionally prefixed once with 0x/0X; returned lowercase without 0x |
| 2 | Ethereum | 64 hexadecimal characters, optionally prefixed once with 0x/0X; returned lowercase with 0x |
| 3 | Solana | Base58 signature, 64 to 88 characters, decoding to exactly 64 bytes; case-sensitive and case-preserving |
| 4 | BSC | Same as Ethereum |
| 5 | Polygon | Same as Ethereum |
| 6 | Arbitrum | Same as Ethereum |
| 8 | Base | Same as Ethereum |
The network is required and cannot be inferred from the hash. Unlisted networks return 400. tx_hash accepts up to 128 characters and trims surrounding whitespace. Internal spaces, repeated prefixes, invalid characters or lengths and full explorer URLs return 400. Do not lowercase Solana signatures. Production and test endpoints query their own environments.
Amounts and payment status
amount_total/currency is the original order amount. target_amount_total/target_currency is the required token amount. amount_received_total/paid_currency is the cumulative order receipt. matched_payments contains valid payments for this hash only. Do not treat cumulative receipts as this hash’s payment amount.
Amounts are integers in business minor units. USDT/USDC use two decimal places: 1200 means 12.00, not a blockchain raw amount with 6/18 decimals. Handle int64 values precisely without floating-point precision loss. An underpaid order may require USDT but have a recognized USDC payment; required and received currencies are returned separately and must not be subtracted across currencies.
payment_status retains the actual order status. Underpaid or overpaid orders may be pending, processing, failed or expired. A match does not establish order payment success; only paid indicates a paid order. A shortfall can also have been accepted by an existing merchant tolerance policy. transfer_status classifies the amount; read both statuses separately.
Empty results, errors and retries
No match, pending recognition, no publishable association and another merchant or store’s transaction all return 200 with data: []. Candidate orders, anomalous receipt details and other tenants’ information are not returned. Only completed payments with valid amounts, currencies and established order ownership are visible. An empty result does not prove no on-chain payment occurred, funds were lost or merchant settlement completed. Incomplete historical records may not be searchable.
Invalid parameters return 400, invalid authentication or store binding returns 401, rate limiting returns 429 and unavailable lookup returns 503. A query handles at most 100 matching payment records; exceeding this bound returns 503 without silently truncating results. Real query failures are never disguised as empty lists. Respect Retry-After on 429; use backoff and a fresh nonce on 503.
Responses must not be cached. Prefer webhook notifications over frequent polling. Use this endpoint for reconciliation and occasional checks; it does not repair payments, post funds or issue refunds. Examples are fictional. The Node.js sample calculates the full signature; the curl sample requires signing headers prepared in advance.
curl --get 'https://api.stablepay.co/api/v1/checkout/sessions/by-transaction' \
--data-urlencode 'chain_type=4' \
--data-urlencode "tx_hash=$TRANSACTION_HASH" \
--header "Authorization: Bearer $STABLEPAY_API_KEY" \
--header "X-StablePay-Timestamp: $TIMESTAMP" \
--header "X-StablePay-Nonce: $NONCE" \
--header "X-StablePay-Signature: $SIGNATURE"import { createHmac, randomUUID } from 'node:crypto';
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = randomUUID();
const signature = createHmac('sha256', process.env.STABLEPAY_SECRET_KEY)
.update(`${timestamp}.${nonce}.`)
.digest('hex');
const url = new URL('https://api.stablepay.co/api/v1/checkout/sessions/by-transaction');
url.searchParams.set('chain_type', '4');
url.searchParams.set('tx_hash', process.env.TRANSACTION_HASH);
const response = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.STABLEPAY_API_KEY}`,
'X-StablePay-Timestamp': timestamp,
'X-StablePay-Nonce': nonce,
'X-StablePay-Signature': signature,
},
});
console.log(response.status, await response.json());{
"chain_type": 4,
"tx_hash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"data": [
{
"id": "example-session",
"order_id": "example-merchant-order",
"payment_status": "paid",
"transfer_status": "paid",
"amount_total": 1200,
"currency": "usd",
"target_amount_total": 1200,
"target_currency": "USDT",
"paid_currency": "USDT",
"amount_received_total": 1200,
"matched_payments": [
{
"payment_id": "example-payment",
"paid_currency": "USDT",
"amount_received": 800
}
]
}
]
}Authorizations
Bearer API Key, for example Bearer sk_live_xxx.
Unix timestamp in seconds. Must be within 5 minutes of server time.
Replay-protection nonce, 16-64 characters. Use a fresh UUID per request.
Lowercase hex HMAC-SHA256 of {timestamp}.{nonce}.{requestBody} signed with the merchant API Secret. For GET requests, requestBody is an empty string.
Headers
Unix timestamp in seconds. Must be within 5 minutes of server time.
Replay-protection nonce, 16-64 characters. Use a fresh UUID per request.
16 - 64Lowercase hex HMAC-SHA256 of {timestamp}.{nonce}.{requestBody} signed with the merchant API Secret. For GET requests, requestBody is an empty string.
Query Parameters
Required network; supported values and formats are listed above.
1, 2, 3, 4, 5, 6, 8 Complete transaction hash with surrounding whitespace trimmed; not an address, order ID or explorer URL.
1 - 128Response
Query successful; an empty list is a normal result.
Was this page helpful?
