# Syncing with external systems

This guide covers keeping an external system up to date with what happens in Iteras. It could a website with its own user database, an app backend, a data warehouse or a marketing or CRM system. It is written for the case where Iteras has the master data about customers and subscriptions, and the other system keeps a local copy of the parts it needs. If you follow this guide, you should end up with a robust synchronization that just keeps chugging along.

On top of this, we have an extra guide to access control, where your the external system handles users and logins and Iteras decides what each user has access to.

If you are moving existing data into Iteras for the first time, start with migrating data to Iteras instead. This page is about what happens afterwards.

There are two steps to this guide. You need to transfer the full state at first, and you need to keep the state updated when something changes. We start with the changes because we can reuse the code from that to do the full state transfer.

# Getting on top of changes

Changes in Iteras produce events - a customer is created, an address is updated, a subscription changes status. Events exist for customers, subscriptions, vacations and future changes, product assignments, campaigns, payments and more. Each event has a type, a timestamp and an increasing event_id. Keeping another system up to date is essentially a matter of processing the events in order.

There are two ways to get the events and the related data.

# Retrieving events through the API

The /api/events/ endpoint returns events on request. Remember the highest event_id you have processed and ask for everything after it when you need fresh data:

GET /api/events/?created_after_id=84210&types=customer:created&types=customer:updated&types=subscription:changed-status HTTP/1.1

If there are many results, page through them with max_results and the returned next_url.

This is the simplest setup. You don't need to run a service with an open network port on your side, and you control the pace. Use it for a synchronization that runs every night, every hour or every few minutes for a data warehouse or for most website integrations.

# Receiving events as webhooks

Iteras can also deliver the events immediately to your server as they happen. Choose this when changes must be visible in your system right away. Paywall access is a typical case. Setting up a webhook, the delivery format, the required reply and retries are covered under event webhooks.

As described there, the endpoint should check the Authorization header, save the sent information to a queue - a database table, a file or a queue system - and reply with an acknowledgement. Any further processing should be done in a separate task afterwards to ensure the webhook can keep up if there's a burst of changes.

# Processing the changes

The attributes on the events describe what happened, and much of that is internal to Iteras. What a synchronization usually needs is the resulting state of the customer and subscriptions. We have a convenient feature for getting that directly through the prefetch parameter on /api/events/ or with the prefetch parameter in the webhook settings. The prefetch fields are the same ones you would ask the customer data API for, and returned in exactly the same format. For instance with data,subscriptions,subscriptions.current_period every customer or subscription event carries the customer and their subscriptions:

{
  "event": "subscription:changed-status",
  "event_id": 84213,
  "timestamp": "2026-06-28T08:44:16",
  "via": "system",
  "customer_id": "141280",
  "subscription_id": "7612471",

  "prefetch": {
    "customers": [
      {
        "id": "141280",
        "data": { "name": "Jane Doe", "email": "jane@example.com" },
        "subscriptions": [ /* current state, same format as /api/customers/ */ ]
      }
    ]
  }
}

Processing an event then means selecting what fields you want to store and then simply overwriting your local copy of the customer's data with the new state if it has changed. This is an idempotent operation, so an event applied twice does no harm. Of course, you can still use the event's own attributes when you need to know what happened rather than what is true now, e.g. to send a farewell email on subscription:registered-stop.

You may get many events in a burst. An easy way to speed up the synchronization process greatly is to make sure you can write several customers in one storage update so the commit overhead is per chunk instead of per event.

If you process events concurrently, or mix event processing with the full synchronization below, also store the event_id you last applied per customer and skip anything older, so that a slow worker cannot overwrite fresh data with stale data.

# Initializing and maintaining the synchronization

Once you have the code for storing and processing the local state, you're basically set. The only thing left is to get your local database updated with existing customers. For a full synchronization via the API, query the customer data API asking for the same fields you prefetch on events. Grab all customers using pagination, keeping the rate limits in mind, and for each page of customers call the code you just wrote to process a chunk of customers:

url = 'https://app.iteras.dk/api/customers/?fields=data,subscriptions,subscriptions.current_period&max_results=500'

while url:
    result = get_json(url, headers={'X-Iteras-Key': API_KEY})

    update_customers(result['customers'])

    url = result.get('next_url')

Once that is in place, run it once and you're ready for production. If something goes wrong at some point, or you later expand the synchronization to cover more fields, you can always run the full synchronization again.

Keep max_results low while developing anything that pages through results, so the pagination code is exercised before going live.

# Matching customers between the systems

One thing we've glossed over is how you keep track of which customer is which between the two systems. This is not an issue if the external system is just a full or partial copy of the Iteras data, but if it has some state of its own, you need to match customers between the systems.

Our recommendation is to store the Iteras customer ID on your side. The ID assigned by Iteras, does not change, and it is on every customer-related event as customer_id. The API returns it as a string, so store it as one and make no assumptions about its format. An indexed column on the external side can handle lookup from the ID to the local ID.

If the customers have an identity in the external system, you can also store the external ID in Iteras. Set up an extra field on the customer and write your identifier into it whenever you create or first match a customer. Give the field itself machine-readable ID in the field settings in Iteras. With this, the relationship can be followed from both ends: Your Iteras administrators can see which external ID a customer has, and you can look a customer up when all you have is the external ID:

GET /api/customers/?filter=[{"condition_type":"customer:field","field":"external_system_id","operator":"equal","value":"u-90431"}] HTTP/1.1

(URL encoded in practice - see getting started.)

It might be tempting to try to match on something else, e.g. the email address. But people change them, share them within a household and mistype them. Matching on email is reasonable only when first connecting two systems with no shared key - from then on you should establish and match on IDs.

# Checking for drift

One benefit of the plan described in this guide is that you can periodically run a full synchronization to make sure the systems are not drifting apart. Make the code that updates the customer state print to its log when it detects a changed customer. That way it's easier to figure out what might be the cause of a drift, if there is one.

Here are some causes drift:

  • An event type your code quietly ignores. If you're branching, log unknown event types rather than dropping them.
  • Changes in the field setup in Iteras. If the data is restructured, you need to remember to update the integration. Using machine-readable IDs can help if the cause is simply a renamed field.
  • Timezone mistakes. Datetimes in the API are in the Iteras account's timezone unless they carry an explicit offset - see dates and timezones. If you store them as UTC without conversion, subscription dates can land on the wrong day.
  • Concurrency issues with a two-way synchronization, see below.

A crash halfway through a chunk should normally not be a cause, as long as you only advance your queue position after processing. The events are delivered or fetched again, and idempotent processing absorbs the repetition.

Note that a full synchronization is not a replacement for processing events: reading the whole customer base often enough to stay fresh is slow for you and heavy for us. The events give freshness, the full synchronization gives confidence that nothing has slipped through.

# Writing back to Iteras

What've we've described so far is a one-way flow of state from Iteras to the external system. If you need to write back to Iteras, that works well too, but it needs a bit more care than the one-way case.

Give every field exactly one owner and document that in your code. For instance, name, address and email and subscription state is owned by Iteras, and extra external data is owned by the external system. A field with two owners can easily end up with the last writer winning at random. You can add as many fields as you like to the Iteras objects, so you can make Iteras the owner of some external data too, if it's simpler that way.

Beware of loops. A write to Iteras produces an event. But then the event comes back to you. If your handler reacts to it by writing to Iteras again, the two systems keep each other busy and fill the customer's history until something stops them. The way you defend against this is by comparing before writing. Only send or process an update when the value you have actually differs from the existing value.

Let Iteras manage the subscriptions. Starting, stopping, switching plans and renewing subscriptions involves invoicing, payment agreements, cancellation terms and business policies. Iteras allows you and your coworkers to set up all of this, customize it to suit the segments you work with and adjust it over time. So ask Iteras to perform operations through the documented operations and let Iteras work out of the details. The resulting events tell you what happened.

Decide what a deletion means before the first one arrives. customer:deleted comes when a customer is deleted in Iteras. You need to decide whether that should delete the state in your end, anonymize them or mark them inactive.