What an underpayment is
When a price is set and a payment is made against it, three things can happen: the payment matches the amount due, it exceeds it, or it falls short. The last case is an underpayment — the payer has sent real value, the transaction is valid, but the total received is less than the obligation. Nothing about the payment is broken; it simply does not reach the figure that was owed.
Crypto payments make this a routine case to handle rather than an edge case. A customer can mistype the amount, send a round number instead of the exact figure, or have a network fee deducted from the transfer so that the amount that actually lands is smaller than the amount they intended to send. Each of these produces a confirmed deposit that is short of the invoice total. Because the deposit is on-chain and irreversible, the payment cannot simply be rejected the way a declined card would be — the funds have already moved, and the system has to account for them.
An underpayment is measured against a fixed reference. The invoice carries a specific amount due, and the confirmed deposit is compared against that figure: equal settles it, more is an overpayment, less is an underpayment. The difference — the remaining amount needed to reach the total — is the shortfall. A payment is only judged underpaid once the deposit has met the chain's confirmation requirements and credited, so a transaction still confirming is not yet an underpayment; it is pending.
Why it matters for a merchant
An underpayment leaves the merchant holding real funds against a bill that is not yet satisfied. Ignoring the deposit would mean writing off money the customer actually sent; treating the invoice as paid would mean releasing goods or service for less than the agreed price. Neither is acceptable, so the merchant needs the shortfall surfaced clearly and tied to the right invoice, with the credited funds accounted for either way.
What the merchant does next is a business decision, not a technical one. A small shortfall might be accepted and the invoice closed; a larger one might prompt a request for the remaining balance, or a refund of the partial deposit so the customer can start over. The payment system's job is to make the situation unambiguous — here is the invoice, here is the amount due, here is what was received, here is the gap — so that decision can be made on accurate figures and the same partial deposit is never double-counted.
Underpayment on halfin
halfin invoices are fiat-anchored with the crypto amount locked at activation, so the amount due is a fixed figure for the life of the invoice. When a confirmed deposit credits for less than that locked amount, halfin records the shortfall and marks the invoice underpaid rather than paid, keeping the partial deposit accounted for against the invoice. The comparison is always made against the locked amount, not the live market rate, so the outcome does not change because the rate moved while the payment was in flight.
The outcome is delivered as its own webhook event. A deposit that matches the locked amount produces invoice.paid, one that exceeds it produces invoice.overpaid, and one that falls short produces invoice.underpaid — so your integration can branch on the exact result instead of inferring it. As with every halfin webhook, the envelope is HMAC-signed: verify the signature before acting on it, then use the underpaid event to drive whatever your business rule requires, whether that is requesting the balance, refunding the partial deposit, or accepting the shortfall and closing the invoice.
- Compared against the locked amount — the deposit is measured against the crypto amount fixed at activation, not a live rate.
- Confirmation-gated — an invoice is only judged underpaid after the deposit meets the chain's confirmation threshold and credits.
- Surfaced as invoice.underpaid — a dedicated event, distinct from invoice.paid and invoice.overpaid.
- Merchant decides the response — request the balance, refund the partial deposit, or accept and close; halfin keeps the funds accounted for.