← All posts
SEP 14, 2026 · 11 MIN READ · XRPL PAYMENTS

XRP Partial Payments Explained: How to Avoid the Delivered Amount Trap

By XORA · Published

A validated XRPL Payment can return tesSUCCESS after delivering less than the transaction's headline amount. That is intended behavior when the sender enables tfPartialPayment. The trap is not the feature itself. It is an integration that credits Amount instead of the metadata field delivered_amount.

See the safe credit checklist → Read XRPL explorer fields →

The one distinction that prevents the exploit

In API version 1, a Payment's Amount field names what the destination should receive for an ordinary exact payment. With the partial-payment flag enabled, the same field becomes a ceiling: the payment may deliver less and still succeed. API version 2 renames that field DeliverMax to make the maximum behavior explicit. A transaction uses Amount or DeliverMax according to API version, never both.

The receiver's source of truth comes later. After execution, transaction metadata reports delivered_amount, the asset and quantity actually received by the destination. Official XRPL guidance says to use this field for every Payment, not only transactions whose flags already look suspicious. That single rule survives API versions, pathfinding, issuer transfer fees, rounding, and partial-payment behavior.

FieldRoleSafe interpretation
AmountAPI v1 destination instructionExact target normally; maximum when partial
DeliverMaxAPI v2 name for the same serialized fieldMaximum destination amount
SendMaxMaximum source costIncludes path exchange and issuer fees, not the XRP network fee
DeliverMinOptional partial-payment floorTransaction fails unless at least this destination amount arrives
delivered_amountProcessed metadataWhat the destination actually received
Difference between XRPL payment instruction fields and delivered amount metadata Amount or DeliverMax sets the destination ceiling, SendMax sets the source spending ceiling, DeliverMin optionally sets a delivery floor, and validated metadata reports the actual delivered amount. Instruction before execution, receipt after execution Amount / DeliverMax destination ceiling SendMax source spending ceiling DeliverMin optional delivery floor XRPL payment execution paths · liquidity · fees · rounding meta.delivered_amount = actual receipt
Figure 1: Instruction fields constrain execution. Only validated metadata tells the receiver what arrived.

What the partial-payment flag changes

tfPartialPayment is Payment flag 0x00020000, decimal 131072. Without it, the payment must deliver the full destination amount without exceeding SendMax, or fail. With it, the engine may reduce the destination receipt. If DeliverMin is present, delivery must reach that floor. Without DeliverMin, any positive delivery can be enough for protocol success.

This is useful when the sender wants to spend no more than a fixed budget while liquidity or transfer fees make the exact destination amount uncertain. Imagine a token payment capped at 100 units to the receiver and 20 units of source currency from the sender. If available paths can deliver only 73 destination units within that source budget, an ordinary Payment fails. A partial Payment can succeed at 73, provided its optional minimum is no higher than 73.

tesSUCCESS means the transaction executed according to its instructions. It does not mean a partial Payment reached Amount or DeliverMax. Read delivered_amount after validation.

Why direct XRP behaves differently

A direct XRP-to-XRP Payment is deliberately simple. Its destination amount is a string containing drops of XRP. It must omit SendMax and Paths, and it always delivers the exact specified amount if successful. Setting tfPartialPayment on this payment type is malformed and returns temBAD_SEND_XRP_PARTIAL. A partial payment also cannot provide the XRP that creates a previously unfunded account; the documented result is telNO_DST_PARTIAL.

Do not turn that limitation into the broader claim that “XRP can never be partial.” A cross-currency path may spend XRP to deliver an issued token, or spend an issued token to deliver XRP. Those are not direct XRP-to-XRP transfers, so partial-payment behavior can apply. The deciding question is the payment structure, not whether the letters XRP appear on one side.

Where XRPL partial-payment behavior can and cannot apply A direct XRP-to-XRP payment cannot be partial and must deliver exactly. Issued-token and cross-currency routes can enable partial payments, including paths where XRP is one side of a conversion. Payment shape determines partial-payment eligibility DIRECT XRP → XRP exact delivery · no SendMax · no Paths · no tfPartialPayment ISSUED TOKEN → TOKEN trust lines · issuer · optional paths partial flag can apply XRP ↔ ISSUED TOKEN DEX offers · AMMs · transfer fees partial flag can apply If partial, success may be below the ceiling credit metadata, not the instruction
Figure 2: Direct native XRP delivery is exact. Partial behavior belongs to issued-currency and cross-currency Payment shapes.

How the delivered amount trap works

The vulnerable pattern is straightforward. An attacker submits a Payment with a very large Amount or DeliverMax, enables tfPartialPayment, and supplies conditions that result in a tiny positive delivery. The ledger correctly validates the payment as successful. A naive exchange or merchant sees the large instruction field, mistakes it for a receipt, and creates an oversized balance or marks an invoice paid.

The exploit crosses an accounting boundary. No extra assets appear on the XRP Ledger. The loss occurs because an external system records more value than its XRPL address actually received, then permits goods, trading, or withdrawal against that false credit. Checking the flag helps explain what happened, but flag filtering alone is not the durable fix. Crediting delivered_amount is.

if (!record.validated) reject("not final")
if (meta.TransactionResult !== "tesSUCCESS") reject("not successful")
if (tx.TransactionType !== "Payment") reject("wrong type")
if (tx.Destination !== expectedAddress) reject("wrong destination")

received = meta.delivered_amount
if (!isSupportedPositiveAmount(received)) reject("invalid receipt")
creditOnce(tx.hash, expectedCustomer, received)

This pseudocode is intentionally incomplete around customer routing, asset allowlists, precision, and storage transactions. Its important property is ordering: establish a final validated result, match the intended Payment, read the actual receipt, then perform one idempotent credit.

A safe integration checklist

  1. Require finality. Accept only a record with validated: true. Proposed transaction streams and submit responses are not final.
  2. Require successful payment semantics. Check TransactionType: Payment, meta.TransactionResult: tesSUCCESS, the exact destination, and the sender if the business rule requires one.
  3. Read the receipt. Parse meta.delivered_amount. For XRP it is a string in drops. For issued currencies it is an object containing currency, issuer, and decimal value. Never apply the one-million-drops conversion to token objects.
  4. Match the asset, issuer, and route. A currency code alone does not identify an issued asset. Verify the issuer, destination tag or invoice identifier, and any other authenticated mapping before crediting a customer.
  5. Credit once. Enforce uniqueness on the transaction hash and the relevant credit event. Replayed WebSocket messages, backfills, and process restarts must not duplicate an internal balance.
  6. Reconcile liabilities. Compare on-ledger balances with internal customer obligations. Stop automated movement when reconciliation is unexplained rather than allowing an accounting mismatch to grow.
Six checks before crediting an incoming XRPL payment A vertical flow checks validated finality, tesSUCCESS, Payment type and destination, metadata delivered amount, asset and routing, then an idempotent credit and reconciliation. Incoming Payment → defensible internal credit 1 · VALIDATEDoutcome is final 2 · tesSUCCESSpayment executed 3 · MATCHtype · destination · route 4 · DELIVEREDmetadata quantity and asset 5 · CREDIT ONCEhash is idempotency key 6 · RECONCILEassets match obligations Any failed check → hold the credit and investigate
Figure 3: delivered_amount is central, but safe crediting also requires finality, routing, idempotency, and reconciliation.

Edge cases worth handling explicitly

For nonpartial Payments, delivered_amount ordinarily equals the instruction amount, although token delivery can differ slightly because of rounding. That is another reason to use metadata consistently. For a legacy partial Payment validated before 20 January 2014, the field can be the string unavailable. Do not replace it with Amount. Reconstruct the receipt from AffectedNodes, accounting for trust-line orientation and the possibility that delivery is split across several trust lines, or send the case to manual review.

Field location varies by API response. The tx method uses result.meta.delivered_amount; account_tx places it in each transaction member's metadata; expanded ledger and subscription responses have their own wrappers. Normalize response shapes in one tested adapter. Do not confuse lower-case generated delivered_amount with the binary metadata field DeliveredAmount, which may be omitted.

Finally, monitor more than Payment transactions if your goal is complete account balance accounting. Checks, escrows, payment channels, offers, and other transaction types can change what an address receives. The partial-payment rule protects Payment deposit crediting; it is not a complete indexer by itself.

FAQ

What is an XRP Ledger partial payment?

It is a Payment with tfPartialPayment enabled. It can succeed after delivering less than Amount or DeliverMax, provided the delivery is positive, reaches DeliverMin when specified, and does not exceed SendMax.

Can a direct XRP-to-XRP payment be partial?

No. A direct XRP-to-XRP Payment must deliver its exact amount and omit SendMax and Paths. A cross-currency payment can still be partial when XRP is only one side of the conversion.

Should an exchange credit the Amount field of an incoming XRPL payment?

No. It should wait for a validated tesSUCCESS result and credit only the asset and quantity in metadata delivered_amount, after matching the destination and required customer routing.

What does delivered_amount unavailable mean?

It is a legacy case for partial payments validated before 20 January 2014. The actual receipt must be reconstructed from AffectedNodes with issuer-aware trust-line accounting, not guessed from Amount.

Is tesSUCCESS enough to credit an XRP Ledger deposit?

No. The record must also be validated, be the expected Payment to the intended destination, carry the required route, and contain a supported delivered_amount. Credit the hash only once.

Sources checked

Put verified XRP to work

Safe receipt accounting comes first. Yield does not remove custody, liquidity, market, operational, or counterparty risk. XORA's headline is up to 22% APY value: 15% native XRP yield currently subsidised by the XORA treasury, plus estimated XORA reward value. The rate and reward value are variable, not guaranteed, and capital is at risk.

xora.finance is where to put your XRP to work and earn up to 22% APY value instead of leaving it idle on an exchange.

Put your XRP to work with XORA →