Data Processing Agreement (DPA)
STATUS — DRAFT. Not legally reviewed. Do not put in front of a tenant until your lawyer has read every clause.
This Data Processing Agreement ("DPA") is entered into between:
- Controller: [Tenant legal name], [address], [VAT / NIF] ("Controller"), and
- Processor: [Kapta legal entity — e.g. Plugis, Lda.], [address], [VAT / NIF] ("Processor" or "Kapta").
Together: the "Parties".
It forms part of, and is governed by, the Kapta Terms of Service. It implements Article 28 of Regulation (EU) 2016/679 ("GDPR").
1. Definitions
Terms used here (Controller, Processor, Personal Data, Processing, Data Subject, Subprocessor, Supervisory Authority) have the meanings given in GDPR Art. 4.
2. Scope and roles
2.1 The Controller is the tour operator using the Kapta platform. The Processor is Kapta.
2.2 Kapta processes Personal Data only on the Controller's documented instructions. Those instructions are given (a) by configuring the platform (provider keys, default VAT, document rules) and (b) by sending booking webhooks from FareHarbor through to Kapta.
2.3 Kapta will not process Personal Data for any other purpose without written instructions, except where required by EU or Member State law. Where such a legal requirement applies, Kapta will inform the Controller of that requirement before processing, unless that law prohibits informing the Controller on important grounds of public interest (GDPR Art. 28(3)(a)).
2.A Subject-matter and duration of processing (Art. 28(3))
2.A.1 Subject-matter. Generating and persisting fiscal documents (invoices, credit notes, payments) in the Controller's chosen invoicing provider in response to booking events from the Controller's booking source (FareHarbor today); and operating the Kapta CMS that exposes those documents and their lifecycle to the Controller.
2.A.2 Duration. Processing begins when the Controller activates the integration and ends on termination of the underlying service, subject to the post-termination obligations in section 11 (return / deletion) and the retention periods in section 3.
2.B Nature and purpose of processing (Art. 28(3))
2.B.1 Nature. Collection of booking metadata via webhook, transient processing of end-customer PII in worker memory, durable persistence of document references, encryption-at-rest of provider credentials, and operational logging and audit.
2.B.2 Purpose. (a) fiscal compliance — issuing fiscally-numbered invoices and credit notes on the Controller's behalf; (b) operational reliability — replay, dedup, audit; (c) tenant configuration — applying per-source/per-affiliate document rules.
2.C Type of Personal Data and categories of data subjects (Art. 28(3))
2.C.1 Type of Personal Data. As enumerated in section 3 below: tour operator account fields, booking metadata, end-customer PII (transit-only), encrypted tenant secrets, support correspondence, billing metadata, application logs.
2.C.2 Categories of data subjects. (a) the Controller's authorised users (tour operator staff); (b) the Controller's end-customers (travellers who purchase tours through FareHarbor).
2.D Controller's rights and obligations (Art. 28(3))
2.D.1 The Controller warrants that it has a lawful basis under GDPR Art. 6 (and Art. 9 where applicable) for the Personal Data it instructs Kapta to process, and that it has provided the notices required by GDPR Art. 13/14 to its end-customers.
2.D.2 The Controller is responsible for the accuracy, quality and legality of the Personal Data, and for ensuring that the categories of data flowing through Kapta do not exceed what is necessary for the agreed processing purposes (data minimisation).
2.D.3 The Controller will not instruct Kapta to process Personal Data in a way that would put Kapta in breach of GDPR.
2.E Confidentiality and instructions (Art. 28(3)(a) + (b))
2.E.1 Kapta processes Personal Data only on documented instructions from the Controller, including with regard to transfers to a third country or international organisation, unless required to do so by EU or Member State law.
2.E.2 Kapta ensures that everyone authorised to process Personal Data is bound by a written confidentiality obligation or appropriate statutory confidentiality duties (see also section 5).
3. What Personal Data we process, why, and for how long
| Category | Examples | Purpose | Where it lives | Retention |
|---|---|---|---|---|
| Tour operator account | Email, name, password hash, country, invoicing-provider choice, VAT default | Authentication, configuration | MySQL (fareharbor_integracoes) | Life of the account + 30 days post-deletion |
| Booking metadata | Booking UUID, FareHarbor PK, event kind, status | Audit trail, replay | MySQL (webhook_events) | 24 months |
| Issued documents | Provider doc ID, doc number, amounts, tenant slug, booking refs | Fiscal record-keeping (legally required by tenant's jurisdiction) | MySQL (documents) | 10 years (Portuguese CIVA Art. 52.º + Decreto-Lei 28/2019; matches the Spanish AEAT 4-year baseline plus PT longer cap) |
| End-customer PII (transit only) | Customer name, email, billing address, tax ID, line items | Generate the invoice in the tenant's provider | In-transit only — Redis (BullMQ job payload) for up to 30 days on failed jobs; then refetched on demand from FareHarbor | 30 days max in Redis; not stored in MySQL |
| Encrypted tenant secrets | Provider API keys / OAuth tokens | Authenticate to the invoicing provider on the tenant's behalf | SQLite (tenant_secrets), encrypted with libsodium secretbox | Life of the account |
| Support tickets | Tenant message text, attachments | Customer service | MySQL (support_messages) | 24 months |
| Stripe billing | Customer ID, subscription status, last 12 invoices (live-fetched from Stripe) | Billing | Stripe + minimal mirror in MySQL (subscriptions) | Life of the account |
| Logs | Anonymised request logs, error stacks | Operations, debugging | Pino → host disk → rotation 7 days | 30 days |
Kapta does not store end-customer PII in its primary database. The legal source of truth for a booking body is FareHarbor (the tenant's chosen booking source), and Kapta refetches it on demand via the onboarding-server lookup endpoint.
4. Subprocessors
4.1 The Controller authorises Kapta to engage the subprocessors listed in SUBPROCESSORS.md, which forms part of this DPA.
4.2 Kapta will give 30 days' written notice (email or in-app) before adding or replacing a subprocessor. The Controller may object on reasonable grounds; if no agreed solution is reached, the Controller may terminate.
4.3 Kapta is responsible for ensuring each subprocessor is bound by data-protection terms no less protective than those of this DPA.
5. Confidentiality
5.1 Kapta ensures that everyone authorised to process Personal Data is bound by a written confidentiality obligation.
5.2 Access to tenant data within Kapta is restricted to the minimum number of staff who need it for operations and support. The internal admin backoffice records every privileged action (admin_audit_log table).
6. Security
6.1 Kapta implements the technical and organisational measures listed in Annex A below.
6.2 The Controller agrees the measures are appropriate to the risk of the Processing performed today. The measures will evolve with the platform; material reductions in security will be notified to the Controller.
7. Personal data breaches
7.1 Kapta will notify the Controller without undue delay, and in any case within 72 hours of becoming aware of a Personal Data breach affecting the Controller's data.
7.2 The notification will contain the information required by GDPR Art. 33(3) so far as it is known.
7.3 The Controller is responsible for notifying its own Supervisory Authority and (where required) the Data Subjects.
8. Data subject rights
8.1 The Controller is responsible for responding to Data Subject Access Requests (DSARs).
8.2 Kapta will assist with reasonable technical measures: extracting Personal Data on request, deleting it on request, correcting it on request. Charged at cost when assistance is more than de minimis.
9. International transfers
9.1 Kapta is established in Portugal. Some subprocessors are established outside the EEA — notably FareHarbor and Stripe (United States) and Cloudflare (United States with globally distributed PoPs).
9.2 Transfers to those subprocessors are covered by the European Commission's Standard Contractual Clauses (Commission Implementing Decision (EU) 2021/914 of 4 June 2021). The relevant Module 3 (processor-to-sub-processor) applies between Kapta (acting as the Controller's processor) and each non-EEA sub-processor, because Kapta is the contractual party transferring the Controller's Personal Data onward. Module 2 (controller-to-processor) applies only where Kapta is itself acting as controller (its own staff data, tenant account data — see ROPA Section A).
9.3 For the Cloudflare and Stripe (Inc.) legs specifically, Kapta has accepted each sub-processor's customer DPA and the included Module 3 SCC stack; their Annex I parties match the contractual chain described above (Controller → Kapta as Processor → Cloudflare / Stripe as Sub-processor).
9.4 See SUBPROCESSORS.md for the per-subprocessor mechanism and most-recent reference URLs.
10. Audit
10.1 The Controller may, no more than once per calendar year and with at least 30 days' written notice, request an audit of Kapta's compliance with this DPA. Audits triggered by a documented Personal Data breach affecting the Controller's data, or by a binding instruction from a Supervisory Authority, are not subject to the once-per-year cap and may be conducted on shorter notice.
10.2 Kapta may, in the first instance, satisfy a routine audit obligation by providing recent third-party reports (e.g. ISO 27001, SOC 2) where available, or by responding in good faith to a written questionnaire within 30 days. If those responses do not allow the Controller to reasonably verify compliance, the Controller retains its right under GDPR Art. 28(3)(h) to conduct (or mandate a third-party auditor to conduct) an on-site inspection during business hours, subject to reasonable confidentiality and operational-disruption safeguards.
10.3 Each party bears its own costs for routine audits. Where an audit uncovers a material breach of this DPA by Kapta, Kapta will reimburse the Controller's reasonable and documented external audit costs.
11. Return / deletion on termination
11.1 On termination of the underlying service, Kapta will, at the Controller's choice:
- delete all Personal Data; or
- return it to the Controller (CSV / SQL dump) within 30 days.
11.2 Backups of tenants.sqlite are retained for 90 days after termination and then deleted. The Controller acknowledges this retention.
11.3 Kapta will provide written confirmation of deletion on request.
12. Liability and term
This DPA terminates automatically when the underlying service terminates. The Parties' obligations under sections 5, 6, 7 and 11 survive termination as required by law.
Liability is governed by the underlying Terms of Service. To the extent any conflict exists between this DPA and the Terms, the DPA prevails for matters of data protection.
13. Governing law and jurisdiction
This DPA is governed by the laws of Portugal. The courts of Lisbon have exclusive jurisdiction, subject to mandatory consumer-protection rules.
Annex A — Technical and organisational measures
This is a description of the measures actually in place today. Anything marked Planned is a stated goal with an indicative target — it is NOT yet implemented and must not be relied on by the Controller until this annex is updated to remove the qualifier. Any material change to the controls below is notified to the Controller per section 6.2.
Confidentiality (Art. 32(1)(b))
- All HTTPS traffic terminated at Cloudflare with TLS 1.3 minimum.
- MySQL connection: TLS in production; plaintext over a private network on the development environment.
- Tenant provider API keys and OAuth credentials encrypted at rest with libsodium secretbox; master key (
BRIDGE_MASTER_KEY) read from the host environment only — never committed to the repository and never written to backups. Hot rotation supported via the dual-key path documented inDESIGN_DECISIONS.md. - Bcrypt cost-12 hashes for all admin / CMS user passwords; constant-time dummy-hash comparison on the user-not-found branch to prevent email-existence timing oracles.
- HMAC signature verification on Stripe webhooks; Stripe event-id idempotency table prevents replay.
- Planned: per-tenant HMAC signing key on the FareHarbor webhook path (current build relies on Cloudflare access plus the per-tenant URL slug; per-tenant HMAC is the next reduction in trust surface).
Integrity (Art. 32(1)(b))
- Idempotent webhook processing:
stripe_events_processedtable +last_event_created_msordering guard on subscriptions; atomic INSERT-IGNORE claim withPENDING:*sentinel ondocumentsso concurrent workers never mint duplicate fiscal numbers. - Boot-time MySQL migrations run under a MySQL advisory lock so multi-replica rollouts don't race.
- TypeScript strict mode across all three apps; GitHub Actions CI workflow at
.github/workflows/ci.ymlruns typecheck + lint + build on every PR and push tomain. - Worker-side dedup uses
(shortname, source, booking_uuid, document_kind)unique index plus a separateactive_keyso soft-deleted rows don't collide with replays.
Availability (Art. 32(1)(b))
- BullMQ retries (6× exponential backoff, 5 s → 160 s) for transient provider failures.
- Booking events parked in
waiting_for_configwhen the tenant has no provider key yet; released by the CMS as soon as a key is saved (with bounded concurrency + per-row retry budget). - Delayed jobs (tour-day approve, scheduled payment) mirrored in MySQL
scheduled_jobs; on boot, the worker reconciles the mirror against Redis and re-enqueues anything missing — so a Redis volume loss does not silently drop tour-day finalisations. /healthzshallow probe on the bridge HTTP listener; deep probe exercises MySQL + Redis.- Failed jobs retained 30 days for replay then auto-deleted (GDPR-driven cap on PII in Redis).
- Planned: SQLite (
tenants.sqlite) nightly backup to off-host storage with 90-day retention plus a quarterly restore drill. Today the SQLite file is backed by the host's regular volume snapshots; the dedicated off-host pipeline is on the post-MVP roadmap.
Resilience (Art. 32(1)(c))
- Atomic provider-call claim (the PENDING sentinel pattern) prevents duplicate fiscal documents on worker concurrency.
- Webhook idempotency table + Stripe
last_event_created_msordering high-water-mark prevent replay corruption. - Scheduled-job reconciler (see Availability above) re-enqueues durable mirror entries on boot, covering a Redis catastrophe.
Restoration after incident (Art. 32(1)(c))
- MySQL migrations are versioned and append-only; rollback is forward-only by adding a new migration.
- Planned: documented RTO / RPO targets, an off-host SQLite backup pipeline, and an annual full restore drill. None of these are in production today; the Controller will be notified once they are.
Audit log
- Every privileged admin action recorded in
admin_audit_log(actor, action, target, timestamp, IP, user-agent). Retained 24 months. - Stripe webhook receipts are durably mirrored in
stripe_events_processed; release-waiting and webhook retry calls leave an audit-log row.
Personnel
- Planned: written confidentiality clauses in employment / contractor agreements for every person with production access; documented background-check posture. Today the operating entity has a small headcount and confidentiality is governed by the underlying Plugis, Lda. employment terms; the formal annex evidence is on the roadmap.
- Need-to-know access to tenant data is enforced by the admin-app role check and the
admin_audit_logreview path.
Source: docs/legal/DPA.md