# Before you integrate: built-in capabilities and sources of truth

The cheapest integration is the one you don't build. Before you choose an API, a runtime, or a Connector, answer two questions: does commercetools already do this, and which system owns the data?

This module is the home of the commercetools integration model. The integration guides and the API reference link here for the reasoning behind each decision, and this module links out to them for field lists and implementation steps.

Throughout the module, you follow the platform team at Zen Electron as it turns a working commercetools Project into a production integration landscape. The team has a list of integration requests from both brands. This page applies those two questions to that list.

## Every integration is a standing cost

An integration is not a one-off project. After it goes live, it's a pipeline that your team owns. It has the following ongoing costs:

- **Operation**: someone monitors it, responds to failures, and upgrades it when either system changes.
- **Security**: it holds credentials and API Client scopes that must be rotated and kept to the minimum.
- **Consistency lag**: data copied between systems is eventually consistent, so each system can briefly show a different value.
- **Operational dependency**: if the integration is in a customer-facing path, its availability becomes part of your storefront's availability.

These costs are worth paying when the business needs a capability that commercetools doesn't have. They are not worth paying to rebuild a capability that commercetools already provides.

## Check for a built-in capability first

For three common requirement areas, commercetools offers a built-in capability that often removes the need for an integration. Rule these out explicitly, and record why, before you design an integration.

| Requirement area | Built-in capability | What can justify an external system |
| --- | --- | --- |
| Search and discovery | [Product Search](/api/projects/product-search.md) | A discovery-platform requirement that Product Search can't meet. See [When an external search engine is justified](/guides/search-integration.md#when-an-external-search-engine-is-justified). |
| Promotions | [Product Discounts](/api/projects/productDiscounts.md), [Cart Discounts](/api/projects/cartDiscounts.md), [Discount Codes](/api/projects/discountCodes.md), and [Discount Groups](/api/projects/discount-groups.md) | A promotion engine that must remain the system of record for offers. See [Rule out the built-in discounts first](/guides/promotion-loyalty-integration.md#rule-out-the-built-in-discounts-first). |
| Shipping | [Shipping Methods](/api/projects/shippingMethods.md) | A requirement that Shipping Method modeling can't express. See [Rule out built-in Shipping Methods first](/guides/shipping-integration.md#rule-out-built-in-shipping-methods-first). |

Some domains have no built-in equivalent, so an integration is expected:

- **Payments**: commercetools tracks the state of a Payment, but a payment service provider (PSP) moves the money.
- **ERP, PIM, CRM, and OMS**: these systems own data or processes that commercetools consumes or feeds.

Whether tax needs an external engine depends on the jurisdictions you sell in. For the options, see [Integrate an external tax service](/guides/tax-integration.md).

### Capabilities that are often mistaken for commerce analytics

Analytics requests often get routed to the wrong tool. Three commercetools capabilities sound like analytics but serve other purposes:

- **[Platform Insights](/api/platform-insights.md)**: measures how the API performs, not what customers buy.
- **[Change History](/api/history/overview.md)**: records who changed what, so it's a governance log rather than a data feed.
- **Client-side behavioral tracking**: belongs in the storefront and never passes through commercetools.

For the limits of each, see [Confirm you need a commerce data export](/guides/analytics-export-integration.md#confirm-you-need-a-commerce-data-export).

commercetools has no Export API. To move commerce data, such as Orders, Customers, and Products, into a warehouse, you build an export integration. The analytics guide covers the pattern.

## Assign a source of truth to each data domain

When an integration is needed, decide which system is the source of truth for each data domain before you decide how data moves. The source of truth is the only system where that data is created or edited. Every other system holds a copy.

Follow these rules:

- **One source of truth per domain.** If two systems can both edit the same data, you need a conflict-resolution strategy that someone has to own forever.
- **Never sync the same field in both directions.** A bidirectional sync of one field creates update loops and last-write-wins conflicts.
- **Two one-way flows for different domains are fine.** Product content flowing in from a PIM and Orders flowing out to an OMS is not bidirectional sync.
- **Aggregated resources need attribute-level ownership.** When two systems feed one Product (for example, content from a PIM and stock-related attributes from an ERP), assign each Attribute to exactly one source. See [Multiple sources of product data](/guides/product-data-integrations.md#multiple-sources-of-product-data).

For the typical owner of each data domain, see [Plan integrations](/guides/implementation-guide/plan-integrations.md).

Orders follow a common pattern. commercetools captures the Order at checkout, and a downstream OMS or ERP usually becomes the source of truth after export. If you have no OMS or ERP, commercetools can remain the source of truth for Orders.

## Link records across systems

After two systems hold copies of the same record, each needs a way to find the other's copy. commercetools offers the following mechanisms:

| Mechanism | Available on | Use it when |
| --- | --- | --- |
| `externalId` | Customer | One external system (typically the CRM) owns the Customer identity. |
| `syncInfo` | Order | You need to record that an Order was sent to, or received from, a specific system, and when. |
| Custom Field | Most resources | You need a link that the built-in fields don't cover, such as a second external ID. |

For the alternatives to `externalId` on a Customer, see [Compare Customer link fields](/guides/crm-integration.md#compare-customer-link-fields). For why an Order needs `syncInfo` instead of `externalId`, see [Store the OMS identifier on the Order](/guides/oms-integration.md#store-the-oms-identifier-on-the-order).

### Link a Customer to its CRM record

Set the CRM identifier on an existing Customer with the `setExternalId` update action:

```http title="Request: set the CRM identifier on a Customer"
POST https://api.australia-southeast1.gcp.commercetools.com/zen-electron/customers/key=eht-cust-10482
Authorization: Bearer {access_token}
Content-Type: application/json

{
  "version": 3,
  "actions": [
    { "action": "setExternalId", "externalId": "CRM-0048213" }
  ]
}
```

```json title="Response excerpt"
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "key": "eht-cust-10482",
  "version": 4,
  "email": "jane.doe@example.com",
  "externalId": "CRM-0048213"
}
```

- `externalId` holds one value. If a second system also needs a reference, store it in a Custom Field.
- You can also set `externalId` when you create the Customer.

### Record that an Order was exported

[SyncInfo](/search.md?urn=ctp:api:type:SyncInfo) records an Order's synchronization activity, and each entry references a [Channel](/api/projects/channels.md) that represents the external system. The [Update SyncInfo](/search.md?urn=ctp:api:type:OrderUpdateSyncInfoAction) action restricts which Channel roles it accepts, so create the Channel once per Project before the first export:

```http title="Request: create a Channel for the OMS"
POST https://api.australia-southeast1.gcp.commercetools.com/zen-electron/channels
Authorization: Bearer {access_token}
Content-Type: application/json

{
  "key": "zen-oms",
  "roles": ["OrderExport"],
  "name": { "en-AU": "Zen Electron OMS" }
}
```

After the OMS accepts an Order, record the OMS identifier with the `updateSyncInfo` update action:

```http title="Request: record the export on the Order"
POST https://api.australia-southeast1.gcp.commercetools.com/zen-electron/orders/order-number=EHT-2026-000917
Authorization: Bearer {access_token}
Content-Type: application/json

{
  "version": 7,
  "actions": [
    {
      "action": "updateSyncInfo",
      "channel": { "typeId": "channel", "key": "zen-oms" },
      "externalId": "OMS-77120394",
      "syncedAt": "2026-10-02T03:14:00.000Z"
    }
  ]
}
```

```json title="Response excerpt"
{
  "orderNumber": "EHT-2026-000917",
  "version": 8,
  "syncInfo": [
    {
      "channel": {
        "typeId": "channel",
        "id": "b7c2e1d0-4a5f-4e3b-9c8d-2f1a6e7b8c90"
      },
      "externalId": "OMS-77120394",
      "syncedAt": "2026-10-02T03:14:00.000Z"
    }
  ]
}
```

- The request identifies the Channel by `key`, but the response returns a reference by `id`.
- To find the OMS identifier, read the `syncInfo` entry for the OMS Channel.

## Worked example: Zen Electron's ownership map

Zen Electron's platform team starts by assigning a source of truth to each data domain. The result is the following ownership map:

| Data domain | Source of truth | Flows into commercetools | Flows out of commercetools |
| --- | --- | --- | --- |
| Product content (descriptions, images, specifications) | PIM | Yes | No |
| Stock levels | ERP | Yes | No |
| Prices | commercetools | No | No |
| Customers | commercetools | No | Yes, to the CRM |
| Orders and fulfillment status | commercetools at checkout, then the OMS after export | Yes, fulfillment status updates | Yes, new Orders to the OMS |

Two systems feed the same Products: the PIM and the ERP. This is the first integration on Zen Electron's list: the catalog and stock feed. Attribute-level ownership keeps it safe: the PIM owns content Attributes, the ERP owns stock, and neither writes the other's fields.

Next, the team runs the built-in-first check on three requirements:

1. **Zenith Living search**: customers search and filter a Store-scoped catalog. Product Search covers this, so no integration is needed.
2. **Electronics High Tech merchandised search**: the merchandising team wants to tune ranking and run ranking experiments in a dedicated tool. This justifies an external search engine, so it goes on Zen Electron's list of integrations to plan later in this module.
3. **Same-day metro delivery**: Electronics High Tech offers same-day delivery only to specific Australian metro postcodes. A Shipping Method predicate can handle this without an integration by listing the postcodes:

   ```text title="Shipping Method predicate: list the eligible postcodes"
   shippingAddress.country = "AU" and shippingAddress.postalCode in ("2000", "2010", "3000", "3004")
   ```

   The predicate needs no integration, but someone maintains the postcode list by hand in the Shipping Method. If the eligible postcodes change often, or come from another system, the alternative is an eligibility service that sets a Custom Field on the Cart, which the predicate then checks:

   ```text title="Shipping Method predicate: rely on a Custom Field"
   custom.isMetroArea = true
   ```

   That alternative is an integration for same-day metro delivery eligibility. Whether it's worth building depends on how often the list changes. [Choose a connection point](/learning-integrate-with-commercetools/integration-essentials/choose-a-connection-point.md) compares the ways to set that Custom Field.

## Key takeaways

- An integration is a standing cost in operation, security, consistency, and availability. Check for a built-in capability first.
- Product Search, the Discount family, and Shipping Methods are the most common built-in alternatives to an integration.
- Platform Insights and Change History are not commerce-data exports, and commercetools has no Export API.
- Assign one source of truth per data domain. Never sync the same field in both directions, and use attribute-level ownership when two systems feed one resource.
- Link Customers with `externalId`. Link Orders with `syncInfo`, which requires a Channel with the role `OrderExport` or `OrderImport`, or with a Custom Field.

## Related pages

- [Area overview page with navigation](/learning-integrate-with-commercetools.md)
- [Previous page: Overview](/learning-integrate-with-commercetools/integration-essentials/overview.md)
- [Next page: How integrations fit together](/learning-integrate-with-commercetools/integration-essentials/how-integrations-fit-together.md)
- [Search documentation and API specs](/search.md)