Skip to content

Integration  ·  Revenue operations

How to build an order-to-cash integration

The deal is closed-won in the CRM and finance has not heard about it. The thinking behind order-to-cash as a CRM to ERP integration, the prompts that build it, and what it takes to keep it running.

Built with Tray Headless

  1. System Salesforce
  2. Step Validate the deal
  3. Step Sales order
  4. Step Invoice raised
  5. Step Payment applied
  6. System NetSuite

Validation happens in the CRM where the error is readable, and the order write is keyed so a retry cannot create a second one.

The short answer

What is an order-to-cash integration?

Order-to-cash at a software company is a CRM to ERP integration in four parts: a clean handoff from closed-won to a sales order, idempotent writes so a retry never creates a second order, a reconciliation loop that compares both systems rather than trusting the last write, and status flowing back so the account team can see the invoice. Most of these come apart on idempotency. A timeout on the order create is invisible until finance finds two orders for one deal at month end.

What matters here

  • Make every write idempotent with a key derived from the opportunity. A retry must find the existing order, not create a second.
  • Validate before you write. A missing tax code or an unmapped product fails in the ERP, where the error is least readable.
  • Reconcile on a schedule. Event-driven sync is right until one event is missed, and nothing tells you it was.
  • Write status back to the CRM. An account team that has to ask finance whether an invoice went out is the problem this solves.
  • Never let the integration change a price. If the quote and the order disagree, that is a stop and a human, not a resolution.

Who this is for

You run revenue operations or finance systems at a software company. Deals close in the CRM and orders live in the ERP, and the gap between them is currently somebody rekeying, or a nightly job nobody trusts.

How it works in practice

The path from a deal closing to cash being applied against it.

  1. 1

    Closed-won triggers a validation pass

    Products mapped, tax code present, billing account resolvable, currency supported. Nothing is written until the whole thing checks out.

  2. 2

    The sales order is created idempotently

    Keyed on the opportunity id, so a retry finds the existing order rather than creating a second.

  3. 3

    Line items map through a product mapping table

    Maintained as data rather than in code, because the catalogue changes more often than the integration does.

  4. 4

    The invoice reference flows back to the CRM

    So the account team can answer a billing question without opening the ERP or asking finance.

  5. 5

    Payment status is synced as it changes

    Invoiced, part paid, paid, overdue. Renewal conversations depend on knowing which.

  6. 6

    A nightly reconciliation compares both systems

    Every closed-won opportunity in the period against every order, and the difference is a report somebody reads.

What order-to-cash is made of

This is a CRM to ERP integration with money attached. It is four parts, and the second one is what stops the month-end surprise.

A validated handoff

Product mapping, tax code, billing entity and currency all checked in the CRM, where the error is readable, before anything reaches the ERP.

Idempotent writes

An external key derived from the opportunity on every create. Retries are safe, and duplicate orders become impossible rather than unlikely.

A reconciliation loop

A scheduled comparison of both systems. Event-driven sync misses events, and only a reconciliation finds out.

Status flowing back

Order number, invoice number and payment state on the opportunity, so the commercial team is not dependent on finance for an answer.

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

    Set up, then learn both schemas properly

    Every painful order-to-cash bug is a schema assumption.

    Headless skills build-workflow

    Use build-workflow. The systems in play are both Salesforce and
    NetSuite, or whatever we run in those seats.
    
    From Salesforce I need Opportunity, OpportunityLineItem, Account and
    Product2 with their real field names. From NetSuite I need SalesOrder,
    Invoice, Customer and Item, including which fields are mandatory and
    which are internal ids, not names.
    
    Do not assume a mapping between the two. Show me both, and I will tell
    you where they meet.
  2. 2

    Validate in the CRM, before anything reaches the ERP

    Because an ERP validation error is the least readable error in the stack.

    Headless skills build-workflow

    Use build-workflow. Trigger on Opportunity StageName changing to Closed
    Won.
    
    Validate before writing anything:
    
      Every OpportunityLineItem product exists in the product mapping table
      The account resolves to a NetSuite customer, or flag it for creation
      A tax code and a billing entity are present
      The currency is one NetSuite is configured for
      Amounts are non-zero and the total matches the sum of the lines
    
    If any check fails, stop, post to the deal desk channel with exactly what
    is missing, and write nothing. A validation failure in the CRM is a
    five minute fix. The same failure inside NetSuite is a support ticket.
  3. 3

    Write the order idempotently

    The single step that prevents the duplicate order nobody finds until month end.

    Headless skills tray-patterns

    Create the NetSuite sales order with an external id derived from the
    Salesforce opportunity id.
    
    Before creating, look up that external id. If an order already exists,
    update it rather than creating a second one and return the existing
    reference. A timeout on the create must be safe to retry.
    
    Map line items through a mapping table held as data, not in code. The
    product catalogue changes far more often than this integration will, and
    a new SKU should not be a deployment.
    
    Write the NetSuite order number back to the opportunity as soon as it
    exists, so the link is visible from the CRM side immediately.

    Idempotency is the whole game here. Without an external key, one network timeout produces two orders and finance discovers it during close.

  4. 4

    Flow status back, and never change a price

    So the account team stops asking finance, and the integration stays inside its remit.

    Headless skills tray-gotchas

    Use tray-gotchas, then sync status back to the opportunity as it changes:
    order created, invoiced with the invoice number, part paid, paid in full,
    overdue with the days outstanding.
    
    Two rules the integration must not break:
    
      Never change a price, a discount or a quantity. If the quote and the
      order disagree, stop and raise it. That is a commercial conversation,
      not a field update.
    
      Never create a customer in the ERP silently. Flag it for finance to
      create with the right entity, tax registration and payment terms.
    
    Handle the amendment case explicitly: an opportunity edited after close
    must update the order if it is still open, and stop if it has already
    been invoiced.
  5. 5

    Reconcile nightly, because events go missing

    Event-driven sync is right until one event is lost, and nothing announces that.

    Headless skills tray-patterns

    Build a nightly reconciliation over the last 90 days:
    
      Every closed-won opportunity with no matching sales order
      Every sales order with no matching opportunity
      Orders where the total does not match the opportunity amount
      Invoices with no order, and orders invoiced more than once
    
    Report the differences with enough context to act, and alert if the count
    crosses a threshold instead of emailing a zero every morning.
    
    This is the safety net. Event-driven integration is correct until one
    event is dropped, and the only thing that ever finds that is a
    reconciliation that assumes nothing.
  6. 6

    Check it end to end, then hand the mappings to finance systems

    Because the product catalogue is theirs, not the integration author’s.

    Run the per-step schema checks and the whole-workflow audit before this
    touches production.
    
    Then open the same workflow in Tray Build so the finance systems team can
    maintain the product mapping table and the tax code rules in the visual
    canvas. Those change with every catalogue update, and none of them should
    require a coding assistant.

What it connects to

Two systems of record and the money moving between them, plus the places people need to be told.

Salesforce

Read the closed-won opportunity, its line items and the account. Write back the order number, the invoice reference and payment status.

Reads and writes

NetSuite

Create the sales order idempotently, read the invoice and payment state. Usually the system of record for the order itself.

Reads and writes

Salesforce CPQ

Read the approved quote, ramp structure and any negotiated discount, so the order reflects what was actually agreed.

Reads

Stripe

Read payment capture where collection happens outside the ERP, so paid means paid rather than invoiced.

Reads

Slack

Tell the deal desk when a validation fails, and the account team when an invoice goes out.

Writes

Snowflake

Land orders, invoices and their timestamps, so days from close to invoice is a query instead of a spreadsheet.

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 Microsoft Dynamics 365, SAP S/4HANA, Google BigQuery, Microsoft Teams, HubSpot or Oracle.

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 integration moves money between two systems of record. It gets audited.

It runs on the platform, not on your laptop

Retries, backoff and the nightly reconciliation execute on the same engine, with a run history for every order created.

Every write is attributable

Which run created which order, with the platform audit trail behind it. Finance asks this question during close, every quarter.

Credentials live in the workspace, never in the repo

The ERP credential can create financial records. It lives in your workspace, scoped to what the integration needs, and rotatable without a rebuild.

Finance systems own the mappings

The product mapping and tax rules open in Tray Build. Finance systems maintain them, because a new SKU should be bookable the day it is created.

Failures reach a person, not a log

A validation failure blocks a deal from being invoiced. It needs to reach the deal desk the same hour, not appear in a report the next morning.

Questions people ask

Where does this start, if we already have quote-to-order?

At the order. Quote-to-order covers approval, signature and turning a signed quote into a booked order. This picks up from that order and runs to cash: invoice raised, payment applied, and status back on the account. Build either one on its own.

Does order-to-cash need EDI?

Not in this path. EDI belongs to retail and distribution supply chains, where a supplier receives purchase orders from large customers. A software company runs order-to-cash between CRM, CPQ, ERP and a payment system, and no EDI is involved.

Why does idempotency matter so much?

Because a network timeout on the order create is invisible. Without an external key derived from the opportunity, a retry produces a second order, and nobody finds out until finance closes the month.

Why validate in the CRM rather than letting the ERP reject it?

Because an ERP validation error is the least readable error in the stack, and it arrives after the deal is closed. Checking the product mapping and tax code in the CRM turns a support ticket into a five minute fix.

Should the integration ever change a price?

No. If the quote and the order disagree, stop and raise it. Price is a commercial agreement, and an integration silently reconciling one is how a customer gets invoiced for something they did not sign.

Why reconcile if the sync is event-driven?

Because events get dropped and nothing announces it. A nightly comparison of closed-won opportunities against sales orders is the only thing that finds the deal that never became an order.

Vibe-coding app guides

Vibe-code an entire app with Helix

This moves the data between systems. It does not give anybody a screen to work in. Build that app in Claude Code, Codex or Cursor, then deploy and run it governed on Tray Helix. Same kind of guide, same kind of prompts.

How to build an order management tracker (opens helix.tray.ai in a new tab)

Last reviewed September 2026.