Data residency for fintech: self-hosted Jitsu in your own network
For a bank or lender whose policy keeps customer data in its own network, data residency means the event pipeline runs there too. Self-hosted Jitsu gives a regulated team exactly that: the pipeline runs in your network under the MIT license, and events go only to the destinations you configure. Jitsu is open source, and you deploy it on Kubernetes with the Jitsu operator, following the production deployment guide: ingest, rotor, bulker, the console, the operator and the function servers it manages, with PostgreSQL, Kafka or Redpanda, ClickHouse and MongoDB underneath.
This page is for the platform or infrastructure lead, security architect or data platform lead at a bank, lender or payments company. It is part of the fintech hub.
TL;DR
- Self-hosted Jitsu runs in your network, free under the MIT license, with no usage limits.
- Events go only to the destinations you configure.
- A production install runs on Kubernetes with the Jitsu operator, which routes every event, following the production deployment guide.
- Self-hosted Jitsu does not archive events, so a raw copy belongs in a warehouse or file-storage destination you run.
- Identity stitching has no plan restriction on a self-hosted install.
The problem in plain words
A regulated team reviewing any event pipeline asks three things: where it runs, what leaves the network, and what the vendor can sign. When policy keeps customer data in the company network, a SaaS pipeline fails the first question. In-house code passes it, at the price of a new consumer for every new feed.
Self-hosting an open-source pipeline sits between the two, and the operating work moves to your platform team. This page lists that work; the self-hosted vs managed comparison covers what it costs to run.
What a self-hosted install needs
| Component | What it does | Needed |
|---|---|---|
| Kubernetes cluster | Runs every service | Always: the operator, which routes every event, runs on it |
| Jitsu operator | Provisions function servers and keeps their routing table | For routing any event, even with no custom functions |
| PostgreSQL | Configuration and metadata | Always |
| Kafka or Redpanda | Transport between services | Always |
| ClickHouse | Jitsu's own events log and metrics | Always |
| MongoDB | Function key-value store and identity stitching | For those features |
| Redis | Alternative functions state store | Optional |
| Syncctl | Connector syncs as Kubernetes CronJobs | Only with connectors |
The list comes from the production deployment guide, which also covers the Kubernetes permissions the operator needs. Docker Compose, the legacy setup, is deprecated and cannot run functions or connector syncs (self-hosting overview). To try every service locally first, the quick start deploys them to Minikube.
Identity stitching, an Enterprise feature on Jitsu Cloud, has no plan restriction on a self-hosted install. It needs MongoDB, rewrites anonymous events in six warehouses (ClickHouse, BigQuery, PostgreSQL, Redshift, Snowflake, MySQL), and goes back up to 30 days.
The events
| Event | Sent from | Required properties | In events_raw | In events (masked) |
|---|---|---|---|---|
identify | Browser, app, backend | userId, trait email | traits as sent | email hashed with SHA-256; phone, name, first_name, last_name, address removed |
page | Browser | url | IP address as sent | IPv4 address with the last octet set to 0 in context.ip and requestIp; IPv6 removed |
Account Created | Backend | signup_method | as sent | traits masked as above |
KYC Rejected | Backend | kyc_provider, reason_category | as sent | traits masked as above |
Deposit Completed | Backend | deposit_id, amount, currency, funding_method | as sent | amounts unchanged |
Events reach a self-hosted install the same way they reach Jitsu Cloud, at your own domain: the HTTP API with a server-to-server write key and Strict Mode, Jitsu's JavaScript library on the web. The full list of 35 events, with PII handling for each, is in the fintech tracking plan (CSV), and the backend calls are written out on the onboarding and KYC funnel page.
The function: a raw copy and a masked copy
Inside your own network, one split keeps an unmodified copy for reconciliation and investigations, readable by a few people, and a masked copy for analysts. Monzo's data stack write-up describes the same split, sanitized and raw event tables in BigQuery, built in-house. In Jitsu it is one function on the warehouse connection; paste it into the function editor in the console, which allows the crypto import:
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
requestIpon every event, separately fromcontext.ip, so the masked copy truncates both. - Run the PII guardrail from the fintech hub before it, so card numbers reach neither copy.
- 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_rawto the people who need plaintext. - Splitting doubles the event count. On Jitsu Cloud each resulting event is billed as an active event; a self-hosted install under the MIT license has no Jitsu bill.
Querying it
ClickHouse, against the two tables the function writes. FINAL collapses rows that share a message_id, so retries do not inflate either count, and the timestamp filters keep it to the last 30 days.
-- 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 means one copy failed to load on that day; check bulker's results in Live Events.
If plaintext_emails is above zero, events reached events without passing the function, for example from a connection that does not run it. An email sent as a property lands in its own column, such as email, which this check does not read.
Private Cloud and On Prem
On Prem and Private Cloud are on the Enterprise plan (pricing, contact sales). Jitsu Cloud itself is hosted in the United States.
Example implementation (illustrative): a consumer lender running its own cluster
Illustrative example: not a Jitsu customer.
A consumer lender keeps customer data inside its own cloud account and already runs Kubernetes there. Its platform team deploys Jitsu on Kubernetes with the Jitsu operator, following the production deployment guide. The web app uses Jitsu's JavaScript library and the loan system posts application and decision events over the HTTP API.
One connection writes to its warehouse with the guardrail first and the raw-and-masked function last. Analysts read events, a short access list covers events_raw, and the reconciliation query runs daily.
Where Jitsu does not fit
- Kubernetes is required to route any event, and for functions and connector syncs. Docker Compose is deprecated and cannot run them.
- Profile Builder is documented on the Enterprise plan of Jitsu Cloud only (profiles docs).
- Event backups are a Jitsu Cloud feature; self-hosted Jitsu does not archive events (event backups). Keep a raw copy in a destination you run.
- Functions are JavaScript, or TypeScript built with the Jitsu CLI, and there is no Python runtime.
Questions people ask
Is self-hosted Jitsu free? Yes. Jitsu is open source under the MIT license, with no usage limits on a self-hosted install. On Prem and Private Cloud are on the Enterprise plan.
Where does event data go on a self-hosted install? To the destinations you configure, inside or outside your network.
How is a production install deployed? On Kubernetes with the Jitsu operator, following the production deployment guide.
Can Jitsu Cloud keep data in a specific region? Jitsu Cloud is hosted in the United States and no other Cloud region is documented. For data that has to stay in a region, self-host Jitsu. On Prem and Private Cloud are on the Enterprise plan.
Get started with Jitsu
On Prem and Private Cloud are on the Enterprise plan (Book a demo). To try the pipeline on your own events first, Get started on Jitsu Cloud, or follow the self-hosting quick start.