MercanPay Blog

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.

Published: October 5, 20265 min readTürkçe oku
Handling Crypto Underpayments, Overpayments and Late Payments

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_amount in 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:

  1. 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 id or your order_id so you never process the same event twice.
  2. Check the amount and coin. Confirm that token and paid_amount match your order.
  3. Don't trust the browser redirect. A success_url redirect 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.

#Webhooks#Invoices#USDT#TRX

Related posts

Start accepting crypto payments in minutes

Accept USDT and TRX (TRC20). Fees from 0.4%, a hosted payment page, API and webhooks.