One unified webhook model for every system you connect
Some systems push events. Many push nothing at all. Maesn gives you one webhook to subscribe to, one payload shape to handle and one signature to verify, and does the polling itself wherever a system stays silent.
{
"eventType": "CREATED",
"resource": "INVOICE",
"resourceId": "bca91f06…"
}What are unified webhooks
A webhook is the difference between asking and being told. Instead of calling an API on a schedule to see whether anything changed, you register a URL once and the system calls you when something does. For integrations that keep data in sync, that is the difference between minutes of staleness and a few seconds, and between constant traffic and traffic only when there is news.
The catch is that webhooks are the least standardised part of any API. Support is uneven, the payloads disagree, and the vocabulary for a created object differs from system to system. Unified webhooks collapse that into one subscription model and one event body, delivered through the same normalised data model as the rest of the Unified API. You write one handler, and it keeps working as your customers bring new systems.
Events are the least standardised part
Every system answers the same four questions differently, starting with whether it will tell you anything at all. Build against them directly and each integration needs its own listener.
App-based or user-based, and it changes your code
Where webhooks do exist, ERP and accounting vendors have settled on two incompatible models. Which one a system uses decides how much routing and isolation you own.
App-based
- All events from all connected users arrive at a single endpoint
- Each payload carries a user identifier you have to route on
- Common where a vendor runs a large app marketplace
- Puts routing, validation and user isolation on your side
User-based
- A subscription is created per user and per event type
- Delivery target is explicit rather than shared
- Common in ERP and mid-market systems with stricter isolation
- More to manage, clearer boundaries between customers
Payloads split the same way. Some systems send a ping and pull signal: the event says which object changed and you fetch the object itself in a second call. Others put business data straight into the payload, which saves a call and grows the thing you have to validate. In practice even generous payloads tend to miss a field a real use case needs, so the second call happens anyway.
Maesn standardises on ping and pull for every event, native or not. The event names the object, you read it through the Unified API, and the payload never becomes a place where customer data sits in transit. That is a deliberate security property as much as a consistency one, and it is what makes one handler viable across both vendor models.
Which systems push events, and which stay silent
For ERP and accounting systems that support custom webhooks and triggers: a minority do, and the majority never will.
Events arrive over a persistent connection rather than as discrete HTTP requests, which has to be kept alive, reconnected and scaled per customer. Maesn holds it and emits ordinary unified events from it.
Support exists but follows Xentral's own conventions and is still in beta, so the shape can change without notice. A change on their side is ours to absorb rather than yours to track.
This is the majority, and it includes the systems most in demand in the German market. Which is why “does system X support webhooks” is the wrong question to build an architecture on: the answer is usually no, and it changes without telling you.
All 27 deliver unified events through Maesn, in one model with one payload shape, whether the system pushes natively or Maesn polls it and emits the event itself.
Two entries deserve a note. Fortnox streams change events over websockets rather than webhooks, which sounds equivalent and is not: a persistent bidirectional connection has to be kept alive, reconnected and scaled per customer, where a webhook is a stateless HTTP request you can secure and operate at volume. Xentral has a webhook model of its own, still in beta. Both reach you as ordinary unified events.
The table is also the reason a build-versus-buy estimate for webhooks is usually wrong. Support is not the question you can answer once per vendor. It is a question per object, per model and per payload style, and the answer changes when a vendor ships a release.
Subscribe once, handle one event
One endpoint creates a subscription for one customer. From then on the events arrive at your callback URL in the same shape, whichever system they came from.
Where events go
The endpoint on your side that receives every event for this subscription, as a POST request.
What to hear about
Subscribe to objects being created, updated or deleted, using one vocabulary on every system.
Which objects
The resource you want to watch, an invoice or a customer for example, named the same everywhere.
One webhook type
A single unified webhook covers every system, so you write one handler instead of one per integration.
Synthetic webhooks
For systems that push nothing natively, Maesn polls on your behalf and emits the same event, so the gap never reaches your code.
Subscriptions per customer
Each subscription belongs to one customer, so their events arrive through their own subscription and stay separable.
Signed events
Every event carries an HMAC-SHA256 signature over the raw body, so you can prove it came from Maesn before you trust it.
POST /webhooksx-api-key: YOUR_API_KEYx-account-key: CUSTOMER_ACCOUNT_KEY{"callbackUrl": "https://your-app.com/events","eventType": "CREATED","resource": "INVOICE"}
Events from systems that push nothing
This is the part that does not exist without a unified layer. When a system has no native webhooks, or only covers a fraction of its objects, the usual answer is that you build a poller: a schedule, a cursor per customer and per resource, change detection, and a queue to smooth out the load. It works, and it becomes permanent infrastructure you own forever.
Maesn runs that poller instead, and turns the result into a normal event. You subscribe the same way you would to a native webhook, and you receive the same body with the same eventType, resource and resourceId. Nothing in your handler distinguishes the two, which is the point: whether a system pushes events stops being an input to your architecture. The polling that makes it work is paced by asynchronous processing, so it stays inside each system's limits without you scheduling around them. You will also see these called virtual webhooks; it is the same idea under a different name.
The gap they close is not only whole systems, it is objects. Native webhook support is usually widest on the sales side, invoices and sales orders, and thinnest on purchasing, where a purchase order often has no event at all. That leaves you running webhooks and polling side by side inside the same integration, for the same customer, with two code paths that fail differently. Synthetic events remove that split: an object without a native event arrives the same way as one with.
You subscribe once and receive events. The polling, the scheduling and the change detection stay on Maesn's side, and the event body is the one you already handle.
A callback URL is public, so every event is signed
Delivering events everywhere solves coverage. It does not solve trust, and the endpoint you just published is reachable by anyone who learns the URL.
An unauthenticated webhook endpoint is an open door: anyone who learns the URL can post a forged event to it, and a handler that acts on what it receives will happily book it. Every event Maesn delivers, native or synthetic, carries an X-MAESN-SIGNATURE header computed over the raw body, so your endpoint can establish that the event came from Maesn before it does anything with it.
Recompute over the raw body
Use the secret you received when the webhook was created, and do it before you parse the JSON. Reserializing first changes the bytes and the signature will never match.
expected = hmacSha256(webhookSecret, rawBody)
Compare in constant time
A plain equality check can leak information through timing. Comparing both hex strings in constant time is a small, free hardening step.
isValid = timingSafeEqual(expected, header) // not: expected === header
Key the work idempotently
Networks retry, so the same event can arrive twice. Key your processing so that handling it again is harmless, and a duplicate delivery stops being a duplicate booking.
key = `${eventType}:${resource}:${resourceId}`Taken together these are one posture rather than three features. Ping and pull means a delivered event carries identifiers and not business data, so even an intercepted event exposes very little. The signature means you can trust an event before acting on it. And because Maesn keeps no copy of the data it routes, the platform in the middle is not a second place your customers' records live. For anyone who has to answer how this is secured, the answer is the same at every layer, which is also the point of the wider security posture.
Told when the write actually finishes
Not every system answers a write immediately. Some accept the object, queue it and produce the outcome later, which normally leaves you polling for your own result.
Maesn can close that loop with a webhook: when the system produces the outcome, you get an event telling you whether the write succeeded or failed. It exists because customers asked for it, which is the honest reason most of this layer exists. Every request, response and event along the way is recorded, so when something does go wrong you can see what was sent and what came back through logging and monitoring.
One listener, every system
- Learn each system's event model
- Poll the systems that send nothing
- Turn pings into real payloads
- Verify each scheme differently
- One event model to handle
- Silent systems polled for you
- Normalised payloads either way
- One signature check everywhere
Webhook support is the thing most likely to be missing from the system a customer wants next, and the thing most likely to force a rewrite when it is. Because the event model is Maesn's rather than each vendor's, that question stops mattering during a sales call: you support the system, and how its data reaches you is an implementation detail on our side. The handler you wrote for the first integration is the handler for the last one. Which system a prospect runs is then a question go-to-market teams can answer without checking with engineering first.
Common questions
How do I set up a webhook with Maesn?
What does an event look like?
What are synthetic webhooks?
Which ERP and accounting systems support custom webhooks and triggers?
What is the difference between app-based and user-based webhooks?
What are virtual webhooks?
Websockets or webhooks for ERP integrations?
How should I harden my webhook handler?
How do I verify that an event really came from Maesn?
What happens when a system processes a request asynchronously?
Should I use webhooks or polling?
Which events can I subscribe to?
Build once on the Unified API.
See how unified webhooks works for your integration, or dive into the technical reference.











