·3 min read·← All posts
Data Residency UAE Saudi Arabia Open Banking

The constraints

Three regulators; three overlapping rule sets:

A platform serving customers in both jurisdictions has to keep Saudi data in Saudi territory AND honour UAE-specific transfer rules for UAE customer data.

The architecture

Two parallel deployments:

                         ┌─ UAE customers ──► UAE deployment (Bahrain region)
                         │
   Bancnet front-end ────┤
                         │
                         └─ Saudi customers ──► Saudi deployment (Riyadh region)

Per-deployment:
  - Postgres + RLS (data physically in-region)
  - LLM inference on-prem or in-region cloud
  - Audit logs in-region
  - Backups in-region

The front-end is the only shared layer. It serves both jurisdictions and routes based on the user’s residency claim (validated against the bank’s KYC data, not self-asserted).

The router

A user logs in. The platform reads their residency from KYC and routes:

func routeRequest(claims auth.Claims, req Request) (deployment string, err error) {
    residency := claims.Residency  // "UAE" or "SAU", validated at KYC
    switch residency {
    case "UAE":
        return "uae-bahrain", nil
    case "SAU":
        return "saudi-riyadh", nil
    default:
        return "", ErrUnsupportedResidency
    }
}

The router is the only place residency is enforced. The downstream deployments are blind to each other — they don’t know the other exists. This is by design: a code bug in one can’t leak data from the other because the connection doesn’t exist.

The data flows you’d think work but don’t

Cross-deployment analytics. A “show me transactions across both jurisdictions” report runs nowhere — there’s no joined view. Reports are per-jurisdiction. Aggregate insights (counts, anonymous patterns) are computed by hand on per-jurisdiction outputs and combined off-platform.

Cross-deployment LLM context. A user’s chat history doesn’t follow them across jurisdictions because there’s no cross-jurisdiction store. If a UAE user moves to Saudi, they start fresh.

Backups across regions. Tempting: store UAE backups in a redundant Saudi region for resilience. Not allowed — Saudi region might house UAE data; UAE region might house Saudi data; both regulators object.

The discipline: design every data path with “in which jurisdiction does this data live, in transit and at rest” answered explicitly. Anything cross-jurisdiction is either explicitly approved (rare) or refactored to per-jurisdiction (default).

The audit shape

Each jurisdiction has its own audit log. The hash chain doesn’t cross jurisdictions. Audit reads are per-jurisdiction; auditors get scoped credentials.

When a Saudi auditor inspects, they see only Saudi data. The UAE deployment doesn’t exist from their perspective. Same in reverse.

What this cost in engineering

About 20% more infrastructure cost (two deployments instead of one). About 15% more engineering effort (per-jurisdiction config, per-jurisdiction testing, per-jurisdiction monitoring).

What it saved: not having a regulator letter. The cost of getting this wrong dwarfs the cost of getting it right by 100×.

What transfers

For any platform serving multiple jurisdictions with strict data-residency rules:

  1. Treat data residency as physical, not logical. The data lives in a place; you can prove where.
  2. Cross-jurisdiction flows need explicit approval, default-denied.
  3. Reports are per-jurisdiction unless aggregated insights are computed off-platform.
  4. Each jurisdiction has its own audit; auditors are scoped to their jurisdiction.

The Bancnet pattern works at three jurisdictions. The same shape extends to N. The cost is linear; the safety is constant.

← Back to all posts