maesn
Use case

Sync customer and supplier data without guessing which records match

Both sides already hold the company. Your product knows it as one record, the accounting system knows it as another, and nothing in either of them says the two are the same. Maesn delivers what each side holds, in one shape, so your rules can decide.

In your product
Müller GmbH
info@mueller.de
In their ledger
Mueller G.m.b.H.
buchhaltung@mueller.de
Same company?
The guide matches on
  • vatId2 of 20
  • email20 of 20
  • companyName20 of 20

Only the first one is an identifier, and it is documented on 2 systems. On the rest you are matching companies by name and email.

Trusted by winning software teams
HubSpotTipaltiPaywiseRallyQredNordhealthFindityFintoClockinProvetHeroHolviLanes & PlanesHubSpotTipaltiPaywiseRallyQredNordhealthFindityFintoClockinProvetHeroHolviLanes & Planes
The problem

Two records, one company, and no key to prove it

Moving contact data between two systems is a solved problem. Knowing which contact is which is not, and the documented flow spends its middle step on exactly that.

Documented on the twenty systems that expose a customer record
vatIdidentifierA registered identifier. Two companies cannot share one.2 of 20
emailOften a shared inbox, and one company can have several.20 of 20
companyNameA string a human typed. Two spellings are two values.20 of 20
The guide names all three in one breath, and only one of them is a key. On eighteen of the twenty systems the identifier is not in the documented field list, which leaves a name and an email address to decide whether two records are one company. That is a matching problem with a tolerance, not a lookup, and where the tolerance sits is a decision about your data rather than about the connection.

Every other use case in this family starts with a document. This one starts with an identity question, and it is the reason contact sync is usually the first integration a team builds and the one they revisit longest. An invoice references a customer. An expense references a supplier. If the two sides disagree about which company that is, everything downstream is wrong in a way that is quiet rather than loud.

The documented flow names three fields to reconcile on, and they are not equivalent. A VAT identifier is registered, so two companies cannot share one, and matching on it is a lookup. An email address is often a shared inbox and a company can have several. A company name is a string somebody typed, where a legal form written out in full and the same one abbreviated are the same company to a human and two different values to a comparison. The identifier is the one that would settle it, and it is in the documented field list of two of the connected systems.

So on almost every system this is a matching problem with a tolerance rather than a key lookup, and the tolerance is a product decision. Match too loosely and two customers merge into one. Match too strictly and your users get a second copy of a company they already have, in a system where the next invoice will then reference the wrong one. Neither failure announces itself.

How Maesn solves it

One connection, both routes to a contact

Contact records do not arrive the same way everywhere, and which route a system needs is knowable in advance rather than at the first empty response.

GET /customers + /suppliers20 systems

Two lists, kept apart the way most accounting systems keep them.

GET /contacts8 systems

One list, on the systems that hold customers and suppliers together.

The routes are not a preference, and the second one is not the small version of the first. On five systems the combined endpoint is the only way in bexio, Moneybird, odoo, Procountor, SnelStart — so a product that implements the obvious route and stops there loses them without an error to notice. Across both routes the reach is twenty-five of the twenty-nine connected systems.

Whichever route a system uses, what comes back is the same normalized record: a company name, an address, a contact type, an email, under the same field names on every system your customers run. That is what makes a matching rule something you write once instead of once per vendor, and it is the substance of the phrase one connection, since the customer authorises all of it once.

Keeping the two sides aligned afterwards is the half that usually turns into a project, and here it is a query parameter. Asking only for what changed since your last run means a nightly sync stays cheap as your customers' contact lists grow, and where a system emits events, one webhook subscription tells you rather than you asking. The documentation names the systems that support events, and it is a minority of them, so the read is the mechanism that always works and the event is the one that makes it prompt.

Where a system carries something the shared model has no field for, and on contact data that happens often because identifiers differ by country, reaching past the model is documented rather than improvised. What none of this does is decide what counts as a match. A layer that merged two records on its own judgement would be making a call about your customers' data that it has no standing to make.

What you get

A round trip that goes as far as the system allows

This is the one use case in the family where data genuinely moves in both directions, so the version that holds of the promise is a measurement rather than an adjective. Each step below is a subset of the one above it.

GETRead their record20
POSTCreate what they are missing16
PUT / PATCHCorrect what has changed11
DELETERemove a record0
Where a two-way sync becomes a one-way import

On five systems you can create a customer and never update one: Abacus, Pennylane, Sage Active, Twinfield, Xentral. The record lands once and then the two copies drift apart, because the correction the guide describes has nowhere to go. That is worth knowing before you promise a customer that their data stays aligned, and it is not visible from the create count alone.

Reading a customer record is the best covered operation in the entire model, which is why this use case is a reasonable place to start an integration rather than a hard one to finish. Writing narrows it, correcting narrows it again, and deleting is not available on any object. That last one reads worse than it is: accounting systems expect a customer with history to be deactivated rather than removed, and corrections to arrive as new documents. It does mean a duplicate you create is not something you can quietly delete afterwards, which puts the weight back on the matching step.

What you get from the connection, then, is a full picture of what the other side holds and the ability to act on it as far as that system permits, without a per-vendor project behind each verb. What you do not get is a decision. The guide describes resolving conflicting information as your work, and the numbers above are the reason that framing is right: on a system that cannot update, the resolution has to happen before the write rather than after it. Once the contacts agree, creating an invoice and posting an expense both reference a company that means the same thing on both sides, which is the whole reason their guides open here.

Proof

One company, the same company in both systems

HubSpot keeps client records aligned with DACH accounting systems.

We integrate German accounting systems and rely on Maesn as a trusted enabler - ensuring seamless data flow, full compliance, stronger partnerships, and efficient, reliable financial processes that support our growth.
Aron Gosselin
Partnerships Manager, HubSpot

HubSpot is this use case at the scale where the matching question stops being theoretical: client data, invoices and payment reminders moving between a CRM and the accounting systems their DACH customers actually run, for hundreds of those customers. The systems their case study names are sevdesk, Lexware Office and DATEV, three systems that disagree about almost everything except that a customer record has a name and an address. That disagreement is what a shared model absorbs, and it is why the interesting work stays with the rules rather than the plumbing.

Key facts

Every operation on a contact, counted per system

Customers and suppliers on all three operations, then the combined endpoint, counted from the per-system documentation.

Across the 29 connected systems
What you needTodayOn demandSystem cannot
Customers, reading2027
Customers, writing1649
Customers, updating11810
Suppliers, reading1928
Suppliers, writing14411
Suppliers, updating11711
Combined list, reading8417
Read the first three rows downwards and the shape of a sync appears: reading a customer is the best covered operation in the whole model, writing one is well covered, and correcting one is where it thins out. Suppliers follow the same curve a step behind on reading and writing, and level with customers on updating. The last row is the combined endpoint, and its count is low for a reason that is not a gap: most systems keep customers and suppliers apart, so the route that merges them only exists where the system itself does.

The table is the answer to the question that decides how much of a sync you can promise, which is not whether contact sync works but how deep it goes on the systems your own customers run. Customers sit ahead of suppliers on reading and on writing, and the two are level on updating, which matters if your product is built around the payable side: the first half of the feature is slightly thinner there, and a bill still needs its supplier to exist first.

The last row is the one people misread. A low count on the combined endpoint is not a coverage gap, it reflects how the systems themselves are built: most keep customers and suppliers as separate lists, so a merged route only exists where the underlying system merges them too. Counting the routes together rather than separately is the only number that describes reach honestly, which is why the section above does it that way.

Where the line runs

We deliver the records, you decide what matches

On this use case the boundary is unusually sharp, because the documented flow puts the decision in the middle and names it as yours.

What Maesn delivers
  • Both routes to a contact record
  • One shape on every system
  • Writes and corrections back
  • Only what changed since last time
What stays your product
  • Which records are one company
  • How close a match has to be
  • Which side wins a conflict
  • When the sync runs

Everything on the left is the connection: two documented routes into a contact list, the same record shape whichever system the customer runs, writes and corrections in the other direction as far as that system allows, and a cheap way to ask what changed. That is the part which would otherwise be one integration per accounting platform, each with its own idea of what a customer is.

Everything on the right is your product, and on this use case it is the substance rather than the trimming. Whether two records are one company, how close a match has to be before you act on it, which side wins when both have changed, and how often any of it runs are decisions that depend on your data and your users' expectations. The documentation calls this resolving conflicting information and leaves it with you, which is the right place for it.

One thing belongs here because the page it replaces put it in a headline. Maesn does not promise that a record can never be written twice. What prevents a duplicate is the reconciliation step, done by your product before the write, and the lastModified filter is what keeps the copy you match against current enough for that decision to be a good one.

Matching a counterparty before the write is plumbing, and it is the kind owned by product and engineering teams, not by the accounting system on either end.

Contact sync FAQ

Common questions

What does customer and supplier data sync actually involve?

Reading the records that already exist in your customer's accounting system, matching them against the ones in your product, creating what is missing on either side, and correcting what has changed. The documented flow puts reconciliation in the middle rather than at the end, which is the right order: everything after it depends on having decided which records are the same record.

Should we read /contacts or /customers and /suppliers?

Whichever the system in front of you supports, and that is genuinely per system rather than a matter of taste. Most accounting systems keep customers and suppliers apart and expose them as two lists, which is the route with the widest reach. A handful hold them together and expose one combined list instead, and on those the combined endpoint is the only way in. Building both is the only way to reach everything.

How do we decide that two records are the same company?

Your product decides, and the documentation is honest that it hands you the fields rather than the answer. It suggests matching on the VAT identifier, the email address and the company name. The first of those is a real identifier and it appears in the documented field list of two systems, so on the rest you are comparing a name and an email address. Where you set the tolerance, and what happens to a near miss, is a decision about your data.

Can we correct a record in the customer's system, or only create one?

Both, on most systems, and the difference matters more here than on any other use case. Reading a customer is supported on twenty systems, creating one on sixteen, and updating one on eleven. On five of the systems that accept a new customer there is no documented update, so the record lands once and the two copies drift from there. That is a per-system property worth checking before you promise that data stays aligned.

Do we get told when something changes on their side?

On the systems that emit events, yes. Maesn exposes one unified webhook subscription rather than one integration per system, and the documentation names the systems that support them: there are nine, and six of them publish events for customer records. Everywhere else the answer is to read what changed since your last run, which is a query parameter rather than a project.

How do we avoid re-reading everything on every sync?

A lastModified parameter on every GET endpoint returns only what changed since the timestamp you pass. That is the mechanism behind keeping two systems aligned without building delta logic of your own, and it works the same way on every connected system, which is the part that would otherwise be per-vendor work.

Can we delete a contact through the API?

No object in the model has delete enabled today. That is less of a restriction than it sounds for accounting data, where a customer with history is normally deactivated rather than removed, and where corrections are expected as new documents. It does mean that cleaning up a duplicate you created is not a delete call, so it is better not to create one.

What stops the same record being written twice?

Your matching does. The documented flow has you reconcile before you write, which is exactly the step that prevents a second copy. Nothing in the documentation describes a de-duplication guarantee on our side, so the safe assumption is the ordinary one: decide what is already there, then write only what is not.

What if a system holds a field the shared model does not carry?

There is a documented route for that, and on this use case it comes up often, because identifiers vary by country. A raw data flag returns the target system's own payload alongside the normalized record, so you can see what it actually holds, and a native call through the pass-through endpoint reaches the system directly on the connection you already have.

Is this the same contact sync the other use cases refer to?

Yes, and that is why it is worth building first. The receivable guide and the invoice guide both open with it, and the expense guide names it as the step that puts suppliers in place. An invoice references a customer and an expense references a supplier, so the record has to exist on both sides and mean the same thing before any of those flows can be correct.

Build once on the Unified API.

See how contact sync works for your integration, or dive into the technical reference.