Skip to Content
API ReferenceOrphan Payments

Orphan Payments

Base URL: https://txnod.com/api/v1. Same HMAC header requirements as the Invoices API: X-Project-Id, X-Timestamp, X-Signature.

An orphan payment is an on-chain receipt that could not be matched to any open invoice address at detection time. Common causes are manual sends to a reused address, invoice expiry before funds arrived, or partners that do not use callback_url. Orphans are surfaced so operators can resolve them by attributing to an external_id — the attribution API creates a synthetic invoice in paid state indistinguishable from the normal-payment path.

List orphan payments

GET https://txnod.com/api/v1/orphan-payments — cursor-paginated list for the authenticated project. Filters by attribution status, chain, tx hash, date range, and amount range.

Request

Query parameters

ParamTypeRequiredConstraints
attributed"true" | "false"no
chain"btc" | "eth" | "tron" | "ada" | "polygon" | "bsc" | "ton"no
tx_hashstringno
date_fromstring (ISO 8601)no
date_tostring (ISO 8601)no
amount_units_gtestringnopattern: ^\d+$
amount_units_ltestringnopattern: ^\d+$
cursorstring (ULID)no
limitintegernomin: 1; max: 200; default: 50

Response

200 — Cursor-paginated orphan-payment list

FieldTypeRequiredConstraints
itemsarray<object>yes
next_cursorstring (ULID)no

Errors

StatusError code(s)Trigger
400validation_errorQuery validation error
401auth_invalid, signature_invalid, timestamp_out_of_windowHMAC authentication failed
429rate_limit_exceededPer-project rate limit exceeded
500internal_errorInternal error

Examples

curl "https://txnod.com/api/v1/orphan-payments?chain=btc&attributed=false&limit=50" \ -H "X-Project-Id: $TXNOD_PROJECT_ID" \ -H "X-Timestamp: $(date +%s)" \ -H "X-Signature: <hex>"
import { TxnodClient } from '@txnod/sdk'; const client = new TxnodClient({ projectId: process.env.TXNOD_PROJECT_ID!, apiSecret: process.env.TXNOD_API_SECRET!, }); const page = await client.listOrphanPayments({ chain: 'btc', limit: 50 }); for (const orphan of page.items) console.log(orphan.tx_hash);

Resolve an orphan from the dashboard

If you don’t want to call the attribution endpoint directly, resolve orphans from the dashboard:

  1. Open Orphan Payments in the dashboard. Filter by attributed=false and the relevant chain.
  2. Identify the orphan row by matching tx_hash, to_address, amount_units, or received_at against your ledger to find the corresponding external_id.
  3. Click Attribute on the row, enter the target external_id, and optionally a user_id / metadata blob for the synthetic invoice.
  4. The attribution creates a synthetic invoice in paid status and enqueues an invoice.paid webhook event indistinguishable from the real-payment path. Your normal handler processes it without any special-case code.

If the external_id is already in use for a different invoice, the dashboard surfaces external_id_conflict. If the orphan was already attributed, it surfaces orphan_already_attributed. Both are idempotent — they identify the collision cleanly and cannot cause double-fulfillment.

Attribute an orphan payment

POST https://txnod.com/api/v1/orphan-payments/{tx_hash}/attribute — attributes a previously unmatched payment to the caller-supplied external_id. Creates a synthetic invoice in paid status and enqueues an invoice.paid webhook event indistinguishable from the real-payment path. Idempotent replay with the same external_id collides via external_id_conflict. Re-attribution of an already-attributed orphan returns orphan_already_attributed.

Request

Request body (application/json)

FieldTypeRequiredConstraints
external_idstringyesmin length: 1; max length: 128
user_idstringnomin length: 1; max length: 256
metadataobjectno
to_addressstringnomin length: 1
tx_output_indexintegernomin: 0

Response

200 — Orphan attributed; synthetic invoice returned

FieldTypeRequiredConstraints
idstring (ULID)yes
project_idstring (ULID)yes
external_idstringyes
coin"btc" | "eth" | "usdt_erc20" | "usdc_erc20" | "trx" | "usdt_trc20" | "ada" | "pol" | "usdt_polygon" | "usdc_polygon" | "bnb" | "usdt_bep20" | "usdc_bep20" | "ton" | "usdt_ton"yes
addressstringyes
amount_cryptostringyes
amount_crypto_unitsstringyes
amount_usdnumber | nullyes
rate_snapshotobject | nullyes
payment_tokenstring | nullyespattern: ^[0-9a-f]{8}$
payment_uristringyes
callback_urlstring | nullyes
metadataobject | nullyes
matching_mode"exact" | "at_least" | "any"yes
confirmation_thresholdintegeryesmin: 0
status"pending" | "detected" | "paid" | "overpaid" | "partial" | "expired" | "expired_paid_late" | "reverted" | "cancelled"yes
expires_atintegeryesmin: 0
expires_at_isostring (ISO 8601)yes
created_atintegeryesmin: 0
created_at_isostring (ISO 8601)yes
derivation_pathstringnopattern: ^m(\/\d+'?)+$
verification_standard"bip84" | "bip44" | "cip1852" | "bip44_ed25519"no
transactionsarray<object>no
confirmationsintegernomin: 0

Errors

StatusError code(s)Trigger
400validation_errorValidation error (e.g. ambiguous tx_hash without disambiguators)
401auth_invalid, signature_invalid, timestamp_out_of_windowHMAC authentication failed
404orphan_not_foundOrphan not found or belongs to a different project
409external_id_conflict, orphan_already_attributedexternal_id_conflict (external_id already in use) or orphan_already_attributed (orphan already attributed)
429rate_limit_exceededPer-project rate limit exceeded
500internal_errorInternal error (e.g. rate quote unavailable)

Examples

curl -X POST https://txnod.com/api/v1/orphan-payments/0xabc123/attribute \ -H "X-Project-Id: $TXNOD_PROJECT_ID" \ -H "X-Timestamp: $(date +%s)" \ -H "X-Signature: <hex>" \ -H "Content-Type: application/json" \ -d '{"external_id":"order-42"}'
import { TxnodClient } from '@txnod/sdk'; const client = new TxnodClient({ projectId: process.env.TXNOD_PROJECT_ID!, apiSecret: process.env.TXNOD_API_SECRET!, }); const invoice = await client.attributeOrphanPayment('0xabc123', { external_id: 'order-42', }); console.log(invoice.id, invoice.status);