Skip to main content

Payment Statuses

A payment starts PENDING and ends in exactly one terminal status:

StatusTerminalMeaningMerchant action
PENDINGNoAwaiting payment — the customer has not completed an on-chain transfer yet.Do not fulfil yet.
SUCCESSYesPaid — the received amount matches the expected amount.Fulfil after verify.
ACCEPTABLEYesPaid with a small deviation that is within the tolerance you configured.Fulfil after verify.
MISMATCHYesPaid, but under- or over-paid outside your tolerance.Hold for manual review.
EXPIREDAlmostThe payment window elapsed with no payment.Do not fulfil — but see below.

SUCCESS, ACCEPTABLE and MISMATCH are final: a payment never leaves them, and never changes into another one.

EXPIRED has one exception, described next.

An EXPIRED payment can still be paid​

A customer who sends the transfer after the payment window has closed is still paid into the address Finomesh gave them — the blockchain does not know the window elapsed. Rather than lose those funds, Finomesh keeps watching that address for a grace period after expiry — currently 7 days, the same for every gateway, network, and asset. If the money arrives inside it, the payment reopens and resolves to SUCCESS, ACCEPTABLE or MISMATCH exactly as a timely payment would.

This means:

  • An EXPIRED payment can change status days later, with no action by you or the customer.
  • You receive a second webhook for that payment, carrying the new status. If you cancelled the order on the EXPIRED notification, treat this one as a genuine late payment, not a duplicate — webhooks are delivered once per status change.
  • The amount is judged against the price quoted when the payment was created, not the price on the day it arrived. For a stablecoin that makes no difference. For a volatile asset, a deposit that lands days late may resolve ACCEPTABLE or MISMATCH where a fresh quote would have said SUCCESS.
  • Verify still decides. Call verify as usual; depositPath on the response tells you why a payment resolved after expiring (see below).

A payment that expired and was never paid stays EXPIRED forever — which is the overwhelming majority of them.

depositPath — how the deposit reached us​

Verify returns depositPath once a payment has a recorded deposit:

ValueMeaning
scannerThe ordinary case: paid inside the payment window.
late_confirmationPaid inside the window, but the blockchain confirmation reached us after it closed. The customer was on time.
late_capturePaid after the window closed, and credited anyway through the grace period described above.
manual_inquiryMatched to the payment from a transaction hash submitted after the fact.

You do not need to branch on it to fulfil correctly — status remains the decision. It exists so a payment that resolved days after it expired can be explained rather than guessed at.

A second deposit to an already-resolved payment​

A payment is settled by one deposit. Its deposit address stops being watched as soon as that deposit is detected — before the payment has even resolved. A later deposit to the same address is not detected, not credited, and produces no webhook, and Submit transaction inquiry cannot recover it either: inquiry only accepts a payment that is still PENDING or EXPIRED, and refuses one that has already resolved to SUCCESS, ACCEPTABLE or MISMATCH. Contact Finomesh support about such funds.

The same is true of a deposit that lands after the grace period has ended — it is not detected. There, because the payment is still EXPIRED, submitting the transaction hash via inquiry is the recovery path.

Wrong-asset deposits​

A customer can send a different asset than the one they selected on the checkout (for example native TRX to a USDT deposit address). Finomesh still credits it: the deposit is valued in USD at the received asset's own market price and compared against what was expected, so it can resolve to SUCCESS, ACCEPTABLE, or MISMATCH exactly like a same-asset deposit — and, inside the grace period, it can reopen an EXPIRED payment the same way. Verify's receivedAssetSymbol / receivedAmountUsd fields tell you what actually arrived and its USD value.

If the price of either the quoted or the received asset is unavailable at the time, the deposit is not auto-credited and is handled manually instead. There is no per-gateway setting that forces every wrong-asset deposit to MISMATCH — don't design an integration around one.

How SUCCESS / ACCEPTABLE / MISMATCH is decided​

Finomesh compares the USD value of what arrived on-chain (minus any fees the customer covers) against the USD value of what was expected:

  • Exact match → SUCCESS
  • Deviation within your configured tolerance (overAcceptableDiff / underAcceptableDiff, as flat USD or a percentage) → ACCEPTABLE
  • Anything else → MISMATCH

You configure the tolerance per payment at creation (or inherit your gateway's defaults). Setting checkAmountDeviation: false disables the check entirely — any paid amount resolves to SUCCESS.

The arithmetic behind that comparison:

  • The base for a PERCENTAGE tolerance is the USD value of the invoice principal at the price snapshot taken when the payment was created (≈ amountUsd, up to sub-cent rounding) — not totalValue (which includes fees).
  • The received side is gross received minus any fee the customer covers — a merchant-paid fee is never subtracted from it.
  • Both sides are rounded to cents before comparison, a PERCENTAGE tolerance is computed as pct / 100 × base rounded to cents, and the bounds are inclusive (<=).
  • On a wrong-asset deposit the received side is valued at the received asset's current market price, not a price captured at creation.
  • If the offered asset has no USD price at all, the comparison falls back to asset units against totalValue (without subtracting fees), and an ABSOLUTE tolerance is then read in asset units rather than USD; deviationUsd is omitted in that case.
  • deviationUsd on the verify response is computed against totalValue, not this amountUsd base — the two figures can differ by a cent right at the acceptance boundary. Use status to decide whether to fulfil; use deviationUsd to explain a MISMATCH, not to re-derive the accept/reject decision yourself. On an open-amount payment it is computed against amountUsd instead and appears only on MISMATCH.
Fulfil only on the verified status

SUCCESS and ACCEPTABLE are the only statuses you should fulfil on, and only after a server-side verify call — never from the callback or webhook alone.