Handling Crypto Underpayments, Overpayments and Late Payments
How to handle crypto underpayments, overpayments and late payments with tolerance settings, invoice extensions, webhook events and working code samples.

Crypto underpayments, overpayments and late payments come up sooner or later for anyone who sells for crypto. A customer's exchange deducts its fee from the amount, someone sends a little extra, or someone pays after the invoice has expired. This guide explains how MercanPay treats each case and how your site should react.
Why the amount isn't always exact
With a card payment the system sets the amount and the customer types nothing. With crypto the customer enters the amount by hand in their wallet or exchange, and that opens the door to differences:
- Exchange fees: some exchanges subtract the withdrawal fee from the amount sent, so "send 25 USDT" can arrive as less.
- Typos and rounding: decimals get mistyped.
- Split payments: the customer sends the total in two transactions.
- Delays: the customer leaves the payment page open and pays hours later.
A good gateway treats these as expected scenarios, not errors. For the overall flow, see what is a crypto payment gateway.
Invoice statuses at a glance
| Status | Meaning | Your action |
|---|---|---|
pending |
Waiting for payment | Wait |
partial |
Underpaid | Wait for the remainder |
paid |
Fully paid | Fulfil the order |
overpaid |
Paid more than requested | Fulfil, then decide what to do with the excess |
expired |
Expired unpaid | Close the order or issue a new invoice |
cancelled |
Cancelled | Close the order |
Every change triggers a signed webhook: invoice.paid, invoice.partial, invoice.overpaid, invoice.expired or invoice.cancelled.
Underpayments and the extension window
When a customer sends less than the invoice amount, the invoice moves to partial and you receive invoice.partial. Then:
- The payment page shows the customer the remaining amount.
- They can send the rest to the same address.
- The invoice gets at least 30 more minutes, so it doesn't expire while they top up.
remaining_amountin the API response tells you what's still due.
Once the rest arrives, the invoice becomes paid and invoice.paid fires. Tie your fulfilment to that event and you're covered.
Underpayment tolerance (0–20%)
Chasing a customer over a few cents wastes everyone's time. The panel has an optional underpayment tolerance setting, from 0% to 20%.
How to choose:
- 0%: every cent counts, for example on thin-margin products.
- 1–2%: a sensible range for absorbing exchange-fee differences.
- Higher: only as a deliberate business decision.
Overpayments
If the customer sends more than requested, the invoice becomes overpaid and invoice.overpaid fires. The full amount received is credited to your balance. paid_amount shows the total paid and overpaid_amount the excess.
For fulfilment, treat overpaid exactly like paid, because the customer paid at least what you asked. For the excess, set your own policy: store credit on the next order, or a refund. A refund is simply a withdrawal from your balance to the address the customer gives you, and the withdrawal fee (1–3 USD) applies.
Late payments
Invoices stay open for at most 24 hours. What if the customer pays afterwards?
A late payment is still credited to your balance. Nothing is lost. The same goes for a payment that lands on a cancelled invoice. The data.payment.late field in the webhook tells you the payment came in late.
Exchange rates matter here:
- USDT invoices: USDT is a stablecoin designed to track the dollar, so the rate question is mostly moot.
- USD-priced TRX invoices: a late payment is re-priced at the current rate. The locked rate no longer applies, since TRX may have moved in the meantime.
We cover how the rate lock works in crypto payment exchange rate risk.
Decide your late-payment rule upfront. For a digital product in stock, delivering anyway is usually fine. For limited stock or a time-bound promotion, reaching out to the customer may be better.
Handling each case in your webhook
Here is a Python (Flask) receiver using the official library's verify_webhook function:
import json
import os
from flask import Flask, abort, request
from mercanpay import verify_webhook
app = Flask(__name__)
SECRET = os.environ["MERCANPAY_WEBHOOK_SECRET"]
@app.post("/mercanpay/webhook")
def webhook():
body = request.get_data()
if not verify_webhook(SECRET, body, request.headers.get("X-MercanPay-Signature", "")):
abort(401)
event = json.loads(body)
invoice = event["data"].get("invoice", {})
payment = event["data"].get("payment", {})
if event["type"] in ("invoice.paid", "invoice.overpaid"):
# fulfil the order; the same event may arrive twice, so make this idempotent
# if payment.get("late"): apply your late-payment policy
pass
elif event["type"] == "invoice.partial":
# mark as partially paid, don't ship yet
pass
elif event["type"] in ("invoice.expired", "invoice.cancelled"):
# close the order or release reserved stock
pass
return "", 200
PHP users get the same logic with MercanPay::verifyWebhook. See PHP crypto payment integration and how to verify a webhook signature.
Three rules to keep:
- De-duplicate. Webhooks are retried until you answer 2xx, after 30 s, 2 min, 10 min, 30 min, 1 h, 3 h, 6 h, 12 h and 24 h. Use the event
idor yourorder_idso you never process the same event twice. - Check the amount and coin. Confirm that
tokenandpaid_amountmatch your order. - Don't trust the browser redirect. A
success_urlredirect can be faked. Fulfil only from a verified webhook or an API lookup.
The delivery history on the panel's Webhooks page shows each event's response code and lets you resend it.
FAQ
The customer underpaid and the invoice expired. What happens to the money?
What arrived is credited to your balance. After a partial payment the invoice gets at least 30 extra minutes. If the rest still never comes, contact the customer and issue a new invoice for the remainder.
Are overpayments refunded automatically?
No. The full amount goes to your balance and the refund decision is yours. To refund, withdraw from your balance to the customer's address.
Which rate applies to a late USD-priced TRX payment?
A late TRX payment is re-priced at the rate current when it arrives.
Should I enable underpayment tolerance?
If small exchange-fee shortfalls happen often, a low tolerance improves the customer experience. Pick a rate that fits your margins.
Get started with MercanPay
Accept USDT and TRX with partial, over and late payments handled for you. Apply for a merchant account and see the documentation for every field and event.


