11 min read
erp
integrações
arquitectura
primavera
aplicações web

Integrating Primavera with a web application without creating debt

Practical guide to integrating Cegid Primavera with a web app: APIs, SQL Server, queues, synchronisation, security and real production pitfalls.

Arquitecta de software junto à janela, a escrever num caderno numa sala clara com uma factura no parapeito

TL;DR

  • Do not connect the web application directly to the Primavera database to write documents. You can break tax rules, stock, numbering and accounting entries.
  • The safest pattern is to have an intermediate integration service, usually in .NET 8 LTS, close to the ERP, which talks to the official Cegid Primavera API and exposes a clean API to the web application.
  • For heavy reads, such as catalogue, customers, stocks or balances, you can use asynchronous synchronisation to PostgreSQL 16, SQL Server 2022 or another application datastore, with version control and reconciliation.
  • The biggest failures appear in idempotency, document series, VAT rounding, stock concurrency and jobs that assume the ERP is always available.
  • Before writing a line of code, decide who owns each piece of data: ERP, web application, or both with explicit synchronisation rules.

The real problem is not “connecting to Primavera”

The question “how do you integrate Primavera with a modern web application?” seems technical, but the hard part is not opening an HTTP connection or installing a library. The hard part is not turning the ERP into a black box full of invisible side effects.

Cegid Primavera, especially in ERP v10 installations in Portuguese business environments, is often the system of record for invoicing, stock, current accounts, items, customers and accounting. The modern web application, built in React, Next.js, Node.js 22 LTS.NET 8 LTS or Django, is usually the place where external users or internal teams want to work with a better experience, current authentication, fast search and tailored flows.

That difference creates tension. The web app wants responses in 100 ms to 300 ms. The ERP may need to validate a tax series, calculate VAT, check a warehouse, lock a document, post movements and write several tables. Treating these two realities as if they were the same thing is the shortest route to technical debt.

In projects where Impact Origin encounters back-office ERP, the rule of thumb is simple: Primavera should not be “the web application database”. It should be integrated as a critical external system, with a contract, logs, retries, idempotency and reconciliation. This seems slower at first, but it avoids months of difficult fixes when duplicate documents, negative stock or invoices that do not match SAF-T appear.

The four possible approaches, and when to use each one

There are four common ways to integrate a web application with Primavera. None is perfect. The choice depends on the ERP version, licensing, volume, tax criticality and the team that will maintain the system.

First approach: official API or public Primavera layer. In Cegid Primavera ERP v10 environments, you should start with the official extensibility and integration documentation at https://developers.primaverabss.com and the specific documentation for the installed version. Depending on the product and configuration, you may have Web API.NET SDK, integration services or extensions. It is the best option for creating documents, updating entities with business rules and respecting internal validations. The cost is that you need to know the ERP model well, deal with unfriendly messages and, often, run part of the integration on Windows, close to the Primavera server.

Second approach: direct read access to the SQL Server database. This may be acceptable for reports, internal dashboards or read-only synchronisation, provided you know exactly which tables and views you are querying. Even so, it is best to prefer stable views, replicas or an intermediate database. Writing directly to Primavera tables is a bad idea in almost all cases. It is not just a support issue. It is a matter of tax and operational consistency.

Third approach: files, exports and imports. CSV, XML, Excel, SAF-T and shared folders still exist because they work in real companies. They are useful when volume is low, the operation is batch-based and the ERP does not expose a suitable API. The downside is latency, lack of immediate feedback and the difficulty of guaranteeing idempotency. If the user needs to know within 2 seconds whether the order was created, a file is not enough.

Fourth approach: middleware with queues and its own database. This is my preferred option for integrations with some longevity. The web application talks to your API. That API writes requests to a queue, for example RabbitMQ 3.13, Azure Service Bus or SQS, and a worker processes the integration with Primavera. For read data, you synchronise to PostgreSQL 16 or SQL Server 2022. This separates user experience, ERP availability and integration rules.

The strong opinion: if the integration includes invoices, orders, customers, stock or prices with commercial impact, do not make a direct call from the frontend to Primavera, and do not make the web application dependent on ERP latency. Use an anti-corruption layer.

The design that usually ages best has five parts.

The first is the web application. It can be Next.js, React, Vue, Laravel.NET or another stack. It should know product concepts, not internal Primavera details. For example, it should create an “order request” in your domain, not try to fill obscure fields in the ERP’s commercial documents.

The second is your application API. This is where authentication, permissions, input validation, auditing and user experience live. If a customer submits an order, the API can respond quickly with the status “received” and not block for 10 seconds waiting for the ERP.

The third is a queue or outbox table. The outbox is essential when you want to ensure that the local action and the intention to synchronise with Primavera are recorded in the same transaction. Without this, you will have cases where the order exists in the application, but the job never starts because the process crashed at the wrong moment.

The fourth is the integration service. In Primavera integrations, I favour .NET 8 LTS when the available API or SDK is .NET, and I run the service on a Windows machine or application server with low-latency access to Primavera and SQL Server. This service translates your domain into Primavera documents, applies idempotency, handles errors and writes technical logs.

The fifth is the read database. Do not ask Primavera to serve catalogue search, listings with rich filters and dashboards with 300 users. Synchronise the necessary data to a database optimised for the application. For many cases, PostgreSQL 16 is perfectly sufficient. For Microsoft environments, SQL Server 2022 also makes sense.

A simple contract between the web app and the integration can look like this:

{
  "externalRequestId": "web-order-982731",
  "customerCode": "C000245",
  "documentType": "ECL",
  "series": "2026",
  "currency": "EUR",
  "lines": [
    {
      "sku": "ART-001",
      "quantity": 2,
      "unitPrice": 19.99,
      "vatRate": 23
    }
  ]
}

The important field here is externalRequestId. Without a unique external key, safe retries become difficult. If the call fails due to a timeout, you do not know whether the document was created or not. With idempotency, you can repeat the operation and return the same result.

Writing to Primavera: where integrations go wrong

Creating commercial documents is the point where most integrations fail. Orders, delivery notes, invoices, credit notes and stock movements are not simple inserts.

There are numbering rules by series. There are customer validations. There are items with alternative units. There are warehouses and locations. There is VAT included or excluded. There are line and header discounts. There is rounding by line or by document. There are withholdings and tax regimes. There are Primavera user permissions. There are documents that can only be cancelled and not deleted.

The common trap: assuming that the web application can calculate totals and that Primavera merely writes them. In production, this creates differences of cents. A line with unit price, discount, VAT and rounding can match in 95 per cent of cases and fail precisely on the documents that go to accounting. The rule should be: the web application can show estimates, but Primavera must be the final source for the document’s tax totals.

Another trap: not separating recoverable errors from definitive errors. A timeout, a SQL Server deadlock or a temporary network failure should go to retry with backoff. A non-existent customer, blocked item or invalid series should not retry for 6 hours. It should remain as a functional error, visible in an operations console.

For modest volumes, such as 10 to 50 documents per minute, a single worker with controlled concurrency may be enough. For 300 requests per minute, you already need to think about partitioned queues, limits by company, locks by item or warehouse and synchronisation windows. Primavera and SQL Server do not like blind concurrency over the same resources.

Minimum pattern for idempotency in your database:

CREATE UNIQUE INDEX ux_erp_requests_external_id
ON erp_integration_requests (external_request_id);

It seems trivial, but it avoids an entire class of problems. Idempotency should not live only in memory or only in logs. It should be guaranteed by a transactional constraint.

Reading, synchronisation and data that becomes outdated

The reading part seems simpler, but it also has traps. The web application may need customers, addresses, items, prices, stock, order status, invoices and balances. If every page makes calls to Primavera in real time, you will end up with a slow and fragile application.

There are three strategies.

The first is real-time read access via API. Use it for low-volume administrative screens, where data freshness is more important than speed. Example: viewing the details of an invoice that has just been issued.

The second is periodic synchronisation. Every 1 to 5 minutes, you import changes into your database. It is suitable for catalogues, customers and prices that do not change every second. Batches of 500 to 2,000 records per run are a reasonable starting point, then adjusted with real metrics.

The third is event-driven synchronisation. If you can emit events from Primavera, or from an extension, even better. Otherwise, you can use timestamps, audit tables, SQL Server Change Tracking or a staging table populated by the integration. The official SQL Server documentation on Change Tracking is relevant here, but it does not replace knowledge of the Primavera schema.

The inevitable problem is eventual consistency. A user changes an address in the ERP and the web app only sees it 2 minutes later. This is not necessarily bad, provided the product accepts it. The mistake is pretending you have real time when you have polling.

Define internal SLAs by data type. Stock visible on the site may need a maximum delay of 30 seconds. Catalogue can tolerate 15 minutes. Invoices can appear in 1 minute. Current account balances may need to be queried on demand, because the user expects accuracy.

You should also store synchronisation metadata: source_updated_at, synced_at, connector version, payload hash and status of the last attempt. Without this, when someone asks “why is this customer different?”, you are left guessing.

Security, auditing and operations

ERP integrations contain sensitive data: customers, tax numbers, addresses, prices, margins, tax documents and, sometimes, clinical or financial information depending on the sector. Do not treat the integration API as an internal API without security.

Authentication between services should use OAuth 2.0, mTLS, rotating keys or, at a minimum, tokens with short expiry stored in a secret manager. The web application should never store Primavera credentials or expose them to the browser. If you use Azure, Key Vault is a natural option. In AWS, Secrets Manager. On your own servers, at least use restricted permissions and operational rotation.

Authorisation should be explicit. A user who can see orders in the web application should not automatically be able to issue documents in the ERP. The integration should map actions to internal permissions.

Logs are mandatory, but do not dump tax numbers, addresses and full payloads indiscriminately. Record correlation, IDs, document type, duration, result and sanitised error message. For observability, OpenTelemetry is a good foundation. The goal is to answer simple questions quickly: how many requests failed, how long the queue has been delayed, what the p95 for document creation is, which functional errors are blocking the operation.

Minimum metrics:

  • p95 and p99 latency by operation, for example create order, create customer, synchronise stock
  • queue size and age of the oldest request
  • success and failure rate by error type
  • number of retries per hour
  • difference between source_updated_at and synced_at

Alerts should be few and useful. An isolated failure should not wake anyone up. A queue with requests older than 10 minutes, or 20 per cent errors in the last 15 minutes, does deserve attention.

Phased implementation plan

Do not start with the hardest part. A sensible plan reduces risk and shows early where the surprises are.

Phase 1: inventory. Confirm the exact Primavera version, modules used, SQL Server, licences, companies, series, document types, customisations, users and test environment. Without a test environment similar to production, you will be testing on real customers, and that rarely goes well.

Phase 2: domain matrix. List entities and data owner. Where is a customer created? Does an item originate in the ERP or in the web app? Is stock authoritative in Primavera? Where do promotional prices live? This matrix avoids late-stage arguments.

Phase 3: read first. Synchronise customers, items and stocks to the application. Measure latency, data quality and differences. In this phase, duplicate codes, inactive items used in documents, incomplete addresses and rules that nobody wrote down start to appear.

Phase 4: low-risk writing. Start by creating a simple entity or an order in a controlled state. Do not start with final invoicing if the team has not yet mastered series, taxes and cancellations.

Phase 5: assisted operation. During the first few weeks, keep an internal console with pending, failed, reprocessable and ignored requests. Integrations without an operational screen force developers to correct data directly, which is a bad sign.

Phase 6: hardening. Only then should you optimise concurrency, batches, caching, daily reconciliation and alerts. A nightly reconciliation between application documents and Primavera documents is cheap and catches problems before the customer does.

My practical recommendation: set aside time for dirty data. In ERP integrations, code is rarely the only problem. The history and exceptions of the current operation always have more impact than the initial diagram suggests.

Conclusion

Integrating Primavera with a modern web application requires more than an API. It requires clear boundaries, idempotency, well-designed synchronisation, security and visible operations. Writing directly to the ERP database may seem quick, but it usually becomes expensive. The right path is to treat Primavera as a critical system and build an integration layer that can withstand real failures.

If you are facing a similar problem, book a conversation at https://impact-origin.com/agendamento.

Impact OriginEnjoyed this article?At Impact Origin we help founders and teams build and scale custom software, from a startup MVP to the next step. If you have a project in mind, let's talk.