Skip to content

Integration  ·  Platform engineering

How to build one integration for every customer

Each new customer gets a copy of the integration with their own tweaks, and a year later one bug fix has to be made four hundred times. How to build it once with the differences pulled out as settings, the prompts that build it, and what it takes to run it for every customer.

Built with Tray Headless

  1. System Your product
  2. Step Shared workflows
  3. Step Customer settings
  4. Step Customer's account
  5. System HubSpot
Also Slack

One set of workflows serves every customer. What differs between them lives in settings the customer fills in, never in a copy of the logic.

The short answer

How do you build one integration for every customer?

An integration you can offer every customer has four parts: one set of workflows that holds all the logic, every choice that differs between customers pulled out as a setting (which pipeline, which fields, which channel), each customer's own account connections kept separate from everyone else's, and a test customer you can run the whole thing as before anyone else sees a change. In Tray Embedded the workflows are grouped into a project and published as a solution, the settings become config slots and the connections become auth slots. What sinks most attempts is a customer-specific tweak written into the logic. It works once, and then every later change has to remember it.

Stage 2 of 7: Build once for every customer. Part of Embedded integration, end to end : every stage, the systems it runs on and the guide that builds it.

What matters here

  • Write the logic once. Anything that differs between customers is a setting, never a copy.
  • Decide the settings before building. Each one is a question every customer will have to answer.
  • Keep each customer's connections separate. One customer's account must never run another customer's sync.
  • Give every setting a sensible default. The fewer questions a customer has to answer, the more of them finish setup.
  • Run as a test customer before every release. It is the only way to see what a customer sees.

Who this is for

You lead product or engineering for integrations at a software company. Each integration is built per customer or per deal, and changing one means changing every copy.

How it works in practice

How one integration ends up running for hundreds of customers without hundreds of copies.

  1. 1

    You build the workflows once

    The triggers, the calls to your product's API, the calls to the other app, and the error handling, in one project.

  2. 2

    Every per-customer choice becomes a setting

    Which object to sync, which fields, which direction, how often. Each is a question the customer answers, with a default.

  3. 3

    The project is published as a solution

    In Tray Embedded the settings become config slots and the account connections become auth slots.

  4. 4

    Each customer gets their own instance

    Their answers and their connections, running the same workflows, with their own run history.

  5. 5

    A fix is made once

    And reaches every customer when the new version is published, without anyone editing four hundred copies.

What a reusable integration is made of

Four parts. The second decides how long setup takes for every customer.

One set of workflows

All the logic in one project: what triggers it, what it reads, what it writes, what happens when a call fails.

Settings, not copies

Every choice that differs between customers pulled out as a question with a default, from the object to sync to the alert channel.

Separate connections

Each customer authorises their own accounts, and the integration only ever runs with that customer's connections.

A test customer

An instance you set up yourself, with test accounts, so every change is run end to end before a customer sees it.

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

    Write down what differs between customers

    Before any workflow, list the questions.

    Headless skills build-workflow

    Use build-workflow. The integration connects our product to HubSpot,
    with Slack for alerts, or whatever app we are integrating with. Before
    building, tell me which of them are authenticated in the workspace.
    
    Look at the three customers who use this integration most heavily and
    list every way their setups differ: which HubSpot objects, which
    pipelines, which fields, which direction, how often, where alerts go.
    
    For each difference, propose a setting with a plain question the
    customer will see and a default that suits most of them. Anything that
    is the same for everyone stays in the logic.
  2. 2

    Build the workflows with the settings read, never written in

    So a fix reaches every customer.

    Headless skills build-workflow tray-gotchas

    Use build-workflow and tray-gotchas. Build the workflows as one project.
    Wherever the logic needs a customer's choice, read it from project
    config data, never from a value typed into a step.
    
    Handle the cases that vary between customers' accounts: a custom field
    that doesn't exist, a pipeline that was renamed, an API limit that is
    lower on a smaller plan. Each one should stop with a clear message the
    customer can act on, not a step error.
    
    Keep a field on every record written that says which run wrote it, so a
    customer's question about a record can be traced.
  3. 3

    Publish it as a solution

    The settings become what the customer fills in.

    In Tray Embedded, turn the project into a solution. Each config data
    value becomes a config slot and each authentication becomes an auth
    slot.
    
    For every slot, write the label and help text the customer will read,
    in our product's words, not Tray's. Order the screens so the customer
    connects their account first, then answers the questions that depend
    on it, like which pipeline.
    
    Hide any slot whose default is right for everyone. Each visible
    question is one more place a customer can give up.
  4. 4

    Run it as a test customer

    The only way to see what a customer sees.

    Create a test end user, set up an instance of the solution through the
    same screens a customer will use, with a test HubSpot account, and run
    it end to end.
    
    Check what happens when a required field is missing in the customer's
    account, when their token is revoked halfway through, and when they
    change a setting after the first run.
    
    Keep this test customer for good. Every new version runs through it
    before it is published.

    A test customer set up through the real screens finds the confusing questions. Testing the workflow directly never does.

What it connects to

One side is your product's API. The other is whatever app your customers asked for.

HubSpot

The customer's own account: read and write the records the integration syncs, with their connection and their settings.

Reads and writes

Salesforce

The same shape works for any CRM a customer asks for. Build it as its own solution with its own settings.

Reads and writes

Slack

Send alerts to the channel the customer chose during setup, and to your support team.

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, Microsoft Teams, Marketo, Google Chat or Braze.

Connections in this build

Field mapping, templates and common problems for each pairing: HubSpot + Salesforce, HubSpot + Slack and Salesforce + 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

Every customer who turns this on runs it. A bug in it is a bug for all of them.

It runs on the platform, per customer

Each customer's instance runs on its own, with its own run history, so one customer's busy day doesn't hold up another's.

Customer connections are kept apart

Each customer authorises their own accounts. The integration runs with that customer's connection and nobody else's.

Credentials are never in the build

Customers' tokens are stored by Tray against their own user, never in a workflow or in your code.

One change, every customer

A fix is made once in the shared workflows and reaches every customer when the new version is published.

Questions people ask

What should be a setting and what should be logic?

If it differs between any two customers, it is a setting. If it is the same for everyone, it is logic. Look at your three most different customers and list how their setups differ.

What if one big customer needs something nobody else does?

Add it as a setting that is hidden or off by default, rather than a copy of the integration. A copy is cheap on day one and expensive every day after.

How many settings is too many?

Every visible question is a place a customer can give up. Give each a default, hide the ones nearly everyone leaves alone, and watch where customers stop during setup.

Does each customer get their own copy of the workflows?

Each customer gets their own instance with their own settings, connections and run history. The logic underneath is shared, so a fix to it reaches every instance.

Last reviewed October 2026.