⚡ ⚡ Learn how Investing.com uses Jitsu to move data faster

Data residency for fintech: the pipeline runs where your data lives

Before your security team reads a single feature of an event pipeline, they ask where it runs and whose servers it shares. When policy keeps customer data inside your own cloud account, a shared SaaS pipeline fails that review, and the alternative is building and running one in-house.

Jitsu runs inside that boundary, as Private Cloud in a sub-account of yours or as On Prem. Events from your website, apps and backend go to that deployment, and one function on the way to your warehouse writes a masked copy for analysts and an unmasked copy for a short access list.

TL;DR

  • Private Cloud and On Prem both come with the Enterprise plan, which includes a security and compliance review, SSO via OIDC and an uptime SLA.
  • The masked copy hashes email with SHA-256, sets the last IPv4 octet to 0, and removes phone, name and address.
  • Platform teams that want to run Jitsu themselves can deploy the open-source version on Kubernetes.

Private Cloud or On Prem

Private Cloud is a single-tenant deployment that Jitsu manages on a dedicated GCP or AWS sub-account, yours or Jitsu's, and its servers are not shared with other customers.

On Prem runs Jitsu on your own infrastructure, with any cloud provider or on your own servers.

Two copies of every event

Reconciliation and investigations need events unmasked, while analysts can do their work without anyone's email or full IP address. Monzo's data stack write-up describes this pattern, built in-house on BigQuery.

Jitsu does it with one function, placed last on the warehouse connection behind the guardrail. The fields the guardrail lists by name, and any value outside an ID field that reads as a card number, reach neither copy; add your own field names to its list. This is what each copy holds:

EventSent fromRequired propertiesIn events_rawIn events (masked)
identifyBrowser, app, backenduserId, trait emailtraits unmaskedemail hashed with SHA-256; phone, name, first_name, last_name, address removed
pageBrowserurlfull IP addressIPv4 address with the last octet set to 0 in context.ip and requestIp; IPv6 removed
Account CreatedBackendsignup_methodunmaskedtraits masked as above
KYC RejectedBackendkyc_provider, reason_categoryunmaskedtraits masked as above
Deposit CompletedBackenddeposit_id, amount, currency, funding_methodunmaskedamounts unchanged

The fintech tracking plan (CSV) marks the PII handling for all 35 events.

Add it in the Jitsu UI function editor:

import { hash } from "crypto";

const RAW_TABLE = "events_raw";
const TEST_USERS = new Set(["integration-tests"]);
const HASHED_TRAITS = ["email"];
const REMOVED_TRAITS = ["phone", "name", "first_name", "last_name", "address"];

function maskTraits(traits) {
  if (!traits || typeof traits !== "object") return undefined;
  const out = { ...traits };
  for (const key of REMOVED_TRAITS) delete out[key];
  for (const key of HASHED_TRAITS) {
    if (typeof out[key] === "string") out[key] = hash("sha256", out[key].trim().toLowerCase());
  }
  return out;
}

// 203.0.113.42 becomes 203.0.113.0. Anything that is not an IPv4 address is removed.
function truncateIp(ip) {
  return typeof ip === "string" && /^\d{1,3}(\.\d{1,3}){3}$/.test(ip) ? ip.replace(/\.\d{1,3}$/, ".0") : undefined;
}

// Must be the last function on the connection: it returns two events.
export default async function rawAndMaskedCopy(event) {
  if (TEST_USERS.has(String(event.userId))) return "drop";

  const context = event.context ?? {};
  const masked = {
    ...event,
    requestIp: truncateIp(event.requestIp),
    context: { ...context, ip: truncateIp(context.ip), traits: maskTraits(context.traits) },
  };
  if (event.traits) masked.traits = maskTraits(event.traits);

  return [{ ...event, JITSU_TABLE_NAME: RAW_TABLE }, masked];
}

It returns two events, so it has to be the last function on the connection: only the last function may return more than one event (advanced functions).

  • Jitsu records the sender's IP address in requestIp on every event, separately from context.ip, so the masked copy truncates both.
  • An unsalted SHA-256 of an email is pseudonymous, not anonymous: anyone holding a list of emails can match it. Treat the masked table as personal data, and restrict events_raw to the people who need plaintext.
  • Event backups are a Jitsu Cloud feature, so on a self-hosted install events_raw is also your archive of the events that reach the warehouse.

Checking the two copies

Run this on ClickHouse every day.

-- ClickHouse. Daily row counts in the raw and masked copies, and any plaintext email or full IPv4 address left in the masked one.
SELECT
    day,
    raw_rows,
    masked_rows,
    raw_rows - masked_rows AS difference,
    plaintext_emails,
    full_ips
FROM
(
    SELECT toDate(timestamp) AS day, count() AS raw_rows
    FROM events_raw FINAL
    WHERE timestamp >= today() - 30
    GROUP BY day
) AS raw
FULL OUTER JOIN
(
    SELECT
        toDate(timestamp) AS day,
        count() AS masked_rows,
        countIf(position(context_traits_email, '@') > 0) AS plaintext_emails,
        countIf(match(request_ip, '^\\d+\\.\\d+\\.\\d+\\.\\d+$') AND NOT endsWith(request_ip, '.0')) AS full_ips
    FROM events FINAL
    WHERE timestamp >= today() - 30
    GROUP BY day
) AS masked USING (day)
ORDER BY day;

A non-zero difference that stays after the day's loads finish means one copy failed to load; check that destination's loads in Live Events. If an earlier function on the connection sends some events to their own table, as the KYC page's contract function does with kyc_contract_violations, their masked copies land in that table, so add its daily count to masked_rows before comparing.

When plaintext_emails is above zero, events reached events without passing the function, for example from a connection that does not run it.

Running the open-source version yourself

The production deployment guide covers what a self-hosted install needs:

ComponentWhat it doesNeeded
Kubernetes clusterRuns every serviceAlways
Jitsu operatorProvisions function servers and keeps their routing tableAlways, even with no custom functions
PostgreSQLConfiguration and metadataAlways
Kafka or RedpandaTransport between servicesAlways
ClickHouseJitsu's own events log and metricsAlways
MongoDBFunction key-value store, and matching anonymous IDs to user IDsFor those features
RedisAlternative functions state storeOptional
SyncctlConnector syncs as Kubernetes CronJobsOnly with connectors

It also lists the Kubernetes permissions the operator needs, and the quick start deploys every service to Minikube for a local trial. The self-hosted vs managed comparison covers what it costs to run. Events reach a self-hosted install the same way they reach Jitsu Cloud, at your own domain.

Questions people ask

Can Jitsu Cloud keep data in a specific region? Jitsu Cloud runs in the United States. To keep data inside your own cloud account or network, run Private Cloud in your sub-account, On Prem, or a self-hosted install.

What can a security review get from Jitsu? The security page covers SOC 2, and the report is available on request. Questions the report does not answer go into the security and compliance review that comes with the Enterprise plan.

Is self-hosted Jitsu free? Yes. Jitsu is open source under the MIT license, with no usage limits on a self-hosted install.

Get started with Jitsu

Private Cloud and On Prem start with a demo call that scopes the deployment and the security review. The fintech hub covers the other fintech pipelines. To see the pipeline before that, follow the self-hosting quick start, or send test events to a free Jitsu Cloud workspace.

Subscribe to the Jitsu newsletter
Occasional product updates when we ship something worth knowing about. No spam.