Skip to content
Blog

Program invoice matching: automatically match bank transactions to open invoices

· Allgemein

A payment of 539 euros arrives in the bank account.

The payment reference contains RG 4402.

In the ERP there is an open invoice for 555.67 euros. The customer is stored as Müller Textil, while the bank statement shows Mueller Textilien GmbH. A three percent cash discount has also been agreed.

For a person the match is fairly obvious. For software it is not.

This is where invoice matching starts. The application has to combine several incomplete signals and decide which open invoice belongs to a bank transaction and how confident that decision is.

For the functional workflow, see Incoming payments: match them to open invoices automatically.

For the banking integration that provides the transaction data, see Retrieve bank transactions via API: PSD2, FinTS or EBICS?.

Why a simple amount lookup is not enough

The simplest possible solution would be a database lookup by payment amount.

SELECT *
FROM invoices
WHERE status = 'open'
AND amount = 539.00;

This can work with small test data. Real accounting data quickly creates ambiguity.

Several invoices can have the same amount. Customers may deduct cash discount. Invoices can be paid partially. One transfer can settle several invoices. Payment references may also be incomplete or wrong.

A production matching system should therefore combine several signals instead of relying on one field.

Start with a consistent data model

Bank transactions should be translated into one internal format, regardless of whether they came from CSV, FinTS, PSD2 or EBICS.

{
  "id": "tx_12345",
  "bookingDate": "2026-09-04",
  "amount": 539.00,
  "currency": "EUR",
  "debtorName": "Mueller Textilien GmbH",
  "debtorIban": "DE02120300000000202051",
  "reference": "RG 4402"
}

The invoice can be represented in a similarly structured way.

Matching then becomes a comparison between two internal objects instead of a direct dependency on the banking interface.

Step 1: Reduce the candidate set

If a company has 80,000 invoices, a new transaction should not be compared with every one of them.

Only plausible open invoices should become candidates. Filters can include status, currency, invoice date, remaining amount and known customer information.

If the payer IBAN is already linked to a customer, the system can initially consider only that customer’s open invoices.

The smaller and better the candidate set, the simpler the actual scoring becomes.

Step 2: Find invoice numbers in the payment reference

An invoice number in the payment reference is one of the strongest signals. Customers do not always copy it exactly, however.

RE-2026-004402 may appear as RE 2026 004402, Invoice 4402 or RG4402.

Normalise values before comparing them

Characters, whitespace and separators can be normalised before searching.

import re

def normalize(value):
    value = value.upper()
    return re.sub(r'[^A-Z0-9]', '', value)

Shorter invoice fragments can also be searched, but the shorter the identifier becomes, the higher the risk of an accidental match.

Step 3: Evaluate the amount

An exact amount match is a strong signal, but a different amount does not automatically mean that the invoice is wrong.

Account for cash discount

Invoice amount         555.67
Cash discount 3%        16.67
Expected payment       539.00

The system should therefore be able to recognise a valid discounted amount and check whether the payment was made within the relevant discount period.

Account for rounding differences

Small tolerances may also be useful, depending on the accounting process.

difference = abs(transaction.amount - invoice.amount)

if difference == 0:
    score += 30
elif difference <= 0.02:
    score += 20

Step 4: Use the payer IBAN

A known payer IBAN can be a strong additional signal and can reduce the candidate set dramatically.

It should not be treated as absolute truth, however. Companies may pay from several accounts and group companies may settle invoices centrally.

Step 5: Normalise and compare names

Company names are often inconsistent between bank and ERP systems.

Müller Textil GmbH and MUELLER TEXTILIEN GMBH are an obvious example.

Before comparing names, the system can normalise case, umlauts, punctuation, whitespace and legal entity suffixes.

A string similarity method can then produce a score that becomes one more signal in the overall decision.

Step 6: Combine signals into a confidence score

Instead of many isolated yes or no decisions, each candidate can receive a score.

Invoice number detected             +50
Exact amount                        +30
Valid cash discount amount          +25
Known IBAN                          +15
Very similar name                   +10
Plausible payment date               +5

Thresholds can then define what happens next.

Score 80 or higher
Assign automatically

Score 50 to 79
Suggest for review

Score below 50
Leave unmatched

The exact weights are not universal. They should be tested against real transaction history and the organisation’s tolerance for false matches.

Do not only look at the best candidate

A best score of 91 and a second best score of 90 is still ambiguous.

A best score of 96 and a second best score of 42 is a very different situation.

The distance to the second best candidate can therefore be part of the decision.

Partial payments

An invoice does not always get paid in full.

Open invoice          1000.00
Payment                500.00
Remaining amount       500.00

The matching decision and the accounting treatment should be separate steps. A payment can be matched confidently even when the invoice remains partially open.

Combined payments

One transfer may settle several invoices.

Invoice A       500
Invoice B       400
Invoice C       600

Payment        1500

The matching problem then becomes combinatorial. The application has to identify which combination of open invoices matches the total amount.

This resembles a subset sum problem. Candidate reduction is particularly important here to avoid testing unnecessary combinations.

Overpayments

An overpayment should not automatically be forced into another invoice.

The difference may be a typo, a prepayment, a credit balance or another business case. A robust system separates invoice matching from the final accounting treatment.

Avoid processing the same transaction twice

Bank transactions are usually imported repeatedly. A stable external transaction ID should therefore be stored where possible.

If no sufficiently stable ID is available, a fingerprint can be created from several fields. The implementation still needs to account for the possibility of two genuinely identical payments.

Keep allocations traceable

A payment should not simply flip an invoice from open to paid.

A separate allocation record makes the decision traceable.

{
  "transactionId": "tx_12345",
  "invoiceId": "invoice_4402",
  "amount": 539.00,
  "score": 90,
  "method": "automatic",
  "createdAt": "2026-09-04T14:22:00"
}

When should AI be used?

Not every part of invoice matching needs AI.

An exact invoice number, an exact amount or a known IBAN are better handled with deterministic rules.

Invoice number?
      |
      v
Rule

Amount plausible?
      |
      v
Rule

Known IBAN?
      |
      v
Rule

Similar name?
      |
      v
Fuzzy matching

Still unclear?
      |
      v
AI or manual review

AI becomes more interesting when the available information is unstructured or difficult to interpret. Even then, accounting decisions should remain traceable and low confidence cases should be reviewed by a person.

A simple matching function

def calculate_score(transaction, invoice):
    score = 0

    if invoice_number_matches(
        transaction.reference,
        invoice.number
    ):
        score += 50

    if transaction.amount == invoice.amount:
        score += 30

    elif matches_discount_amount(
        transaction,
        invoice
    ):
        score += 25

    if transaction.debtor_iban in invoice.customer.known_ibans:
        score += 15

    similarity = compare_names(
        transaction.debtor_name,
        invoice.customer.name
    )

    score += similarity * 10

    return score

This is not production ready accounting software, but it demonstrates the core idea: generate candidates, calculate signals, score each candidate and decide based on confidence.

Human in the loop instead of automation at any cost

The goal should not be to match every single payment automatically.

The goal should be to remove the obvious cases from manual work and prepare the uncertain cases well.

If two invoices are almost equally likely, the application should not guess.

A practical architecture

Bank
 |
 v
Bank adapter
 |
 v
Transaction normaliser
 |
 v
Candidate generator
 |
 v
Feature extraction
 |
 v
Scoring
 |
 v
Decision engine
 |
 + Automatic
 + Review
 + Unmatched
 |
 v
Payment allocation
 |
 v
Invoice

The banking adapter only retrieves transactions. For the technical options behind that part, see Retrieve bank transactions via API: PSD2, FinTS or EBICS?.

Test with real cases

A prototype should not only use perfectly clean test data.

  • exact invoice number and exact amount
  • no invoice number, but known IBAN and matching amount
  • valid cash discount
  • two invoices with the same amount
  • variant company names
  • partial payment
  • combined payment
  • unknown payer with no useful payment reference

A good matching engine is not only good at finding correct matches. It also needs to recognise when there is not enough information for a safe decision.

Conclusion

Automatic invoice matching is not one algorithm. It is a combination of several smaller decisions.

Invoice number, amount, cash discount, IBAN, name and payment date each provide signals. None of them is always reliable on its own. Together they can create a strong confidence score.

A robust implementation first creates a small candidate set, evaluates several features and then decides whether a match is safe enough for automation.

Clear cases can be processed automatically. Uncertain cases can be suggested for review or deliberately left unmatched.

For the full business workflow, see Incoming payments: match them to open invoices automatically.

If you want to test automatic matching between bank transactions and open invoices and are not yet sure how much can be handled reliably with rules, fuzzy matching or AI, a small prototype is usually the most useful first step.

In 15 minutes we can clarify without obligation which matching approach fits your data, edge cases and existing systems. Book a first conversation.

Collaboration

Custom AI and automation solutions, fitted to process, data risk and cost.

Get in touch →

Blog

New articles by email. No ads.