How to integrate with Weclapp: The rate limit is a queue
A rate limit is usually a number you can plan against: calls per second, credits per request, a bucket that refills. Weclapp states in its own reference that it has no fixed rate limits, and then describes what it does instead. Your request waits in a queue for up to 30 seconds before it is answered or refused, which moves the failure from a status code to a timeout you set yourself.


Your customer pastes a token, and the tenant sits in the hostname
Weclapp is a cloud ERP for small and mid-sized companies in the DACH region, and it covers more ground than an accounting package: CRM, inventory, projects and invoicing in one system. That breadth is why an integration against it rarely stops at invoices, and why the same customer record turns up in a sales flow and in a bookkeeping flow.
Connecting is short, and it is not OAuth. Weclapp appears in the headless list in our authentication documentation, so there is no redirect and no consent screen: an administrator generates a personal access token in their own settings and hands it over with the tenant id. The full object matrix and the connection fields sit on the Weclapp API page.
The tenant id is not a field Weclapp hands back to you. It is the subdomain the customer already browses: every call goes to <TENANT>.weclapp.com, under the path /webapp/api/v2/.
What you can do is spare them the retyping. Our authentication documentation names Weclapp and xentral as the two systems where you can pass tenantId as a query parameter to autopopulate that field on the submission page, so a value your customer has already given you is not asked for a second time. Both credentials are still required: the API token and the tenant id.
Headless flow, the interactive-flow list and the tenantId parameter from our authentication documentation, base URL and token location from Weclapp’s v2 reference, both checked 13 August 2026. The tenantId parameter autopopulates the field on the submission page; the create-account reference lists apiKey and tenantId as required for Weclapp either way.
Two properties of that token decide how your support queue looks a year in. It belongs to one user, so that person’s permissions are the ceiling for everything you can read. And Weclapp states that generating a token “invalidates all previous tokens”, so a customer tidying up their settings can end your connection without meaning to.
Nothing announces that. Compare Lexware Office, whose resource list carries a TOKEN resource with a REVOKED event for exactly this moment. Weclapp’s resource list has no equivalent, so the next failed call is the notification.
The queue is the rate limit, and it is 30 seconds deep
A rate limit is normally a number you can divide your workload by: calls per second, credits per request, a bucket with a refill rate. Weclapp opens its load management section by declining to give you one. Its words: “The weclapp API has no fixed rate limits, and we want to keep it that way.”
What follows is not an absence of control, it is a different one. Weclapp limits the number of requests running at the same time per tenant, and separately watches sustained load. Go over either and your call is not refused, it is held: “the requests will just take a little bit longer”. That waiting room has a documented depth of 30 seconds, and only after that does the answer become HTTP 429.
The second mechanism changes what you are budgeting. Weclapp measures effective request time, not request count, and its own worked example makes the unit explicit: 500 requests at 100 ms each, even run in parallel, come to 50 request-seconds of load and are described as no problem. A handful of expensive queries can therefore cost more than several hundred cheap ones.
The two load headers are only present when a request actually waited, so nothing on the response is the signal that nothing was queued.
Requests over the limit are not rejected. They wait for capacity, and the response says how many milliseconds that took and whether the reason was concurrency, load, or both.
The queue has an end. Weclapp names 30 seconds as the current ceiling, and the same two headers ride along on the refusal.
The middle row is the one that costs people time. It is a success, and it can take half a minute to arrive. Weclapp asks for request timeouts of at least one minute so a call that would have worked is not cut off on your side.
Because the pressure arrives as latency, the platform tells you about it in headers rather than in an error. Two ride on the response when a request had to wait, and both are absent when it did not, which makes their presence the signal.
All four are Weclapp’s own, and they travel on the leg between Weclapp and whoever calls it. Two of them you set on the way out to ask for a shorter wait, and the two from the visual above come back on the way in to report one. Through Maesn that leg is ours, so the table below is what the queue looks like from the inside rather than a set of headers you attach.
| Header | Direction | What it tells you |
|---|---|---|
| X-Weclapp-Wait-Timeout-Ms | Request | How long you will wait before processing starts |
| X-Weclapp-Request-Timeout-Ms | Request | Best effort ceiling for the whole call, queue included |
| X-Weclapp-Wait-Ms | Response | Milliseconds this request spent waiting |
| X-Weclapp-Wait-Reason | Response | concurrency, load, or both |
Every value from the load management section of Weclapp’s v2 reference, checked 13 August 2026. The two request headers can only lower a timeout, never raise it past the server maximum, and invalid, negative or zero values are ignored rather than rejected.
GET /webapp/api/v2/salesOrder?pageSize=100 HTTP/1.1Host: <TENANT>.weclapp.comAuthenticationToken: <TOKEN>Accept: application/jsonAccept-Encoding: gzipUser-Agent: your-product/1.0X-Weclapp-Wait-Timeout-Ms: 5000X-Weclapp-Request-Timeout-Ms: 15000
The User-Agent is not decoration: Weclapp asks for one on automated requests so it can identify misbehaving clients. Accept-Encoding: gzip is requested too, and the API compresses the response either way.
A 429 is easy to handle, because it is loud. The queue is quiet: your call succeeds and takes half a minute. Whatever timeout your HTTP client applies is therefore part of this integration, and Weclapp is explicit about the direction to move it, recommending “request timeouts of at least one minute to avoid aborting requests too early that would get a successful response”.
A short client timeout does not protect you here. It converts a slow success into a failure, and if you retry on that failure you add load to the thing that was already queueing you.
The queue is Weclapp’s, not ours, and a 429 that survives it reaches you. What we do with it is make it the same shape as every other system’s through unified error handling, so a wait that ends in a refusal is one error type in your code rather than a Weclapp-shaped exception.
Our own rate limiting reference groups Weclapp with bexio and sevdesk under a limit that is enforced but whose value is not published. That and Weclapp’s “no fixed rate limits” are the same fact from two directions: something does throttle you, and no number is available to plan against.
An unknown filter is ignored, an unknown sort is rejected
Weclapp’s advice for staying out of the queue is specific, and one line of it is a prohibition: “No unfiltered polling”, alongside “Prefer fewer, larger requests”. So filtering is not an optimisation on this system, it is the mechanism that keeps your integration inside a budget nobody published.
The filter syntax itself is generous. A parameter is a property name and an operator joined by a minus, there are thirteen operators from eq through ilike to in, several parameters combine with AND, and an or- prefix or an or<group>- prefix builds the boolean shapes underneath.
Then comes the sentence to build defensively around: “Filtering query parameters for unknown properties or properties that don’t support filtering are silently ignored.” No error, no warning, no field in the response. The filter simply is not there, and what comes back is the collection.
Filter dropped, full collection returned
Weclapp states that filtering parameters for unknown properties, or for properties that do not support filtering, are silently ignored.
Request refused
The same reference states that an unsupported or unknown property in a sort returns an error response.
The quiet one is the expensive one. A dropped filter returns the whole collection, which is the unfiltered read that Weclapp’s own best practices ask you to avoid, and the load it creates is what puts the next request in the queue.
One paragraph earlier in the same reference, sorting takes the opposite line: “If an unsupported or unknown property is specified then an error response is returned.” Same API, same class of typo, two different fates. Only one of the two parameters tells you that the field name was wrong.
Dates make the same mistake easy to walk into twice, because the query layer accepts two formats depending on which door you use. The simple filter parameters take milliseconds since the epoch. The newer filter expressions take ISO 8601, and they carry Weclapp’s own label: “This is still a beta feature.”
# Simple filter parameter: milliseconds since the epochGET /webapp/api/v2/party?createdDate-gt=1398436281262# Filter expression, beta: ISO 8601 in the same positionGET /webapp/api/v2/party?filter=(lastModifiedDate >= "2022-01-01T00:00:00Z")# Paging: page is one-based, 100 by default, usually 1.000 at mostGET /webapp/api/v2/party?page=2&pageSize=250
Both date lines are from Weclapp’s own examples. Note that page counts from 1 rather than 0, which is the kind of detail that shifts an entire import by one page if you assume otherwise.
Operators, the silent-ignore rule, the sorting error, both date formats and the paging defaults from Weclapp’s v2 reference, checked 13 August 2026. The 1.000 ceiling on pageSize is the general one and individual resources may document their own.
Five resources send events, and every pair is its own subscription
Five Weclapp resources are switched on here today: CUSTOMER, SUPPLIER, INVOICE, CREDIT_NOTE and SALES_ORDER, each of them able to report CREATED, UPDATED and DELETED. Weclapp publishes more than that, and the rest of the matrix is available on request rather than closed.
That carries more weight here than it would on a system with a published call budget, because subscribing is Weclapp’s own answer to the queue in the previous section: “Ideally: Event-driven architecture using webhooks.”
The arithmetic of switching that on is the part worth planning for. A subscription names one resource and one event type, and it is created per customer. Five resources times three types is fifteen subscriptions for one connection that wants everything, and fifteen deletions when that customer leaves.
Naming a resource and an event type is not a Weclapp peculiarity, it is what a subscription is on our side as well. What differs between systems is the level it attaches to: Exact Online subscribes to a resource on its own, and on QuickBooks and Xero the choice is made once inside your own app registration. On Weclapp it attaches to the customer, which is why the count above is per connection rather than per product.
POST /webhooks?companyId=<companyId>X-API-KEY: <your key>X-ACCOUNT-KEY: <the end user key>{ "callbackUrl": "...", "eventType": "CREATED","resource": "CUSTOMER" }# what arrives at callbackUrl{ "eventType": "CREATED","filterDate": null, // not supported by weclapp"resource": "CUSTOMER","resourceId": "1605408d-ed88-4228-8a12-ab857a2972d8","userId": null } // not supported by weclapp
Two identifiers meet in this call and they are not the same one. The tenantId from the previous section is Weclapp’s and sits in the hostname; companyId here is ours, and our Weclapp documentation lists it as required when you are not using the interactive flow. The generic webhooks reference documents only the two headers, so treat it as the per-system doc has it and send it.
Resources, event types, the subscription call and the event body from our Weclapp documentation, the unified webhook model and the systems list from our webhooks documentation, both checked 13 August 2026. Verification is HMAC-SHA256 over the raw body using the secret the subscription returns, compared against X-MAESN-SIGNATURE.
Two properties of the payload shape what you can do with it. The first is shared with Lexware Office and stated in both sets of docs: filterDate and userId arrive as null, so an event tells you neither when the change happened nor who made it.
The second is easier to build against: notifications arrive as a single object, because the array form is documented only for Business Central, QuickBooks and Xero.
A CREATED or an UPDATED event gives you an id, and the obvious next move is to read that record. DELETED gives you an id for a record that is gone. There is nothing to fetch, so the identifier is the entire content of the event, and you can only act on it if your own store can resolve it.
There is a matching asymmetry on the write side. No Weclapp object is available for deletion through us today, so this is an event announcing an action that the same connection cannot perform.
JSON serialisation: strings for money, integers for dates
Weclapp’s JSON has three conventions that will not match your deserialiser’s defaults, and all three are deliberate rather than accidental.
- Decimals are strings. Prices and quantities are “serialized as JSON strings and not as JSON numbers to prevent accidental loss of precision”, always with a dot as the decimal mark. Parse them as decimals, not as floats, and the reason Weclapp gives is the reason to keep them that way.
- Timestamps are integers. Dates and times are “milliseconds since 1970-01-01T00:00:00Z”, which is also why the simple filter parameters in the previous section want epoch values.
- There is no empty string. Weclapp states that empty or whitespace-only strings “are always interpreted as null”, so you cannot clear a text field by sending one. Nulls are also omitted from responses unless you ask for them with
serializeNulls.
JSON type table and the null handling from Weclapp’s v2 reference, checked 13 August 2026. Weclapp keeps a version available “for at least one year after the release of a new version”, and our own documentation does not say which one a connection speaks.
Normalising exactly this, so that an amount is a decimal and a date is a date whichever system produced it, is the job of the common data model. It is also the reason the three conventions above are worth reading once and then forgetting.
Seven objects are readable, and two of them stay quiet
The matrix is small and unusually consistent: every object that sends an event can also be read, so there is no case here of a notification you cannot follow up on. The interesting rows are the last two.
| Object | Events | Read | Create | Update |
|---|---|---|---|---|
| Customers | Yes | Yes | Yes | Yes |
| Suppliers | Yes | Yes | Yes | Yes |
| Invoices | Yes | Yes | Yes | On request |
| Credit notes | Yes | Yes | On request | On request |
| Sales orders | Yes | Yes | On request | On request |
| Bills | On request | Yes | On request | On request |
| Payments | On request | Yes | On request | On request |
Counted from coverage-data.ts, which is generated from the per-system documentation, checked 13 August 2026. On request means available on request rather than unavailable in Weclapp, so these rows are a statement about what is wired today. The removed endpoints below come from Weclapp’s v2 changelog, which moved with the paragraph it sources.
Bills and payments are readable and their events are the two on request, which is worth asking about early rather than late: those are the purchase side and the money side, and they are usually what a reconciliation or a payables flow is built on. If they matter to your product, ask for the event at the same time as the read.
Deletion is unavailable on every object here, so removing records stays a job in the source system. Accounting practice expects corrections as new documents anyway, which is why this rarely turns out to be the constraint it first looks like.
One structural note for anyone matching our object names against Weclapp’s own endpoints. Version 2 removed /contact, /customer, /lead and /supplier and consolidated them into /party, with a type field telling the kinds apart. Read the endpoint names above as Weclapp’s current shape rather than as our field list, and expect the two vocabularies to differ.
The five wired resources are not the boundary of what Weclapp can send. Most of the matrix sits on request, which means it can be switched on rather than that it is closed, and bills and payments are the two that most reconciliation and payables flows reach for first. Asking at design time costs a message. Discovering it after you have built a polling loop costs the loop.
Testing all of this does not need a special environment. Weclapp offers a free trial account, and our sandboxes page lists a Maesn-provided sandbox for it as well. What does not exist is a separate sandbox base URL, so you switch accounts rather than endpoints.
What stays on your side: The timeout, the filter and the token
Weclapp documents itself well, and almost everything above is discoverable in an afternoon. The cost is not the reading, it is that the next system answers the same questions with different mechanisms, and the one after that again. What we take off the list:
- The tenant, the token and the header choice. The subdomain is captured once at connection time and becomes the base URL,
AuthenticationTokenis attached to every call, and the prefill parameter keeps your customer from typing a tenant id you already hold. - Fifteen subscriptions behind one call. Resource and event type pairs, our own
companyIdparameter and the signature check reach your code as the same event you already handle everywhere else. - Two date formats and a string decimal. Epoch milliseconds, ISO 8601 in the beta expression syntax, amounts as strings and absent nulls arrive as one normalised shape.
- Page one, and the operator vocabulary. One-based paging, the 100 default and the thirteen filter operators sit behind one paged interface.
What stays with you, and the first two are decisions rather than tasks:
- Your request timeout. Weclapp asks for at least a minute because its queue can hold a successful call for 30 seconds. That number lives in your HTTP client and no integration layer can set it for you.
- How much you read, and how often. The budget is effective request time, so the shape of your sync is the lever. Fewer and larger beats many and small here, and the guidance to avoid unfiltered polling is Weclapp’s own.
- Knowing your own field names. A filter on a property that does not exist is accepted and dropped. Whatever validates your query fields, whether a test against a real tenant or a generated field list, is yours to run.
- The token lifecycle at the customer. A regenerated token retires the old one and nothing announces it. Detecting that, and asking the right person for a new one, is a support flow rather than an API call.
Frequently asked questions
Does Weclapp have a rate limit?
Why does a Weclapp request wait before it runs?
How do I know a Weclapp request was delayed?
Does Weclapp support webhooks?
Why does my Weclapp filter return every record?
What happens when a Weclapp user generates a new token?
Does Weclapp offer a sandbox?

QuickBooks Online Webhooks: Events, Retries and Recovery
QuickBooks Online webhooks cover 29 entity types and expect HTTP 200 in three seconds. Why Intuit still asks you to poll change data capture.
Lennart Svensson · 25 Aug 2026
Lexware Office Pagination: The 406 and One Page Size
Lexware Office validates the page size and rejects a bad one with 406, the same code it uses for an unsupported media type. What that means for your read loop.
Lennart Svensson · 20 Aug 2026
How to Integrate with DATEV Rechnungswesen: One Connection
One connection carries reading and writing in DATEV Rechnungswesen, on a two-year token. Which objects travel in which direction is the real decision.
Lennart Svensson · 17 Aug 2026Build once on the Unified API.
Weclapp meters you in time, QuickBooks in calls per minute, and sevdesk sends no events to plan around at all. Build against one interface and each of those becomes a row in a table rather than a project.