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
externalIdNoYour 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. Wingspan applies the credits that target the invoice and any eligible Automatic credits first.
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 above the invoice, refund the invoice doesn't return the excess. Its refundable amount is the applied amount of completed entries, and the excess is handled through an overpayment credit instead. The credits API can't send cash. Closing a credit with POST /v3/payments/credits/{creditId}/complete closes the remainder and moves no money.

For money paid outside Wingspan, record the payment with receivedAmount. 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"
}

If you want the $300.00 excess to lower the client's later invoices, create a credit with no invoiceId:

// POST /v3/payments/credits
{
  "payerId": "Pz4Rk8Wq2Tn6Lx9Vb3Mc1H",
  "amount": 300.00,
  "currency": "USD",
  "reason": "OverCollection",
  "memo": "Excess on wire WIRE-6001",
  "metadata": { "paymentReference": "WIRE-6001" }
}

Wingspan never held that money, so you can't refund it through Wingspan. Pay it back yourself and close the credit with POST /v3/payments/credits/{creditId}/complete.

Editing or removing a payment doesn't change a credit you created for it. Change or close the credit through the Credits calls.

Webhooks

EventFires when
InvoicePayment.ReturnedA payment comes back, such as a bounced check
Invoice.ReturnedThe invoice moves to Returned after a payment comes back

You can subscribe to Invoice.PaidOffPlatform for an off-platform payment that clears the balance. To see the result of editing or removing a recorded payment, read the invoice. See Event types for the current list.

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?