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
- System Your product
- Step Shared workflows
- Step Customer settings
- Step Customer's account
- System HubSpot
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
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
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
The project is published as a solution
In Tray Embedded the settings become config slots and the account connections become auth slots.
- 4
Each customer gets their own instance
Their answers and their connections, running the same workflows, with their own run history.
- 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
Write down what differs between customers
Before any workflow, list the questions.
Headless skills
build-workflowUse 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
Build the workflows with the settings read, never written in
So a fix reaches every customer.
Headless skills
build-workflowtray-gotchasUse 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
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
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
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.
Related guides
Platform engineering
How to let customers connect their own accounts
Let customers authorise their own apps inside your product, under your brand, with expired connections caught and fixed before a sync stops. The prompts.
Platform engineering
How to build customer field mapping
Let each customer match your product's fields to their own, custom fields included, then turn the integration on with a first sync that doesn't flood their account. The prompts.
Platform engineering
How to roll out integration changes to every customer
Ship a new version of a customer-facing integration to every customer: test it as a customer first, release in waves, and ask for setup again only when it must. The prompts.
Last reviewed October 2026.