Skip to content

Automation  ·  Finance

How to build three-way matching

An invoice matches its purchase order, so it gets paid, and nobody notices the goods never arrived. The model behind matching that checks all three documents, the prompts that build it, and what it takes to run it.

Built with Tray Headless

  1. System Coupa
  2. Step Match line by line
  3. Step Hold until received
  4. Step Clear within tolerance
  5. System NetSuite
Also Slack

An invoice that arrives before its receipt is held, and released on its own the moment the receipt is recorded.

The short answer

What is three-way matching?

Three-way matching has four parts: a comparison of order, receipt and invoice at the line rather than the total, tolerances on price and quantity that finance sets and can change, holds for invoices that arrive before their receipt that release on their own, and every mismatch sent to a named person with the three documents side by side. The step that fails most is the receipt. If nobody records that goods or services arrived, every invoice either waits forever or gets matched two ways, and two-way matching pays for things that never came.

Stage 6 of 7: Three-way match and payment. Part of Procure-to-pay, end to end : every stage, the systems it runs on and the guide that builds it.

What matters here

  • Match at the line, not the total. Two wrong lines can add up to a right total.
  • Ask the requester to confirm services were delivered. There is no delivery note for a consultant's week.
  • Hold an invoice that arrives before its receipt, and release it the moment the receipt lands. Rejecting it annoys a supplier who did nothing wrong.
  • Treat a price change as an exception, never a tolerance. That is a supplier changing terms.
  • Measure the first-pass match rate. It tells you whether the tolerances or the receipts are the problem.

Who this is for

You run accounts payable or finance systems. Invoices are matched to the purchase order by eye, receipts are recorded when somebody remembers, and the exceptions queue is a shared inbox.

How it works in practice

What has to happen between a supplier's invoice arriving and it being cleared to pay.

  1. 1

    The invoice is tied to its purchase order

    By the order number on the invoice, or by supplier, amount and open orders when the number is missing or wrong.

  2. 2

    Each invoice line is matched to an order line and a receipt

    Quantity, unit price and amount compared line by line, with freight and tax handled as their own lines.

  3. 3

    Differences inside tolerance clear on their own

    A small rounding difference or a quantity a few percent over, within limits finance sets per category.

  4. 4

    An invoice ahead of its receipt waits, then releases

    Held rather than rejected, and cleared automatically when the receipt is recorded, usually a day or two later.

  5. 5

    Everything else goes to a named person

    With the order, the receipt and the invoice side by side and the difference highlighted, not a line in a shared queue.

  6. 6

    Cleared invoices go forward for payment

    On the supplier's terms, with the match result recorded against the invoice for the audit.

What three-way matching is made of

Four parts, and the second is where most mismatches really start.

Line-level comparison

Each invoice line against its order line and receipt, so a short delivery on one item is caught even when the total looks right.

Receipts for goods and services

Goods received from the warehouse or the requester, services confirmed by the person who ordered them, with a reminder when an invoice is waiting on one.

Tolerances finance controls

Price and quantity limits by category and supplier, held in a table, with a price change always treated as an exception.

Holds and assigned exceptions

Invoices ahead of their receipt wait and release on their own. Real mismatches go to one named person with an age on them.

The Tray Headless prompts

Paste these into Claude Code or Codex with the Tray Headless plugin installed. Each stage runs on its own. The systems named in them are the worked example rather than a requirement, and every prompt says so.

Once per project, run /tray-workflows:set-workspace to pick the workspace these build in. Point it at a sandbox first.

  1. 1

    First, read how orders and receipts are held

    Matching is only as good as the receipt data under it.

    Headless skills build-workflow

    Use build-workflow. The systems in play are Coupa, NetSuite, Slack,
    Okta and Snowflake, or whatever we run in those seats. Before you plan
    anything, tell me which of them are already authenticated in the
    workspace.
    
    I need the purchase order and its lines, the receipt and its lines, and
    the supplier invoice and its lines, in whichever system holds each.
    
    Then tell me: are services receipted at all today, or only goods. That
    answer decides how much of this is matching and how much is getting
    receipts recorded in the first place.
  2. 2

    Match line by line

    Two wrong lines can add up to a right total.

    Headless skills build-workflow tray-gotchas

    Use build-workflow and tray-gotchas. When an invoice is posted as a
    draft, tie it to its purchase order:
    
      By the order number on the invoice, if it is valid for that supplier
      Otherwise by supplier and open orders with enough remaining value
      Otherwise stop and route to AP, never guess between two orders
    
    Then match each invoice line to an order line and its received quantity:
    quantity, unit price and line amount. Treat freight, tax and discounts
    as their own lines, not as differences.
    
    Handle units explicitly. An order for 2 boxes of 50 and an invoice for
    100 units is a match.
  3. 3

    Set tolerances finance can change

    So the small differences clear and the real ones stop.

    Hold tolerances in a table, by category and optionally by supplier:
    
      price: a percentage and an absolute cap, whichever is smaller
      quantity: a percentage over, never under
      amount: a total cap on the invoice difference
    
    Inside every tolerance, clear the line. Outside any, stop.
    
    Always treat these as exceptions regardless of tolerance:
    
      A unit price different from the order on every line. That is a price
      change, and it needs the buyer.
      An invoice quantity above the ordered quantity in total.
      The same invoice number from the same supplier twice.
  4. 4

    Hold invoices that are ahead of the receipt

    The goods are usually two days behind the paperwork.

    When an invoice line has no receipt yet:
    
      For goods, hold the invoice and release it automatically when the
      receipt is recorded and the line matches.
      For services, message the requester in Slack: did this arrive, with
      the order line and the invoice amount, and buttons for yes, partly
      and no. Record their answer as the receipt.
    
    If a hold is still open after a number of days I set, remind the
    requester, then route to AP with the age shown.
    
    For a partial receipt, clear the matched quantity and keep the rest of
    the invoice on hold. Never clear the whole invoice for part of the goods.
  5. 5

    Send every mismatch to a person, and report the rate

    A shared exceptions queue is where early payment discounts expire.

    Every exception goes to one named person: the buyer for a price
    change, the requester for a receipt question, AP for anything else.
    Show the order line, the receipt and the invoice line side by side with
    the difference highlighted, in Slack, with the age of the invoice and
    any discount deadline.
    
    Report weekly: first-pass match rate, exceptions by reason and by age,
    invoices on hold waiting for a receipt, and discounts lost to an open
    exception.
    
    If most exceptions are missing receipts, the fix is receipting, not the
    tolerances.
  6. 6

    Validate, then hand the tolerances to AP

    Because what counts as close enough is finance judgement.

    Run the per-step schema checks and the whole-workflow audit before this
    touches production. Run it in report-only mode for one month end first,
    so the tolerances are set from real invoices.
    
    Then open the same workflow in Tray Build so accounts payable can change
    the tolerance table and the hold periods in the visual canvas.

What it connects to

The order and receipt live with procurement, the invoice and the payment with finance.

Coupa

Read purchase order lines and receipts where procurement runs there, and record service receipts confirmed by the requester.

Reads and writes

NetSuite

Read the draft vendor bill and its lines, write the match result, and clear matched invoices for payment.

Reads and writes

Slack

Ask the requester to confirm a service was delivered, and send each exception to its owner with the three documents.

Reads and writes

Okta

Find the requester, the buyer and their deputies today, so an exception never goes to somebody who has left.

Reads

Snowflake

Land every match result, so first-pass match rate by supplier and category is a query.

Writes

Same build, other stacks

The design does not change if you run something else in one of these seats. The same prompts build it against SAP S/4HANA, Google BigQuery, Microsoft Teams, Azure Active Directory, Oracle or Databricks.

Connections in this build

Field mapping, templates and common problems for each pairing: Coupa + NetSuite and Okta + Slack.

Named systems are the ones most teams run, not the only ones that work. Each is an authentication in your Tray workspace, referenced by name, so the workflow never holds a credential. Where we have a connector page, the name links to it.

Running it in production

This decides which invoices get paid without anybody looking. It is audited, line by line.

It runs on the platform, not on your laptop

Matching, holds and releases run on the same engine through the month end spike, with every run recorded.

Every cleared line is evidence

The order line, the receipt, the invoice line, the tolerance applied and its version. That is what an auditor samples.

Credentials are managed, never written into the build

A credential that can clear invoices for payment is one of the most sensitive you hold. It lives in your workspace, scoped and separately rotatable.

AP owns the tolerances

The tolerance table and hold periods open in Tray Build, tuned by the team that works the exceptions.

Nothing clears on a guess

An invoice that could belong to two orders stops for a person. A wrong match pays the wrong order and leaves the right one open.

Questions people ask

What is the difference between two-way and three-way matching?

Two-way matching compares the order and the invoice. Three-way adds the receipt, so you only pay for what actually arrived. Two-way can be a sensible policy for low-value services, as long as it is a choice and not a gap.

How do you receipt services?

Ask the person who ordered them. A short message with the order line and the invoice amount, answered with yes, partly or no, is a receipt an auditor will accept.

What should happen when the invoice arrives before the goods?

Hold it and release it automatically when the receipt is recorded. The goods are usually a day or two behind, and rejecting the invoice means the supplier sends it again.

What tolerance should we set?

Start narrow and set it from real data. Run matching in report-only mode for a month end, see where the differences cluster, and set tolerances by category from that.

Can AP change the tolerances without engineering?

Yes. The tolerance table and hold periods open in Tray Build, so a change in policy is a table edit.

Last reviewed October 2026.