Architecture and data protection
This page is written for a compliance, data protection or IT review. It states what PLANA Connect does, what it stores, what it does not store, and where. Every figure below describes the service as it is built, not as it is planned.
If you only need the short version: PLANA Connect stores no transactions. It stores the links between records, so that the same expense is never imported twice.
What the service is
PLANA Connect is a hosted synchronisation service between Payhawk and Odoo. It runs as a separate application. Nothing is installed into your Odoo, and no PLANA code runs inside it.
It connects to both systems the way any integration does:
| System | How | Credential |
|---|---|---|
| Payhawk | REST API v3 over HTTPS | API key you create in Payhawk |
| Odoo | XML-RPC or JSON-2 over HTTPS | Integration user + API key you create in Odoo |
Both credentials are issued by you, scoped by you, and can be revoked by you at any time without involving PLANA. The connector holds no standing access of its own.
Where it runs
| Hosting | Exoscale, Sofia zone (bg-sof-1), Bulgaria, EU |
| Data location | All application data stays in that zone. Nothing is replicated outside the EU. |
| Transport | HTTPS to both Payhawk and Odoo, and to the control panel |
| Sub-processors | Exoscale (infrastructure). No analytics, no third-party trackers in the control panel. |
What data is stored, and what is not
This is the part a compliance review usually asks first, so it is stated in full.
Not stored
None of the following is written to PLANA's database at any point:
- expense amounts, dates, categories or descriptions
- invoice and bill contents, including line items and tax amounts
- bank statement lines, balances or account movements
- receipt images and PDF documents
- supplier bank details (IBAN and account numbers)
- card numbers or any payment instrument data
Receipts are the clearest example. When a receipt is attached to an Odoo bill, the file is downloaded from Payhawk and uploaded straight into your Odoo in the same operation. It passes through memory and is never written to disk or to a database on our side.
Webhook deliveries are recorded as {topic, delivery_id} only. The body Payhawk sends is not stored.
Stored
| What | Why | Contains |
|---|---|---|
| Record links | So the same expense is never imported twice | A Payhawk ID, the Odoo ID it maps to, and a checksum used to detect changes. No content. |
| Sync state | Where the last run got to | A timestamp per flow |
| Run history | So you can see what happened and when | Run start and finish, status, and counts such as "created 12, updated 3" |
| Diagnostic log | So a skipped record can be explained | Log lines. See the note below. |
| Configuration | Your settings | Which flows are on, company ID, tax mapping and similar |
| Credentials | To reach your systems | Encrypted. See below. |
| Account and access records | Login, audit | Your users, sessions, and an audit trail of access |
A point we would rather state than have you find
The diagnostic log quotes record names, because a warning that does not say which record it is about cannot be acted on. A real line from the log reads:
partner 'ACME Supplies Ltd' matches Payhawk supplier 100080179 by VAT but its externalId is already set - skipping (duplicate partner in Odoo)
Where a vendor is a sole trader, that vendor name is a person's name. So although the connector stores no transactions, the diagnostic log can contain a limited amount of personal data in the form of vendor names. This is why the retention window below exists.
Retention
| Data | Kept |
|---|---|
| Diagnostic log lines | 30 days, then deleted automatically |
| Webhook delivery records | 90 days |
| Completed job records | 90 days |
| Run history (start, finish, status, counts) | Kept. It contains no names or identifiers. |
| Record links | Kept for the life of the connection. They are the connector's state: deleting them would cause every record to be imported again. |
| Access and audit records | Kept, as accountability records |
| Your configuration | Kept until you delete the company |
Deletion runs automatically every hour and is not something anyone has to trigger.
On termination, your connection and its credentials are deleted. Record links and configuration are deleted with the company.
Credentials
Credentials are held under envelope encryption:
- each customer has their own data encryption key
- that key is stored only in wrapped form, encrypted by a master key held outside the database
- credentials are encrypted with the customer's key before they are written
- the API never returns a credential value. The control panel shows which fields are set, never what they contain
- every use of a credential by PLANA staff is written to an audit trail
A database copy on its own does not yield a usable credential.
Access control
| Customer access | Email and password, with two-factor authentication (TOTP) mandatory |
| Separation | Every query is restricted to one customer at database level, by row level security. It is not only application logic. |
| PLANA staff access | Separate identity provider (SSO), separate permission plane |
| Staff audit | Every staff action inside a customer account is recorded: who, when, which customer, what |
PLANA support can see your configuration and your run history in order to support you. Credential values are not visible to staff.
Which data moves, and in which direction
Each flow can be switched on or off independently on your company's settings page.
| Flow | Direction | What moves |
|---|---|---|
| Expenses to bills | Payhawk to Odoo | Reviewed expenses become vendor bills |
| Receipt documents | Payhawk to Odoo | The receipt file is attached to the bill |
| Bank statements | Payhawk to Odoo | Card and account movements become bank statement lines |
| Payments | Payhawk to Odoo | The bill is marked paid |
| Suppliers | Two-way, or one way, or off | See the next section |
| Tax rates | Odoo to Payhawk | Your purchase taxes, so reviewers pick the same ones |
| Chart of accounts | Odoo to Payhawk | Expense accounts, so expenses are coded correctly |
Nothing is ever deleted in either system by the connector. It creates and updates; it does not remove.
Suppliers: only suppliers
A frequent question, and the answer is specific: the connector sends vendors only. It does not send your contact database.
When publishing from Odoo to Payhawk, a partner is included only if all four of these are true:
- Odoo counts it as a vendor. Odoo sets this the first time a partner is used on a purchase order or a vendor bill.
- It is the main contact record, not a delivery or invoicing address.
- It is not archived.
- It is a company in its own right, not a person filed underneath one.
Customers, prospects, employees and private contacts are never sent. The fourth condition also keeps employees out: a person recorded under a company inherits that company's tax ID, and sending them would be both wrong and rejected by Payhawk.
If that is still more than you want, the direction itself is a setting with three values:
| Setting | Effect |
|---|---|
| Both ways (default) | Payhawk suppliers become Odoo vendors, and Odoo vendors are published to Payhawk |
| Import only | Payhawk to Odoo only. Nothing leaves your Odoo. |
| Off | No supplier synchronisation at all |
For a review that requires no outbound flow of contact data, Import only answers it with one setting and leaves the rest of the integration working.
Who owns which field
Where suppliers do sync both ways, ownership is per field, not per record:
- Odoo owns identity. Name, tax ID, address and contact details. A correction made in Odoo is published to Payhawk.
- Payhawk owns bank details. An IBAN is captured in Payhawk, because that is where payments are made from, so it flows into Odoo. This is additive only: the connector never removes or overwrites payment details already held in your ERP.
How synchronisation runs
| Schedule | Every 15 minutes by default, configurable per company |
| Real time | Payhawk webhooks trigger a run immediately when an expense is reviewed |
| Full pass | Once a day, outside working hours, the connector re-reads everything to catch changes an API filter cannot report |
| Idempotency | Every record is matched to its existing counterpart before anything is written, so a repeated run never duplicates |
| Failure | A failed run is retried. A record that cannot be processed is logged and skipped; it never blocks the others. |
Questions this page does not answer
For a Data Processing Agreement, sub-processor list, or anything specific to your own compliance framework, contact us at payhawk@plana.solutions and we will answer directly rather than by referring you here.