Partial and off-platform payments

Take more than one payment on an invoice, record checks and wires paid outside Wingspan, fix or remove a recorded payment, and handle bounced checks and overpayments.

An invoice can take more than one payment. Wingspan keeps a running balance from every payment on it, whether the money came through Wingspan or you recorded a check or wire paid outside Wingspan. The invoice stays open until the balance reaches zero.

How the balance works

Every payment is an entry in the invoice's payments array. Three read-only fields on the invoice track the balance:

FieldWhat it holds
amountPaidThe total of the payments that count toward the invoice
amountDueamount minus applied credits minus amountPaid
amountDueAfterCreditsamountDue minus credits waiting to apply. The next payment through Wingspan collects this.

A payment counts while it's pending, processing or completed. A failed, cancelled or returned payment stops counting, and amountDue goes back up.

A partly paid invoice stays Opened, or Overdue once its due date passes. When amountDue reaches zero, the invoice moves to Paid. If every payment on it was recorded off platform, it moves to PaidOffPlatform instead.

Let clients pay part of an invoice

Your clients can pay part of an invoice through Wingspan when partial payments are on for them. Off-platform recordings and money Wingspan receives can always be partial, whatever the setting.

Set it at three levels. The most specific setting wins:

LevelWhere
One invoiceacceptsPartialPayment on Create an invoice or Update an invoice
One clientacceptsPartialPayment on Update a payer
Your AccountacceptsPartialPayment on Update payee settings. Off by default.

Leave the invoice or client value empty to inherit from the level above. The invoice's effectiveAcceptsPartialPayment shows the result.

With partial payments on, Pay an invoice takes any amount above zero, up to amountDueAfterCredits. With them off, amount must equal amountDueAfterCredits. Leave amount out to collect the full balance. Autopay always collects the full balance.

Record a payment made outside Wingspan

A $1,200.00 invoice is open, and the client mails a $400.00 check. Record it with Record an off-platform payment:

curl -X POST "https://api.wingspan.app/v3/payments/invoices/Iv7Hn2Qx5Rt8Kw3Lp6Zb9D/pay-off-platform" \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "receipt-88412",
    "amount": 400.00,
    "paymentMethodDescription": "Check",
    "paidDate": "2026-10-09",
    "referenceNumber": "1042"
  }'
// (trimmed)
{
  "status": "Opened",
  "amount": 1200.00,
  "amountPaid": 400.00,
  "amountDue": 800.00,
  "amountDueAfterCredits": 800.00,
  "payments": [
    {
      "id": "Pm3Kx8Wq1Tz6Nv4Rb7Hm2A",
      "source": "OffPlatform",
      "method": "OffPlatform",
      "status": "Completed",
      "amount": 400.00,
      "currency": "USD",
      "externalId": "receipt-88412",
      "paidDate": "2026-10-09",
      "paymentMethodDescription": "Check",
      "referenceNumber": "1042"
    }
  ]
}

The invoice now shows $800.00 due and stays Opened. Record the next check the same way, with a new externalId.

FieldRequiredNotes
externalIdYesYour reference for this payment, such as a receipt ID from your ledger. Unique per invoice for you.
amountYesThe part applied to this invoice. Up to amountDue, less credits waiting on this invoice. Credits on the client's balance don't apply to off-platform payments.
paymentMethodDescriptionYesHow the client paid, such as Check or Wire.
paidDateYesThe date the client paid, YYYY-MM-DD. Can't be in the future.
referenceNumberNoCheck number or transfer reference.
receivedAmountNoThe full amount you received, when it's more than amount. See Overpayments.
notesNoFree text.

Send an Idempotency-Key with every recording. You only see your own externalId values. The client can record payments it made on its side of the same record with Record an off-platform payment for a payable, and each party manages only the entries it recorded.

One check, several invoices

When one check pays several invoices, record one entry on each invoice for the part applied there, and give them the same referenceNumber.

Retry safely

If you lose the response, send the request again with the same externalId and a new Idempotency-Key. Wingspan keeps the first response to each key for 24 hours and returns it again, errors included, so the old key can't tell you anything new. Leave out If-Match: if the first attempt went through, it changed the invoice's ETag, and the old value would fail with 412. Your externalId keeps the payment from being recorded twice.

If the payment was already recorded, you get 409 with code ResourceConflict. Read the invoice. An entry with your externalId means the payment is on the invoice. If there's no such entry, someone removed it, and you need a new externalId to record it again.

Edit or remove a recorded payment

You can change or remove the off-platform payments you recorded. The paymentId is the entry's id.

To fix an amount, send Edit an off-platform payment entry with the fields to change in updateMask. Say the $400.00 check was really $350.00:

curl -X PATCH "https://api.wingspan.app/v3/payments/invoices/Iv7Hn2Qx5Rt8Kw3Lp6Zb9D/payments/Pm3Kx8Wq1Tz6Nv4Rb7Hm2A" \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H 'If-Match: "q3Xv9LmT2RbK7wN4cY8dP1sZ6hF"' \
  -H "Content-Type: application/json" \
  -d '{ "updateMask": ["amount"], "amount": 350.00 }'

The invoice now shows $850.00 due. You can edit amount, receivedAmount, paidDate, paymentMethodDescription, referenceNumber, notes and metadata. The externalId can't change.

To take a payment off the invoice, send Remove an off-platform payment entry with If-Match. It returns 204, and the amount is due again. Remove the entry if a check you recorded bounces, or if you recorded a payment twice.

Both calls need the invoice's current ETag in If-Match. Any new payment or change on the invoice changes the ETag, so read the invoice first if anything else may have written to it. A stale ETag returns 412 and changes nothing.

You try to changeResult
An off-platform payment you recorded200 for an edit, 204 for a removal
A payment the client recorded403
A payment Wingspan collected or received409

Checks and transfers Wingspan receives

When a client mails a check to Wingspan or sends a wire or ACH transfer, Wingspan applies it to the invoice as a payment with source set to WingspanReceipt and method set to Check, Wire or Ach. You don't record these yourself.

A check clears like an ACH debit. If it bounces, its entry moves to Returned and stops counting, so amountDue goes back up. The invoice moves to Returned while a balance is due, and InvoicePayment.Returned fires. The client can pay again through Wingspan.

Overpayments

The balance never goes below zero. When a client pays more than is due, amount on the entry is the part applied to the invoice and receivedAmount is the full amount received.

For money Wingspan receives, Wingspan creates a credit for the excess, linked to the payment by sourcePaymentId. Wingspan pays you the full amount it received. The credit lowers the client's later invoices, or you can refund it, up to the excess.

For money paid outside Wingspan, record the payment with receivedAmount, then create the credit yourself. For a $1,500.00 wire against a $1,200.00 balance, record the payment:

// POST /v3/payments/invoices/Iv7Hn2Qx5Rt8Kw3Lp6Zb9D/pay-off-platform
{
  "externalId": "receipt-90010",
  "amount": 1200.00,
  "receivedAmount": 1500.00,
  "paymentMethodDescription": "Wire",
  "paidDate": "2026-10-15",
  "referenceNumber": "WIRE-6001"
}

Then create a credit for the $300.00 excess, linked to the new entry's id:

// POST /v3/payments/credits
{
  "payerId": "Pz4Rk8Wq2Tn6Lx9Vb3Mc1H",
  "invoiceId": "Iv7Hn2Qx5Rt8Kw3Lp6Zb9D",
  "amount": 300.00,
  "currency": "USD",
  "reason": "OverCollection",
  "sourcePaymentId": "Pm8Wn2Kq5Rx1Tz7Lb4Hv6C",
  "disposition": "Balance"
}

The credit lowers the client's later invoices. Wingspan never held that money, so you can close the credit but can't refund it through Wingspan.

Editing or removing a payment doesn't change a credit linked to it. Change or close the credit through the Credits calls.

Webhooks

EventFires when
InvoicePayment.UpdatedYou edit a payment you recorded
Invoice.PaymentRemovedYou remove a payment you recorded. context.paymentId names it.
InvoicePayment.ReturnedA payment comes back, such as a bounced check
Invoice.ReturnedThe invoice moves to Returned after a payment comes back
Invoice.PaidOffPlatformOff-platform payments clear the balance

Payable.PaymentRemoved fires on the client's side when the client removes a payment it recorded.

Errors

StatusCodeMeaning
422ValidationErroramount is zero or less, more than the balance, or more than receivedAmount, or a required field is missing.
409ResourceConflictYou already used this externalId on this invoice. Read the invoice to see whether it's recorded.
409InvalidStateTransitionThe invoice can't take payments: it hasn't been sent, it's cancelled, or it's fully refunded.
409ResourceLockedOther writes kept getting there first. Retry with a new Idempotency-Key.
412If-Match doesn't match the invoice's current ETag. Nothing changed.

Related pages


Did this page help you?