# Jitsu — full text Source: https://jitsu.com/docs/quick-start # Quick Start ## Step 1: Create Jitsu Instance Try [Jitsu.Cloud](https://use.jitsu.com). It's free for up to 200,000 events per month; Or (advanced) [Host Jitsu on you servers](/docs/self-hosting/) ## Step 2: Add Site Add your site or app to Jitsu. You can add multiple sites. Open your Jitsu dashboard, go to **Sites » Add Site** and fill the form. Adding custom domain is optional. If you don't have one, you can use the default one. It will look like this: `https://.d.jitsu.com` :::info Read more about [sites](/docs/core-concepts/#sites) on [core concepts](/docs/core-concepts/) page. ::: ## Step 3: Add Warehouse Destination Go to **Destination» Add Destination** Select from supported destination types and fill the form. :::info Read more about [destination](/docs/core-concepts/#destinations) on [core concepts](/docs/core-concepts/) page. ::: ## Step 4: Connect site and destination Go to **Connections» Connect site and destination** Here you need to choose what source and destination to connect. Also, you can set up additional connection options that are specific to the destination type, e.g: for data warehouse destinations you can choose from Batch or Stream mode and set up data layout. On the same form builtin or user defined [Functions](/docs/functions) can be added to processing pipeline for specific connection. :::info Read more about [connection](/docs/core-concepts/#connection) on [core concepts](/docs/core-concepts/) page. ::: --- Source: https://jitsu.com/docs/core-concepts # Core concepts See a brief definition of the main items in Jitsu and how they related to each other. Core concepts are: * [Site](#sites) (aka Push Sources, aka Steam), * [Destination](#destinations) * [Cloud Destination](#cloud-destinations) * [Device Destination](#device-destinations) * [Data Warehouse](#data-warehouse-destinations) * [Connections](#connections) - link between Site and Destination (many-to-many) * [Function](#functions) * [Connector](#connectors) * [Sync](#syncs) - link between Connector and Destination (many-to-many) ## Sites Also known as `Stream` or `Push Source` or `App` *Site* indicates the source of incoming events. While typically the source is a website or web application, it could be also mobile application, backend service, or any other source of events. The site generates events and pushes them to Jitsu. Events are typically triggered by user actions such as page views, screen views, conversions etc. Each project can have multiple sites. There're number of ways to send data from the site: * [HTML snippet](/docs/sending-data/html) * [React Lib](/docs/sending-data/react) * [JS Package](/docs/sending-data/npm) * [HTTP API](/docs/sending-data/http) See [Sending data](/docs/sending-data) for more details. ## Destinations Destination is a place where Jitsu sends events coming from Sites. Destination could be a database () or external service (e.g.Product Analytics, or CRM). Before sending to destination Jitsu batches data if necessary and applies [functions](/docs/functions). ### Data Warehouse Destinations Data warehouse destinations are used to store raw events in a data warehouse. Jitsu supports number of data warehouses: [BigQuery](/docs/destinations/warehouse/bigquery), [ClickHouse](/docs/destinations/warehouse/bigquery), [Redshift](/docs/destinations/warehouse/bigquery), [MySQL](/docs/destinations/warehouse/mysql), [Postgres](/docs/destinations/warehouse/mysql) and [more](https://jitsu.com/integrations/destinations). Datawarehouse destinations support stream and batch modes: * Stream - events are sent to destination as soon as they are received by Jitsu * Batch - events are sent to destination in batches. Batch size is configurable In most cases, batch mode is preferable. Most of the data warehouses (on the high volumes) either do not support streaming at all, or significantly more expensive. The only exception is ClickHouse with [Async Inserts](https://clickhouse.com/blog/asynchronous-data-inserts-in-clickhouse) ### Cloud Destinations Cloud destinations are external SaaS services that accept events via API. The most common examples are product analytics services and CRMs. See a list of supported destinations [here](https://jitsu.com/integrations/destinations) ### Device Destinations Device destinations are being executed on a client-side by exectuing JavaScript code (aka JS Tag, or JS Pixel) in the browser. The main examples are [Google Analytics 4](/docs/destinations/ga4-tag) and [Tag](/docs/destinations/tag) — an arbitrary piece of JavaScript code :::caution [Functions](/docs/functions) are applied only to Datawarehouse destinations and Cloud Destinations, but not to Device destinations ::: ## Connections Connection is a link between Site and Destination. Each connection has a set of rules that define how data is processed `Site <-> Destination` is `many-to-many` relationship. It means that destination can receive data from multiple sites. And site can send data to multiple destinations (it is call multiplexing) ## Functions Function is a JavaScript code that is applied to tge events from site. The typical use-case is data filtering, enrichment and transformation. [Read a detailed function guide to learn more](/docs/functions) ## Connectors Connectors, or `Pull Sources` are pulling data from external services via API and store the data into Destinations. Connectors mostly are wrappers around [Airbyte](https://github.com/airbytehq/airbyte/tree/master/airbyte-integrations/connectors/) connectors, but some connectors a "Native", they are maintained by Jitsu team. See a full list of connectors [here](https://jitsu.com/integrations/connectors?showCatalog=true) ## Syncs Sync is a link between a connector and a destination. You can create multiple syncs for the same connector, which makes `Connector <-> Destination` `many-to-many` relationship. The sync defines what data to pull from the connector, and how often :::caution [Functions](/docs/functions) can't be applied to data that comes from Connectors ::: --- Source: https://jitsu.com/docs/api # Management API Jitsu exposes an HTTP API for managing workspaces, sites, destinations, connections, syncs and functions. The Jitsu UI is a client of this same API. :::info Examples below reference `use.jitsu.com`, the Jitsu Cloud instance. If you self-host Jitsu, replace it with your own host. ::: The full reference is generated from the [OpenAPI spec](https://use.jitsu.com/api/spec.yaml) and is listed in the sidebar under each resource — start with [Workspace](/docs/api/reference/workspace) or [Configuration](/docs/api/reference/configuration). ## Authentication All endpoints require an `Authorization` header with a personal API key: ``` Authorization: Bearer ``` Generate keys in the Jitsu UI on the [user settings page](https://use.jitsu.com/user). :::tip API keys have the format `{keyId}:{keySecret}`. The secret is only shown once at creation — copy it then. ::: You'll also need your `workspaceId`, visible on the workspace settings page: ## CLI Most things you can do over HTTP can also be done with [`jitsu-cli`](/docs/jitsu-cli), which calls this API under the hood. Quick start: ```bash npm i -g jitsu-cli jitsu-cli login jitsu-cli config destinations list -w my-workspace ``` ## Cascade delete When deleting an entity that other entities reference (Connections or Syncs), two query parameters control behaviour: - **`strict`** (default `false`) — if `true`, refuses to delete an entity that is referenced elsewhere. - **`cascade`** (default `false`) — if `true`, also deletes the referencing entities. Use with caution. --- Source: https://jitsu.com/docs/mcp # MCP Server Jitsu runs an [MCP](https://modelcontextprotocol.io) server, so AI agents can manage your pipeline directly: create destinations, wire up streams, inspect [Live Events](/docs/features/live-events), and edit [Functions](/docs/functions). There is a single endpoint: ``` https://use.jitsu.com/mcp ``` :::info `use.jitsu.com` is the Jitsu Cloud console. If you self-host Jitsu, replace it with your own console host — the MCP server is served at `/mcp` on the same host. ::: For interactive clients, authentication is **OAuth 2.1** — you don't paste an API key. The first time a client uses the server, a browser tab opens asking you to approve the connection. Approve it once and the client gets a scoped, revocable token. The connection shows up under your account and can be revoked any time (see [Managing access](#managing-access)). In CI and other headless environments the browser flow can't run. There you authenticate with a personal API key instead — see [Automation and CI](#automation-and-ci). ## Connect a client The endpoint is the same everywhere; only the configuration format differs. Add the server with one command: ```bash claude mcp add --transport http jitsu https://use.jitsu.com/mcp ``` The next time the agent calls a Jitsu tool, Claude Code opens a browser tab for the OAuth approval. Run `/mcp` inside Claude Code to check the connection status or re-authenticate. 1. Open **Settings → Connectors**. 2. Click **Add custom connector**. 3. Name it `Jitsu` and set the URL to `https://use.jitsu.com/mcp`. 4. Click the connector and **Connect** — a browser window opens for the OAuth approval. Add Jitsu to your MCP config — `~/.cursor/mcp.json` for all projects, or `.cursor/mcp.json` in a single project: ```json { "mcpServers": { "jitsu": { "url": "https://use.jitsu.com/mcp" } } } ``` Open **Settings → MCP**, and Cursor will prompt you to log in to Jitsu through the browser. Create `.vscode/mcp.json` in your workspace (or run **MCP: Add Server** from the command palette and pick **HTTP**): ```json { "servers": { "jitsu": { "type": "http", "url": "https://use.jitsu.com/mcp" } } } ``` Start the server from the `mcp.json` editor or the **MCP: List Servers** command. VS Code opens a browser tab for the OAuth approval on first use. ## Automation and CI OAuth needs a browser, so it doesn't work in CI, cron jobs, or any headless setup. For those, authenticate with a personal API key — the same key the [Management API](/docs/api) uses. The key maps to your user and inherits your workspace access, so an agent running with it can do anything you can. Generate a key on the [user settings page](https://use.jitsu.com/user) and pass it in an `Authorization` header. API keys have the format `{keyId}:{keySecret}`; the secret is shown only once at creation. Keys can be set to never expire, which is what you want for CI. ``` Authorization: Bearer ``` With Claude Code, add the header on the command line: ```bash claude mcp add --transport http jitsu https://use.jitsu.com/mcp \ --header "Authorization: Bearer $JITSU_API_KEY" ``` For clients configured through a file, set a static header on the server — the same shape as in [Connect a client](#connect-a-client), plus a `headers` block. Read the key from an environment variable rather than committing it. For Cursor (`~/.cursor/mcp.json`): ```json { "mcpServers": { "jitsu": { "type": "http", "url": "https://use.jitsu.com/mcp", "headers": { "Authorization": "Bearer ${JITSU_API_KEY}" } } } } ``` VS Code uses a top-level `servers` key instead of `mcpServers`; add the same `headers` block to the `jitsu` entry shown in its tab above. Revoke a key from the same [API Keys](https://use.jitsu.com/user) page to cut off access. ## What the agent can do The tools mirror the [Management API](/docs/api) and cover the whole surface: configuration, [Live Events](/docs/features/live-events), connector syncs, testing and debugging, and usage statistics. ### Configuration Every configuration object follows the same `list / get / create / update / delete` shape, so an agent that learns one resource knows them all. | Tool | What it does | | --- | --- | | `list_workspaces` | List the workspaces you can access | | `list_resources` | List resources of a type in a workspace | | `get_resource` | Get a single resource by id | | `get_resource_schema` | Get the JSON Schema for creating/updating a resource type | | `create_resource` | Create a resource | | `update_resource` | Update a resource by id | | `delete_resource` | Delete a resource by id | Resource `type` is one of `destination`, `stream`, `service`, `function`, `connection`, `domain`, `custom-image`, or `notification`. The agent typically calls `get_resource_schema` first to learn the exact payload, then `create_resource` or `update_resource`. ### Live Events, testing and debugging | Tool | What it does | | --- | --- | | `list_event_sources` | List the sources — streams, connections, destinations — you can read events for | | `query_events` | Read recent [Live Events](/docs/features/live-events) records — incoming events, function logs, warehouse write statuses, and the dead-letter queue | | `test_connection` | Test destination credentials — an existing destination, or an unsaved config before creating it | | `run_function` | Run a [function](/docs/functions) against a sample event and get back the result, logs, and store mutations — nothing is persisted | | `run_profile_builder` | Same, for a profile-builder function against sample events | `query_events` exposes the same real-time stream as the [Live Events](/docs/features/live-events) view in the UI, and takes the same filters an operator would use: a single source or all of them at once, log levels (e.g. errors only), a time range, and substring search. The agent calls `list_event_sources` first to find the stream, connection, or destination to watch. Combined with the config tools, this lets an agent verify its own work — create a connection, send a test event, check that it landed. Because [Functions](/docs/functions) are configuration objects too, the agent can read a function's code with `get_resource`, try a fix against a sample event with `run_function`, and ship it with `update_resource`. So when Live Events shows a function throwing, the agent can find the bug, prove the fix, and deploy it without leaving the conversation. ### Connectors and syncs The full connector lifecycle — from checking credentials to re-running a sync from scratch: | Tool | What it does | | --- | --- | | `get_connector_spec` | JSON schema of a connector package's credentials | | `check_source_credentials` / `get_source_check_result` | Verify a service's stored credentials against the real source | | `discover_streams` | Ask a connector which streams it can sync (the catalog used to configure a sync) | | `run_sync` | Start a sync run, optionally a full re-sync | | `cancel_sync` | Cancel a running sync task | | `list_sync_tasks` | Recent sync tasks, filterable by sync, status, and time range | | `get_sync_logs` | Log records of a sync task | | `get_sync_state` | The saved incremental cursor of a sync, keyed by stream | | `reset_sync_state` | Delete the saved cursor — for one stream or all — so the next run re-syncs from scratch | Connector operations that talk to the actual source (`get_connector_spec`, `discover_streams`, `check_source_credentials`) are asynchronous: the first call starts the work and the agent polls until the result is ready. The tool descriptions spell this out, so agents handle it on their own. ### Statistics | Tool | What it does | | --- | --- | | `get_event_stat` | Event counts per period, connection, stream, destination, and status | | `get_sync_stat` | Distinct source→destination pairs with at least one successful sync in a period | | `get_profile_builder_stats` | Build progress of a profile-builder version | ### Audit log | Tool | What it does | | --- | --- | | `get_audit_log` | Read the audit log: configuration changes with a field-level diff (secrets masked), membership changes, and logins — filterable by type, severity, origin (`ui`, `api`, `cli`, `mcp`) and time range | `get_audit_log` reads one workspace when given its id or slug (workspace owners only), or every workspace when called without one (platform admins only). Results are paged: when a response carries `nextCursor`, the agent passes it back as `cursor` to get the next page. With `origin: mcp` an agent can review exactly what it — or another agent — changed before. ### Tool annotations Every tool carries the standard [MCP tool annotations](https://modelcontextprotocol.io/specification/2025-03-26/server/tools#tool-annotations) (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`), so a client can auto-approve read-only tools and ask for confirmation only where it matters — the genuinely destructive ones are `update_resource`, `delete_resource`, and `reset_sync_state`. The annotations are enforced server-side too: tools that aren't read-only are rejected while the deployment is in maintenance or read-only mode. ## Managing access Each connected client holds a token issued through OAuth. To see or revoke them, open the [**API Keys** section of your account](https://use.jitsu.com/user). Revoking a key disconnects that client immediately; reconnecting re-runs the browser approval. Every configuration change made through the MCP server lands in the workspace audit log with an **MCP** origin, so you can always tell what an agent changed, as opposed to a person in the UI or a script on the API. The log is also readable through MCP itself — workspace owners (and platform admins, across workspaces) can call `get_audit_log` to review what changed and by whom. --- Source: https://jitsu.com/docs/sending-data # Sending Data Jitsu offers a variety of ways to ingest events data. Jitsu UI has interactive instructions for various integration methods. You can find them in the **Setup instruction** section of your Site context menu. - [HTML Snippet](/docs/sending-data/html) - [HTTP API](/docs/sending-data/http) - [Javascript Library](/docs/sending-data/npm) - [React](/docs/sending-data/react) - [Segment Proxy](/docs/sending-data/segment-proxy) --- Source: https://jitsu.com/docs/sending-data/js-reference # JavaScript Reference Whether you used [HTML snippet](/docs/sending-data/html), [JavaScript library](/docs/sending-data/npm), or [React](/docs/sending-data/react) you'll have access to the same API to send events. The API is based on [Analytics.js](https://getanalytics.io/api/), and is compatible with it. ## Configuration ### Basic Configuration | Name | Script Attribute | Description | |----------------|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `writeKey` | `data-write-key` | Browser Write Key configured on Jitsu Site entity. If no Browser Write Key is added for Site entity, Site ID value can be used a Write Key. On Jitsu.Cloud can be omitted if Site has explicitly mapped domain name | | `host` | - | Jitsu installation domain, e.g. `your-jitsu-domain.com`. For HTML snippet value is assumed from script URL. | | `debug` | `data-debug` | Enables debug log messages in Browser: `true` or `false`. Default `false` | | - | `data-onload` | Function to call after the script has loaded. Function should be previously defined in `window` | | - | `data-init-only` | By default, the script will send a `page` event. Set this to `true` to just initialize the library. You still will be able to send events manually by setting `data-onload` hook | ### Advanced Configuration | Name | Script Attribute | Description | |-------------------------|---------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `idEndpoint` | `data-id-endpoint` | Endpoint that makes sure that Jitsu anonymousId cookie is set as server (httpOnly) cookie. Endpoint must be hosted on the same domain as the site where Jitsu code is installed. Required to overcome [Safari ITP restrictions](/docs/sending-data/js-reference/itp). | | `cookieDomain` | `data-cookie-domain` | Explicitly specify cookie domain. If not set, cookie domain will be set to top level of the current domain. Example: if JS lives on "app.example.com", cookie domain will be set to ".example.com". If it lives on "example.com", cookie domain will be set to ".example.com" too | | `fetch` | - | Custom implementation of Fetch API `fetch` method | | `fetchTimeoutMs` | `data-fetch-timeout-ms` | Timeout for fetch requests. Default value: `5000` | | `echoEvents` | `data-echo-events` | If `true`, Jitsu will output events in console. In this case you don't need to set writeKey / host. It's useful for debugging development environment | | `defaultPayloadContext` | `data-default-payload-context`
*expects stringified json value* | Default context object that will be merged with the `context` of every event. | | `cookieNames` | `data-cookie-names`
*expects stringified json value* | Map of alternative names for standard cookies. Format: `{"anonymousId":"my_eventn_id"}`
Default values:
`anonymousId`: `__eventn_id`
`userId`: `__eventn_uid`
`userTraits`: `__eventn_id_usr`
`groupId`: `__group_id`
`groupTraits`: `__group_traits` | | `cookieCapture` | `data-cookie-capture`
*expects stringified json value* | Map of cookies to capture in addition to Facebook's `_fbc`,`_fbp` and Google's `_ga` ids. Format: `{"id":"cookie-name"}`. Captured cookies will be added to `context.clientIds` object of event payload. | ### Privacy Settings :::info Available since Jitsu v2.8.0 and npm packages v1.9.7 ::: Privacy settings are nested under `privacy` object of Jitsu Options: ```javascript const analytics = jitsuAnalytics({ host: "https://your-jitsu-domain.com", privacy: { dontSend: false, disableUserIds: true, ipPolicy: "stripLastOctet", consentCategories: { "Analytics": true, "Marketing": false } } }); ``` | Name | Script Attribute | Description | |---------------------|--------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `dontSend` | `data-privacy-dont-send` | If `true`, disables storing anything in cookies and sending events to Jitsu servers. | | `disableUserIds` | `data-privacy-user-ids` | If `true`, disables storing in cookies and sending to Jitsu servers any user identifiers (including anonymousId). In this mode `identify` and `group` calls will have no effect. | | `ipPolicy` | `data-privacy-ip-policy` | Controls how Jitsu collects information about user's IP: `keep` - collect full IP, `stripLastOctet` - collect only first 3 octets, e.g: `104.154.19.0` , `remove` - do not collect user's IP | | `consentCategories` | - | Object containing user's consent state by category name. Provided value will be passed to Jitsu in every event in `context.categoryPreferences` object. | ## Methods ### `.page()` Trigger page view. ```javascript //trigger page view with a custom name jitsu.page("Page Name"); //trigger page view with a custom properties jitsu.page({ propName: "propVal" }); //trigger page view with a name AND custom properties jitsu.page("Page Name", { propName: "propVal" }); ``` :::tip For [HTML Snippet](/docs/sending-data/html) page view is triggered automatically, unless `data-init-only` attribute is set to `true`. ::: ### `.identify()` Identify a user. ```javascript // Identify user: `xyz` as a userId, and additional properties (traits) jitsu.identify('xyz', { name: 'Michael Scott', company: 'Dunder Mifflin', }) //or just set a userId jitsu.identify('xyz') ``` `$doNotSend` is a special property that tells Jitsu to save user and it's properties to local storage, but not to send it to the server. This is useful to avoid sending excessive volumes of events to Jitsu ```javascript jitsu.identify('xyz', { name: 'Michael Scott', company: 'Dunder Mifflin', $doNotSend: true }) ``` :::tip Use `.identify()` for permanent user identifiers such as a database id of registered user. Use `.setAnonymousId()` for temporary identifiers such as cookies ::: ### `.track()` Send a custom event. ```javascript // Event name only jitsu.track("buttonClick"); // Event with properties jitsu.track("itemPurchased", { price: 99 }); ``` ### `.group()` Assign user to a group, usually a company or organization. ```javascript // Group ID. Use permanent identifiers such as a database id of company jitsu.group("g-xyz"); //Group with properties jitsu.group("g-xyz", { name: "Dunder Mifflin" }); ``` ### `.setAnonymousId()` Jitsu automatically detects anonymous id for each visitor. You can override it with `.setAnonymousId()` ```javascript //Set anonymous id of a user, such as ID of the visitor based on cookie jitsu.setAnonymousId("xyz"); ``` :::warning Jitsu automatically detects anonymous id for each visitor, you rarely need to use `.setAnonymousId()`. Use at your own risk. ::: ### `.setContextProperty()` :::info Available since Jitsu v2.11.0 and npm packages v1.10.1 ::: Allows to set a `context` property that will be sent with every event. Effectively, it sets property of `defaultPayloadContext` object that Jitsu merges with a standard `context` for every event. ```javascript jitsu.setContextProperty("pageViewId", "12345"); ``` ### `.getContextProperty()` :::info Available since Jitsu v2.11.0 and npm packages v1.10.1 ::: Get a property of `defaultPayloadContext` object if available. ```javascript const pageViewId = jitsu.getContextProperty("pageViewId"); ``` ### `.configure()` :::info Available since Jitsu v2.8.0 and npm packages v1.9.7 ::: Change Jitsu configuration on the fly. Only `privacy`, `debug` and `echoEvents` settings can be changed on the fly. ```javascript //Change Jitsu configuration on the fly jitsu.configure({ debug: true, privacy: { dontSend: true, disableUserIds: true, ipPolicy: "stripLastOctet" } }); ``` ### `.getConfiguration()` :::info Available since Jitsu v2.11.0 and npm packages v1.10.3 ::: Return current Jitsu configuration. --- Source: https://jitsu.com/docs/sending-data/js-reference/itp # ITP Mitigation :::info Available since Jitsu v2.8.0 and npm packages v1.9.7 ::: ## Safari Intelligent Tracking Prevention Intelligent Tracking Prevention (ITP) is an automatic feature of the Safari web browser designed to limit user tracking. ITP employs multiple measures to achieve this, one of which is the limited lifespan of first-party cookies :::info In Safari browser first-party cookies will be deleted after 7 days without access. ::: Since Jitsu relies on first-party cookies to store anonymous user IDs, ITP may negatively affect the quality of data collected by Jitsu. For example, if some user returns to a particular site after a pause of longer than 7 days, he will receive a new anonymous ID and may appear as a new user (at least unless `identify` is call is used). ## Mitigation Not all first-party cookies are subject to the ITP's 7-day expiry limit. Cookies with the `HttpOnly` flag, served directly from the customer’s website server, will not be removed by ITP. To enable ITP mitigation, customers need to add a simple service (endpoint) to their website that adheres to Jitsu’s specifications. ### Server setup Jitsu ID endpoint must be added to customer's website. #### Endpoint requirements: 1. ID endpoint must respond on the same domain as the website. (That can be a subdomain only if it resolves to the same IP address as the main domain) 2. Respond to `GET` HTTP method. 3. Read **domain** parameter from request query string. 4. Read `__eventn_id` or `__eventn_id_srvr` HTTP request cookie as a source of **anonymousId** value 5. If it couldn't acquire value for **anonymousId** from either of these cookies, generate a new one (UUID string) 6. Add `__eventn_id` and `__eventn_id_srvr` cookies to "Set-Cookie" headers with the following parameters:
`Set-Cookie: __eventn_id=anonymousId; Domain=domain; Max-Age=157680000; Path=/; SameSite=None; Secure;`
`Set-Cookie: __eventn_id_srvr=anonymousId; Domain=domain; Max-Age=157680000; Path=/; SameSite=None; Secure; httpOnly=true;`
where **domain** was acquired from query string and **anonymousId** was set previously 7. Send response with status `200` and JSON payload: `{ "anonymousId": anonymousId }` #### Optional part: It is also possible to protect the userId cookie from expiration, but a better approach is to `identify` the user on every new session. 8. Read `__eventn_uid` or `__eventn_uid_srvr` HTTP request cookie as a source of **userId** value 9. If it couldn't acquire value for **userId** skip the rest and do nothing 10. Add `__eventn_uid` and `__eventn_uid_srvr` cookies to "Set-Cookie" headers with the following parameters:
`Set-Cookie: __eventn_uid=userId; Domain=domain; Max-Age=157680000; Path=/; SameSite=None; Secure;`
`Set-Cookie: __eventn_uid=userId_srvr; Domain=domain; Max-Age=157680000; Path=/; SameSite=None; Secure; httpOnly=true;`
where **domain** was acquired from query string and **userId** was set previously 11. Change response with status `200` and JSON payload: `{ "anonymousId": anonymousId, "userId": userId }` ### Client setup Jitsu client library must be configured to use ID endpoint with `idEndpoint` parameter: ```html ``` ```javascript const analytics = jitsuAnalytics({ host: "https://your-jitsu-domain.com", // path or full URL of ID endpoint idEndpoint: "/api/jitsu-id", }); ``` ```javascript ``` ### Server code example Here is an example of ID endpoint implemented in Typescript language for Next.js 14 framework: ```typescript import {NextRequest, NextResponse} from 'next/server' import {randomUUID} from "crypto"; const USER_COOKIE = "__eventn_uid"; const ANON_COOKIE = "__eventn_id"; function getDomain(request: NextRequest) { let domain = request.nextUrl.searchParams.get("domain"); if (domain) { return domain; } domain = request.headers.get("host")?.toString() ?? ""; if (domain.startsWith("localhost")) return "localhost"; return domain; } function renewCookies(request: NextRequest, headers: Headers, browserName: string, serverName: string, generateNew: boolean) { const cookie = request.cookies.get(browserName) || request.cookies.get(serverName); let cookieValue = cookie?.value if (!cookieValue) { if (!generateNew) return; cookieValue = randomUUID(); } const secure = request.headers.get("x-forwarded-proto") === "https" ? " Secure;" : "" const sameSite = ` SameSite=${secure ? "None" : "Lax"};` const maxAge = 31_536_000 * 5; // 5 years in seconds const domain = getDomain(request); headers.append("Set-Cookie", `${browserName}=${cookieValue}; Max-Age=${maxAge}; Domain=${domain}; Path=/;${sameSite}${secure}`) headers.append("Set-Cookie", `${serverName}=${cookieValue}; Max-Age=${maxAge}; Domain=${domain}; Path=/;${sameSite}${secure} httpOnly=true;`) return cookieValue; } export async function GET(request: NextRequest) { const headers = new Headers({ 'Cache-Control': "must-revalidate,no-cache,no-store", 'Content-Type': "application/json" }) const anonymousId = renewCookies(request, headers, ANON_COOKIE, `${ANON_COOKIE}_srvr`, true); const userId = renewCookies(request, headers, USER_COOKIE, `${USER_COOKIE}_srvr`, false); const payload = { anonymousId: anonymousId, userId: userId } return new NextResponse(JSON.stringify(payload), { status: 200, headers: headers }); } ``` --- Source: https://jitsu.com/docs/sending-data/html # HTML snippet To start tracking events just add following snippet to the `` section of your website: :::tip In all examples below, replace `your-jitsu-domain.com` with your Jitsu installation domain. Jitsu Cloud users may find domain in the top-right corner of Site's **Setup Instruction** page or attach custom domain for a specific Site and use it instead. ::: ```html ``` ## Configuration You can configure Jitsu by adding `data-*` attributes to the script tag. Example: ```html ``` List of available configuration options: |             Name             | Description | |------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `data-user-id` | Set's user id. Equivalent of calling `jitsu.identify(userId)` | | `data-onload` | Function to call after the script has loaded. Function should be previously defined in `window` | | `data-init-only` | By default, the script will send a `page` event. Set this to `true` to just initialize the library. You still will be able to send events manually by setting `data-onload` hook | | `data-write-key` | Browser Write Key configured on Jitsu Site entity. If no Browser Write Key is added for Site entity, Site ID value can be used a Write Key. On Jitsu.Cloud can be omitted if Site has explicitly mapped domain name | For the full list of available options, see [JavaScript Reference](/docs/sending-data/js-reference) section Alternatively you can define window.jitsuConfig object before inserting the snippet. Properties of the object be same as data attributes, but camel cased and without data- prefix: ```html ``` ### Google Tag Manager Google Tag Manager [strips `data-` attributes](https://support.google.com/tagmanager/thread/18040523/what-attributes-are-preserved-on-custom-html-tags-and-what-attributes-are-stripped?hl=en) from the script tag. To configure Jitsu in GTM, you can use following snippet: ```html ``` ### `onload` hook You can specify a piece of code that will be executed after the script has loaded. This can be useful if you want to send additional events or identify user. Example: ```html ``` ### Jitsu Processing Queue Sometimes you may want to send events to Jitsu when it's not guaranteed that Jitsu is initialized. For that case, you can use `window.jitsuQ` object: ```javascript (window.jitsuQ = window.jitsuQ || []).push(function(jitsu) { //send events to Jitsu here jitsu.page(); }); ``` ## Sending Events to Jitsu `window.jitsu` object implements standard [Analytics.js](https://getanalytics.io/api/) interface. See a full list of methods in [JavaScript Reference](/docs/sending-data/js-reference) section --- Source: https://jitsu.com/docs/sending-data/http # HTTP API You can use HTTP API to send data to Jitsu. This is useful if you want to send data from your backend. :::tip In all examples below, replace `your-jitsu-domain.com` with your Jitsu installation domain, or domain linked to your Jitsu Cloud account. Jitsu Cloud users may find domain in the top-right corner of Site's **Setup Instruction** page or attach custom domain for a specific Site and use it instead. ::: ## Authorization ### Write Key authentication Use `X-Write-Key` header to authenticate requests. The header should contain the write key of the site. ### Basic authentication Jitsu also supports basic authentication in the form of a base64 encoded `username:password` string. Where **Write Key** must be provided as the `username` and the `password` field is left empty. E.g.: `writeKey123:` - despite empty password, the colon `:` is still required. After base64 encoding `writeKey123:` becomes `d3JpdGVLZXkxMjM6Cg==` and this is passed in the authorization header like so: `Authorization: Basic d3JpdGVLZXkxMjM6Cg==`. ### Query parameter You can also pass the `writekey` as a query parameter. This is useful for testing purposes, but not recommended for production use. `https://your-jitsu-domain.com/api/s/{event-type}?writekey=keyId:keySecret` ## Ingest endpoint This endpoint can be used to send events to Jitsu: `https://your-jitsu-domain.com/api/s/{event-type}` Can be used both for browser and server-to-server events depending on Write Key type. **event-type** could be: * `page`, `track`, `identify` or `group` * Use `event` as `event_type` if you want server to take actual event type from `type` field of the event payload The endpoint accepts POST requests with events payload in JSON format. ### Examples ```bash curl --location 'https://your-jitsu-domain.com/api/s/page' \ --header 'Content-Type: application/json' \ --header 'X-Write-Key: keyId:keySecret' \ --data-raw '{ "type": "page", "properties": { "title": "Example page event", "url": "https://example.com/", "path": "/", "hash": "", "search": "", "currency": "USD", "width": 1458, "height": 1186 }, "userId": "user@example.com", "anonymousId": "dBRu6l026JMy7mmUewl5WgCM", "timestamp": "2023-04-12T13:28:02.531Z", "sentAt": "2023-04-12T13:28:02.531Z", "messageId": "GBzdRBFz48ZnuUyASrVYUMKJ", "context": { "library": { "name": "jitsu-js", "version": "1.0.0" }, "ip": "127.0.0.1", "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/111.0", "locale": "en-US", "screen": { "width": 2304, "height": 1296, "innerWidth": 1458, "innerHeight": 1186, "density": 2 }, "traits": { "email": "user@example.com" }, "page": { "path": "/", "referrer": "", "referring_domain": "", "host": "example.com", "search": "", "title": "Example page event", "url": "https://example.com/", "enconding": "UTF-8" }, "campaign": { "name": "example", "source": "g" } }, "receivedAt": "2023-04-12T13:28:02.531Z" }' ``` ```bash curl --location 'https://your-jitsu-domain.com/api/s/identify' \ --header 'Content-Type: application/json' \ --header 'X-Write-Key: abc123' \ --data-raw '{ "type": "identify", "userId": "user@example.com", "traits": { "email": "user@example.com" }, "anonymousId": "aTc2kpU1m9gMARgq9RAizRyj", "timestamp": "2023-04-12T13:28:47.743Z", "sentAt": "2023-04-12T13:28:47.743Z", "messageId": "PgLUzb855vhdmhROLXXGy9zP", "context": { "library": { "name": "jitsu-js", "version": "1.0.0" }, "ip": "127.0.0.1", "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/113.0", "locale": "en-US", "screen": { "width": 2304, "height": 1296, "innerWidth": 2304, "innerHeight": 1186, "density": 2 }, "page": { "path": "/", "referrer": "", "referring_domain": "", "host": "example.com", "search": "", "title": "Example page event", "url": "https://example.com/", "enconding": "UTF-8" }, "campaign": { "name": "example", "source": "g" } }, "receivedAt": "2023-04-12T13:28:47.743Z" }' ``` ```bash curl --location 'https://your-jitsu-domain.com/api/s/track' \ --header 'Content-Type: application/json' \ --header 'X-Write-Key: abc123' \ --data-raw '{ "type": "track", "event": "testEvent", "properties": { "testProp": "test event properties" }, "userId": "user@example.com", "anonymousId": "bKTtbVZw3yiqCJvCSJgjVeXp", "timestamp": "2023-04-12T13:29:04.690Z", "sentAt": "2023-04-12T13:29:04.690Z", "messageId": "voV6fulcZR4CTVnN89AnxFnC", "context": { "library": { "name": "jitsu-js", "version": "1.0.0" }, "ip": "127.0.0.1", "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/111.0", "locale": "en-US", "screen": { "width": 2304, "height": 1296, "innerWidth": 1458, "innerHeight": 1186, "density": 2 }, "traits": { "email": "user@example.com" }, "page": { "path": "/", "referrer": "", "referring_domain": "", "host": "example.com", "search": "", "title": "Example page event", "url": "https://example.com/", "enconding": "UTF-8" }, "campaign": { "name": "example", "source": "g" } }, "receivedAt": "2023-04-12T13:29:04.690Z" }' ``` ## Batch endpoint This endpoint can be used to send multiple events in a single request. Can be used both for browser and server-to-server events depending on Write Key type. **Endpoint**: `https://your-jitsu-domain.com/v1/batch` This endpoint is compatible with [Segment's batch endpoint](https://segment.com/docs/connections/sources/catalog/libraries/server/http-api/#batch) and expects POST request with JSON payload in format: ```json { "batch": [ { "type": "page", "properties": { "title": "Example page event", "url": "https://example.com/", ... }, ... }, ... ], "writeKey": "YOUR_WRITE_KEY", "context": { "device": { "type": "phone", ... } } } ``` where: * `batch` - JSON array of events * `writeKey` - Write Key of the site * `context` - optional context object that will be merged with each event context ## Synchronous Functions endpoint This endpoint runs the [functions](/docs/functions) pipeline configured for a single connection **synchronously** and returns the result in the response body. Unlike the ingest and batch endpoints — which accept the event and process it asynchronously — this endpoint waits for the function pipeline to finish and returns the transformed event(s), per-function execution status, and any console output produced by the functions. This is useful for testing functions, or for use cases where you need the transformed event back in the same request (for example, server-side enrichment). **Endpoint**: `https://your-jitsu-domain.com/api/funcs/{conId}` where `{conId}` is the **Connection ID** that links a Site (stream) to a destination. The connection must belong to the stream identified by the provided Write Key, and must have at least one function configured. The endpoint accepts a POST request with a single [Jitsu event](/docs/sending-data/js-reference) in JSON format. Authorization works exactly the same as for the other HTTP endpoints — see [Authorization](#authorization) above (Write Key via the `X-Write-Key` header, Basic auth, `Authorization: Bearer`, or the `writekey` query parameter). ### Example ```bash curl --location 'https://your-jitsu-domain.com/api/funcs/con_abc123' \ --header 'Content-Type: application/json' \ --header 'X-Write-Key: keyId:keySecret' \ --data-raw '{ "type": "track", "event": "Button Clicked", "messageId": "msg-123", "anonymousId": "anon-456", "properties": { "buttonName": "signup" } }' ``` ### Response On success (HTTP `200`) the endpoint returns a JSON object: ```json { "events": [ { "type": "track", "event": "Button Clicked", "messageId": "msg-123", "anonymousId": "anon-456", "properties": { "buttonName": "signup", "enriched": true } } ], "execLog": [ { "eventIndex": 0, "functionId": "func-abc", "ms": 12.5 } ], "logs": [ { "level": "info", "functionId": "func-abc", "functionType": "udf", "message": "Processing event", "args": [], "timestamp": "2026-03-10T12:00:00Z" } ] } ``` where: * `events` - resulting events after the function pipeline runs. Usually a single-element array with the transformed event; an **empty array** means the event was dropped by a function. * `execLog` - execution status of each function in the pipeline. Each entry contains the `functionId`, the `eventIndex` it applies to, execution time in milliseconds (`ms`), an optional `error` object (`name`, `message`) if the function failed, and a `dropped` flag if the function dropped the event. * `logs` - console log entries (`console.log`, `console.error`, etc.) emitted by the functions, including `level`, `functionId`, `functionType`, `message`, `args` and `timestamp`. If the connection has no functions configured, the endpoint responds with `204 No Content` and no body. ### Status codes | Code | Meaning | |-------|-------------------------------------------------------------------------------------------------| | `200` | Functions executed successfully. Response body contains the result described above. | | `204` | The connection has no functions configured. No body is returned. | | `400` | Bad request — wrong `Content-Type`, unreadable body, malformed JSON, or event processing error. | | `401` | The stream or connection could not be found for the provided Write Key. | | `500` | Internal error, or an error returned by the functions server. | | `504` | The functions server timed out while executing the pipeline. | Error responses have the shape `{ "error": "" }`. The message may include a short error ID for masked internal errors, e.g. `error# a1b2c3: functions server error`. --- Source: https://jitsu.com/docs/sending-data/pixel # Pixel API :::info Available since Jitsu v2.8.2 ::: Jitsu has a GIF Pixel API endpoint for tracking email opens, impressions of advertisements and other cases when: * JavaScript is restricted * only HTTP GET request is allowed * only embedding an image is allowed ## Pixel Endpoint This endpoint can be used as a `src` or `` HTML tag to send events to Jitsu: https://your-jitsu-domain.com/api/px/{event-type} ### Query Parameters | | Description | |------------------------------|-----------------------------------------------------------------------------------------------| | `writekey` | Write Key of configured Jitsu Stream | | `data` | base64 encoded event JSON payload | | JSON path
`path.to.node` | path to node where write the value | | `process_headers` | Boolean. Enrich event with user and page data from HTTP headers: `Referer` and `Cookies` | | `cookie_domain` | When `process_headers=true` set Jitsu Anonymous Id cookie (`__eventn_id`) for provided domain | ### Examples Pass base64 encoded JSON payload to image pixel: ```html ``` Pass event properties to image pixel: ```html ``` --- Source: https://jitsu.com/docs/sending-data/npm # NPM Package `@jitsu/js` is an NPM package that allows you to track events from your JavaScript code. The package is **isomorphic** and can be used in both browser and Node.js environments. :::tip In all examples below, replace `your-jitsu-domain.com` with your Jitsu installation domain. Jitsu Cloud users may find domain in the top-right corner of Site's **Setup Instruction** page or attach custom domain for a specific Site and use it instead. ::: ## Installation ```bash npm install @jitsu/js npm install -D @jitsu/protocols # optional, for TypeScript users ``` ## Usage ```javascript export async function track() { const analytics = jitsuAnalytics({ host: "https://your-jitsu-domain.com", // Browser Write Key configured on Jitsu Site entity. // If no Browser Write Key is added for Site entity, Site ID value can be used a Write Key. // On Jitsu.Cloud can be omitted if Site has explicitly mapped domain name that is used in host parameter writeKey: "", }); await analytics.identify("userId", {email: "test", anyOtherProperty: "value"}); await analytics.track("test page", {pageProperty: "propValue"}); await analytics.page("test", {a: 1}); } ``` ### Sending Events to Jitsu `analytics` object implements standard [Analytics.js](https://getanalytics.io/api/) interface. See a full list of methods in [JavaScript Reference](/docs/sending-data/js-reference) section :::info In browser, page properties such as title, location will be detected automatically. In Node.js, you need to provide them manually. See `RuntimeFacade` interface. Provide you own implementation of `RuntimeFacade` for nodejs ::: --- Source: https://jitsu.com/docs/sending-data/react # React ## Setup Start with adding dependency to your project: `npm install @jitsu/jitsu-react`. Then add `` component close to the root level of your app: :::tip In all examples below, replace `your-jitsu-domain.com` with your Jitsu installation domain. Jitsu Cloud users may find domain in the top-right corner of Site's **Setup Instruction** page or attach custom domain for a specific Site and use it instead. ::: ```jsx import React, {Component} from 'react' import {JitsuProvider} from "@jitsu/jitsu-react"; function App() { return ( ); } ``` ## Options |             Name             | Description | |------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `host` | Host Jitsu installation domain. Jitsu Cloud use instruction in UI to obtain domain. | | `writeKey` | Browser Write Key configured on Jitsu Site entity. If no Browser Write Key is added for Site entity, Site ID value can be used a Write Key. On Jitsu.Cloud can be omitted if Site has explicitly mapped domain name. | ## Manual event tracking Call `useJitsu` hook whenever you need manually trigger events object: ```jsx import { useJitsu } from "@jitsu/jitsu-react"; function ChildComponent() { const { analytics } = useJitsu(); return ; } ``` `analytics` implements standard [Analytics.js](https://getanalytics.io/api/) interface. See a full list of methods in [JavaScript Reference](/docs/sending-data/js-reference) section ## Automatic `page` event tracking Jitsu can automatically track `page` events when user navigates to a new page: ### With react-router: ```jsx import {useJitsu} from "@jitsu/jitsu-react" import {useLocation} from "react-router-dom" function ChildComponent() { const {analytics} = useJitsu() const location = useLocation() useEffect(() => { analytics.page() }, [location]) return <> } ``` ### With Next.js: ```jsx import {useJitsu} from "@jitsu/jitsu-react" import {useRouter} from "next/router" function ChildComponent() { const {analytics} = useJitsu() const router = useRouter() useEffect(() => { analytics.page() }, [router.asPath]) return <> } ``` --- Source: https://jitsu.com/docs/sending-data/react-native # React Native ## Setup Jitsu supports Segment [Analytics for React Native](https://github.com/segmentio/analytics-react-native) library to receive events from React Native applications. Please follow the [Installation instruction](https://github.com/segmentio/analytics-react-native) to install the library. :::tip In all examples below, replace `your-jitsu-domain.com` with your Jitsu installation domain. Jitsu Cloud users may find domain in the top-right corner of Site's **Setup Instruction** page or attach custom domain for a specific Site and use it instead. ::: ## Setting up the client Jitsu requires additional settings to direct events to a correct Jitsu instance. ```javascript import { createClient } from '@segment/analytics-react-native'; const segmentClient = createClient({ proxy: 'https://your-jitsu-domain.com/v1/batch', cdnProxy: 'https://your-jitsu-domain.com/v1/projects', writeKey: 'BROWSER_WRITE_KEY' }); ``` where: | | Description | |------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `proxy` | Jitsu server [batch endpoint](/docs/sending-data/http#batch-endpoint). E.g.: `https://your-jitsu-domain.com/v1/batch` | | `cdnProxy` | CDN host for settings retrieval. E.g.: `https://your-jitsu-domain.com/v1/projects` | | `writeKey` | Browser Write Key configured on Jitsu Site entity. If no Browser Write Key is added for Site entity, Site ID value can be used a Write Key. On Jitsu.Cloud can be omitted if Site has explicitly mapped domain name. | ## Event tracking Please refer to the [Analytics for React Native](https://github.com/segmentio/analytics-react-native) documentation for more details on how to track events with Jitsu. --- Source: https://jitsu.com/docs/sending-data/ios # iOS ## Setup Jitsu supports Segment [Analytics-Swift](https://segment.com/docs/connections/sources/catalog/libraries/mobile/apple/) library to receive events from iOS, tvOS, iPadOS, WatchOS, macOS and Linux applications. Please follow the [Installation instruction](https://segment.com/docs/connections/sources/catalog/libraries/mobile/apple/#getting-started) to install the library. :::tip In all examples below, replace `your-jitsu-domain.com` with your Jitsu installation domain. Jitsu Cloud users may find domain in the top-right corner of Site's **Setup Instruction** page or attach custom domain for a specific Site and use it instead. ::: ## Setting up the client Jitsu requires additional settings to direct events to a correct Jitsu instance. ```swift var analytics: Analytics? = nil func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. let configuration = Configuration( writeKey: "BROWSER_WRITE_KEY", apiHost: "your-jitsu-domain.com", cdnHost: "your-jitsu-domain.com") .trackApplicationLifecycleEvents(true) .flushInterval(10) analytics = Analytics(configuration: configuration) } ``` where: | | Description | |------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `apiHost` | Jitsu server URL. E.g.: `your-jitsu-domain.com` | | `cdnHost` | CDN host for settings retrieval. Jitsu server URL. E.g.: `your-jitsu-domain.com` | | `writeKey` | Browser Write Key configured on Jitsu Site entity. If no Browser Write Key is added for Site entity, Site ID value can be used a Write Key. On Jitsu.Cloud can be omitted if Site has explicitly mapped domain name. | ## Event tracking Please refer to the [Analytics-Swift for iOS & Apple](https://segment.com/docs/connections/sources/catalog/libraries/mobile/apple/) documentation for more details on how to track events with Jitsu. --- Source: https://jitsu.com/docs/sending-data/android # Android ## Setup Jitsu supports Segment [Analytics for Kotlin (Android)](https://segment.com/docs/connections/sources/catalog/libraries/mobile/kotlin-android/) library to receive events from Android applications. Please follow the [Installation instruction](https://segment.com/docs/connections/sources/catalog/libraries/mobile/kotlin-android/#getting-started) to install the library. :::tip In all examples below, replace `your-jitsu-domain.com` with your Jitsu installation domain. Jitsu Cloud users may find domain in the top-right corner of Site's **Setup Instruction** page or attach custom domain for a specific Site and use it instead. ::: ## Setting up the client Jitsu requires additional settings to direct events to a correct Jitsu instance. ```kotlin // Add required imports import com.segment.analytics.kotlin.android.Analytics import com.segment.analytics.kotlin.core.* // Create an analytics client with the given application context and Segment write key. // NOTE: in android, application context is required to pass as the second parameter. Analytics("BROWSER_WRITE_KEY", applicationContext) { apiHost = "your-jitsu-domain.com" cdnHost = "your-jitsu-domain.com" // Automatically track Lifecycle events trackApplicationLifecycleEvents = true flushAt = 3 flushInterval = 10 } ``` where: | | Description | |------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `apiHost` | Jitsu server URL. E.g.: `your-jitsu-domain.com` | | `cdnHost` | CDN host for settings retrieval. Jitsu server URL. E.g.: `your-jitsu-domain.com` | ## Event tracking Please refer to the [Analytics for Kotlin (Android)](https://segment.com/docs/connections/sources/catalog/libraries/mobile/kotlin-android/) documentation for more details on how to track events with Jitsu. --- Source: https://jitsu.com/docs/sending-data/segment-proxy # Segment Proxy If you're using Segment to collect data from your website, you can set up [Webhooks (Actions)](https://segment.com/docs/connections/destinations/catalog/actions-webhook/) destination to send data to Jitsu Use this step-by-step guide to set up Segment proxy ## Add Webhooks (Actions) destination 1. Go to **Connections** / **Catalog** section in you Segment Workspace and find Webhooks (Actions) destination: 2. Select data source your want to send to Jitsu and click **Next** 3. Name your destination and click **Create Destination** 4. Enable your destination and click **Save changes** ## Set up Mapping 5. Go to the **Mappings** tab and click **New Mapping** 6. Click on **Send** action 7. Select events to map. You can map all events or specific ones 8. Add test event. If your source is alive you can choose **Load Test Event from Source**, otherwise you can click **Load Sample Event** 9. Select mappings Fill **URL** field with your Jitsu instance [S2S endpoint URL](/docs/sending-data/http#ingest-endpoint) while replacing event type at the end of URL with the `type` variable from the dropdown list: and **Write Key** with your Jitsu site Server-to-server Write Key 9. Send test event and check if it was received in Jitsu Press **Send test event to destination** button Make sure that you see a successful response: Check Jitsu - Live Events section to see if the test event was received Save Mapping and make sure that it's enabled --- Source: https://jitsu.com/docs/sending-data/consent-management # Consent Management :::info Available since Jitsu v2.8.0 and npm packages v1.9.7 ::: Jitsu JavaScript library supports multiple [Privacy Settings](/docs/sending-data/js-reference#privacy-settings) to comply with GDPR and other privacy regulations. There are many Consent Management Platforms (CMPs) available on the market, most of which provide a JavaScript API to access user consent settings after the user interacts with the consent banner. A common approach to integrate user consent with the Jitsu library is to configure Jitsu according to these settings by implementing a JavaScript callback provided by the CMP API. For example: ```javascript // Change Jitsu configuration according to user consent settings. SomeCMP.OnConsentChanged(function(e) { const consent = e.detail.includes('C0002'); jitsu.configure({ privacy: { dontSend: !consent, ipPolicy: consent ? "keep" :"stripLastOctet", disableUserIds: !consent, consentCategories: e.detail.reduce((acc, category) => { acc[category] = true; return acc; }, {}) } }); }) ``` ## Pre-consent user tracking There are two approaches to tracking users before they provide consent: 1. Do not track any users data at all until they provide consent. 2. Track users with limited data collection before they provide consent. :::warning Both approaches must be implemented in conjunction with selected CMP SDKs to ensure proper user consent collection. Without this integration, the Jitsu library will not be able to detect the user’s consent status and may remain in the pre-consent state. ::: ### Do not track In such case Jitsu Library must be initialized in `dont-send` mode. For example: ```html ``` In this mode Jitsu doesn't store anything in cookies and doesn't send events to Jitsu servers. When user provides consent, Jitsu library settings must be adjusted with proper privacy settings. ```javascript // Change Jitsu configuration according to user consent settings. SomeCMP.OnConsentChanged(function(e) { const consent = e.detail.includes('C0002'); jitsu.configure({ privacy: { dontSend: !consent, consentCategories: e.detail.reduce((acc, category) => { acc[category] = true; return acc; }, {}) } }); }) ``` ### Track with limited data collection It is possible to track users with limited data collection before they provide consent. In this case, Jitsu library must be initialized with [privacy settings](/docs/sending-data/js-reference#privacy-settings) that disable collection of Personally Identifiable Information. :::info In this mode `identify` and `group` calls will have no effect. ::: For example: ```html ``` When user provides consent, Jitsu library settings must be adjusted with proper privacy settings. ```javascript // Change Jitsu configuration according to user consent settings. SomeCMP.OnConsentChanged(function(e) { const consent = e.detail.includes('C0002'); jitsu.configure({ privacy: { disableUserIds: !consent, ipPolicy: consent ? "keep" :"stripLastOctet", consentCategories: e.detail.reduce((acc, category) => { acc[category] = true; return acc; }, {}) } }); }) ``` ## OneTrust example Here is an example of how to configure Jitsu library with OneTrust Consent Management for Web. ### Setting up Jitsu Library with limited tracking ```html ``` ### Setting up OneTrust script with adjusted callback ```html ``` --- Source: https://jitsu.com/docs/sending-data/server-to-server # Server to Server Jitsu supports event ingestion in server-to-server (S2S) mode. The ingestion mode is determined by the type of **Write Key** used to configure the Jitsu client. Unlike browser events, s2s events are processed differently: * `context.ip` and `context.userAgent` are not extracted from the request headers, but should be passed explicitly in the event payload * If `context.ip` is not present, the ip field will be empty to avoid confusion; same applies for `context.userAgent` ## Strict mode :::tip Strict mode for S2S stream ensures that no third-party actor can inject events into your stream using publicly accessible information. ::: Jitsu Stream can be configured with **Strict Mode** enabled. In Strict Mode, Jitsu requires a valid writeKey to ingest events into the current stream. Without Strict Mode, if a correct writeKey is not provided, Jitsu may attempt to identify the stream based on the domain or, if there is only one stream in the workspace, it will automatically select that stream. --- Source: https://jitsu.com/docs/functions # Functions Functions are designed to process data in Jitsu before it is sent to the [destination](/docs/core-concepts/#destinations). These functions, written in JavaScript, provide various data handling options: - [Filter](#filter) - Exclude data that fails to meet your specified criteria. - [Transform](#transform) - Modify data to fit the schema of your destination. - [Enrich](#enrich) - Add extra information to the event. In addition, Functions are supported by a runtime environment that includes several built-in services: - [Persistent Storage](/docs/functions/runtime#persistent-storage) - Maintain data between different function calls. - [Logging](/docs/functions/runtime#logging) - Log messages and access them later in [Live Events](/docs/features/live-events). - [Fetch API](/docs/functions/runtime#fetch-api) - Perform HTTP requests to third-party services. - [Warehouse API](/docs/functions/runtime#warehouse-api) - Query your data warehouses. ## Quick intro ```typescript export default async function transform(event, context) { // } ``` * `event` is a first arguments of a function. It contains an event object that is being processed * `ctx` is a second argument, the function context. I contain various services that can be used in the function * `ctx.log` - [logging service](/docs/functions/runtime#logging) * `ctx.store` - [persistent storage](/docs/functions/runtime#persistent-storage) * `ctx.fetch` - [a standard fetch API](/docs/functions/runtime#fetch-api) to make HTTP requests * `ctx.getWarehouse` - [warehouse API](/docs/functions/runtime#warehouse-api) to query your data warehouses * `ctx.geo` - geo information about the event based on IP * `ctx.ua` - parsed user agent * `ctx.headers` - incoming HTTP headers if event came through HTTP API * `ctx.destination` - destination object * `ctx.source` - where event came from. Includes `id` — source id, `type` — ingest type (`browser` or `s2s`) * The full specification of ctx object can be found in [`functions.d.ts`](https://github.com/jitsucom/jitsu/blob/newjitsu/types/protocols/functions.d.ts) :::tip See a full spec of what you can use functions on [Functions Runtime Page](/docs/functions/runtime) ::: Here's a simple example that enriches event with geo information, and parsed user agent. ```javascript export default async function transform(event, context) { if (!event.properties) { //initialize event properties object, just to be safe event.properties = {} } //copy geo and parsed user agent to event properties event.properties.geo = context.geo event.properties.ua = context.ua //always return a modified event return event; } ``` Function return value controls what happens with event after function execution: * If function returns nothing, the original event will be used. * If function returns an object, it will be used as a new event. * If function returns magic string `"drop"`, event will be ignored and won't be sent to the destination. See a full spec of [what function can return](/docs/functions/advanced#return-types). ## Debugging Jitsu comes with a functions debugger/editor that allows to run function on a sample data ## Function examples See a few examples of how to use functions to filter, transform, and enrich data. ### Filter ```javascript export default async function transform(event, { log, props, store }) { //drop events coming from integration tests if (event.userId === "integration-tests") { return "drop"; } } ``` :::info To ignore event, return magic string `"drop"` from the function. If function returns an object, it will be used as a new event. cIf function returns nothing, the original event (or it's modified copy) will be used. ::: ### Transform ```javascript export default async function transform(event, { log, props, store }) { //normalize event type if (event.type === "page_view" || event.type === "pageview" || event.type === "pageView") { event.type = "page"; } return event; } ``` ### Change destination table To change destination table for specific event, you need to add a special property named `JITSU_TABLE_NAME` to the resulting events with a new table name. ```javascript export default async function transform(event, { log, props, store }) { //change table name to "new_table" event.JITSU_TABLE_NAME = "new_table"; return event; } ``` ### Enrich GeoIP enrichment example: ```javascript // Enriches event with Geo IP using https://ip-api.com/ service // For non-commercial use only export default async function(event, { log, fetch }) { try { const url = `https://ip-api.com/json/${event.context.ip}` const result = await fetch(url) if (result.ok) { let json; json = await result.json(); if (json.status === "success") { // remove status fields delete json.query delete json.status // add geo information to context event.context.geo = json return event } else { log.error(`GeoIP status: ${json.status}: ${json.message}`) } } else { log.error(`Failed to fetch GeoIP data ${url}: ${result.status} ${result.statusText}`) } } catch (e) { log.error(`Failed to fetch response from ${url}: ${e?.message}`); } } ``` ## Functions Runtime :::tip See a full spec of what you can use functions on [Functions Runtime Page](/docs/functions/runtime) ::: --- Source: https://jitsu.com/docs/functions/runtime # Functions Runtime Functions execute within a sandboxed Node.js environment, supporting all language features, including async/await syntax. See details below: ## Fetch API The Fetch function is provided as a property of the `context` object. It implements the standard [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) interface. ```typescript export default async function(event, { log, fetch, store }) { const response = await fetch("https://example.com"); if (response.ok) { const json = await response.json(); } } ``` ## Persistent Storage Persistent data storage between function calls is achievable using a key-value storage system, which is accessible through the `store` property of the `context` object. The `store` object is defined by the following interface (in TypeScript notation): ```typescript interface Store { get(key: string): Promise; del(key: string): Promise; // set value for 'key' with default or provided TTL (in number of seconds or string like '7d', '12h', '1m') set(key: string, value: any, opts?: number | string): Promise; // returns value for 'key' or sets it to 'value' if key doesn't exist. TTL can be set with default or provided value (in number of seconds or string like '7d', '12h', '1m') getOrSet(key: string, value: any, opts?: number | string): Promise; // returns remaining TTL for 'key' in seconds or -2 if key doesn't exist ttl(key: string): Promise; } ``` ### Data isolation Data in key-value storage is isolated on Workspace level. If you want to keep similar set of keys for multiple sites or destinations isolated form each other, you need to use that entity id (source, destination, connection) as a prefix for the key. ```javascript export default async function(event, context) { await store.set(`${context.destination.id}:myKey`, "myValue") } ``` ### Time to live (TTL) Default TTL: **31 days** It means that if you don't update data for a particular key for 31 days, that key will be deleted. You can change TTL for a particular key by passing third argument to `set` method as number in seconds or as short [human-readable string](https://www.npmjs.com/package/parse-duration#available-unit-types-are), e.g.: `14`, `"14d"` ```javascript // set TTL to 7 days for 'myKey' await store.set("myKey", "myValue", "7d") ``` Disable TTL: to disable TTL and keep value forever pass `"inf"` or `-1` as third argument to `set` method: ```javascript // disable TTL for 'myKey' to keep it forever await store.set("myKey", "myValue", "inf") ``` Data for a particular key can be updated to refresh TTL. See [the example](/docs/functions/runtime#example). **Check TTL** You can check remaining TTL for specific key: ```javascript // returns remaining TTL for 'myKey' in seconds or -2 if key doesn't exist const remainingSec = await store.ttl("myKey") if (remainingSec < 60 * 60 * 24) { // less than 1 day left. refresh TTL await store.set("myKey", "myValue", "7d") } ``` ### Example "User sign up webhook" example: ```javascript const webHookURL = "https://hooks.slack.com/services/.../.../..." export default async function(event, { log, fetch, store }) { if (event.type === "identify") { // check if user already signed up if (await store.get(`signup/${event.traits.email}`)) { log.info(`User ${event.traits.email} already signed up`); // store to refresh ttl to the next 30 days await store.set(`signup/${event.traits.email}`, true, "30d"); } else { // store signup flag for user with 30 days TTL await store.set(`signup/${event.traits.email}`, true, "30d"); await fetch(webHookURL, { method: "POST", body: JSON.stringify({ text: `Horray! We have new user ${event.traits.email}` }) }); } } } ``` ## Warehouse API Warehouse API is provided as a the `getWarehouse` property of the `context` object. It allows you to query your data warehouses. Pass destination ID or connection ID to `getWarehouse` method to get a Warehouse instance. :::note Only ClickHouse destination currently supports the Warehouse API.
HTTPS port ( 8443 by default ) should be open in your ClickHouse server. ::: **Warehouse type:** ```typescript interface Warehouse { query: (sql: string, params?: Record) => Promise; } ``` where: * `query` - method to execute SQL query. It returns an array of rows. * `sql` - SQL query with optional placeholders for named parameters. Placeholders should be in the form of `@paramName` or `:paramName`. * `params` - object with named parameters. **Example:** ```typescript export default async function(event, { log, getWarehouse }) { const warehouse = getWarehouse("destinationId"); const row = await warehouse.query("SELECT value FROM table WHERE id = @id LIMIT 1", { id: event.properties.id }); event.properties.value = row[0]?.value return event } ``` ## Logging Logging may be helpful for debugging functions and also for monitoring. To log something, use `log` property of the `context` object. `log` object has the following interface (in TypeScript notation): ```typescript interface Log { info(message: string, ...args: any[]); warn(message: string, ...args: any[]); debug(message: string, ...args: any[]); error(message: string, ...args: any[]); } ``` **Examples** with logging: [Enrich](/docs/functions#enrich), [User sign up webhook](/docs/functions#example) In Function editor to see logs from the last Run press **Show logs** button. Logs of Functions that are already attached to Connections can be seen in the [Live Events](/docs/features/live-events) section of the main menu. ## Environment variables You can use environment variables in your functions. They are accessible via `process.env` object. ```javascript export default async function(event, { log }) { const apiKey = process.env.API_KEY; log.info("API key: " +apiKey) // function logic } ``` Environment variable values are set at the Connection level, allowing a single function to operate with different configurations seamlessly. ## Crypto Jitsu offers shortened version of node `crypto` module which supports following methods: [hash](https://nodejs.org/api/crypto.html#cryptohashalgorithm-data-outputencoding), [randomUUID](https://nodejs.org/api/crypto.html#cryptorandomuuidoptions), [randomBytes](https://nodejs.org/api/crypto.html#cryptorandombytessize-callback), [randomInt](https://nodejs.org/api/crypto.html#cryptorandomintmin-max-callback) ```javascript import { hash } from 'crypto'; export default async function(event, { log, geo }) { const h = hash("sha256", JSON.stringify(event)) log.info("Event hash: " + h) } ``` ## Using external libraries If you need to use any external libraries, besides core libraries, you must create a Jitsu project with [SDK](/docs/functions/sdk) and bundle your function with all dependencies. ## GEO Location **Jitsu Cloud** provides IP geolocation information for each event. It's available in `geo` property of the `context`. ```javascript export default async function(event, { log, geo }) { log.info(JSON.stringify(geo)) } ``` `geo` object structure ```json { "country": { "code": "US", "isEU": false }, "city": { "name": "New York" }, "region": { "code": "NY" }, "location": { "latitude": 40.6808, "longitude": -73.9701 }, "postalCode": { "code": "11238" } } ``` ## User Agent Jitsu parses User-Agent header and provides its data. It's available in `ua` property of the `context`.: ```javascript export default async function(event, { log, ua }) { console.log(JSON.stringify(ua, null, 2)) } ``` `ua` object structure ```json { "browser": { "name": "Firefox", "version": "111.0", "major": "111" }, "engine": { "name": "Gecko", "version": "109.0" }, "os": { "name": "Mac OS", "version": "10.15" }, "device": { "vendor": "Apple", "model": "Macintosh", "type": "desktop" }, "cpu": {}, "bot": false } ``` --- Source: https://jitsu.com/docs/functions/pipeline # Functions Pipeline ## Overview Jitsu provides a powerful event processing pipeline that allows you to filter, transform, and enrich events before sending them to destinations. ## Pipeline Architecture The event processing pipeline consists of **3 main steps** executed sequentially: {/* Background */} {/* Title */} Jitsu Event Processing Pipeline {/* Incoming Event */} Event {/* Arrow to Step 1 */} {/* Step 1: Builtin Transformation */} Step 1: Builtin Transformation Identity Stitching {/* Arrow to Step 2 */} {/* Step 2: User Defined Functions */} Step 2: User Defined Functions Sequential Execution {/* UDF Pipeline boxes */} UDF 1 UDF 2 UDF N Filter, Transform, Enrich {/* Arrow to Step 3 */} {/* Step 3: Builtin Destination */} Step 3: Builtin Destination BiqQuery, ClickHouse, Mixpanel, Amplitude, etc. {/* Final Arrow to Destination */} {/* Destination Icon */} Destination (DB/API) {/* Arrow markers */} ### Step 1: Builtin Transformations These are system-level functions like Identity Stitching that run before user functions. Currently includes: - **Identity Stitching**: Recognizes and merges user identities across sessions ### Step 2: User Defined Function Pipeline Your custom JavaScript functions that can: - Filter events (exclude unwanted data) - Transform events (modify structure/fields) - Enrich events (add external data) Multiple UDFs are executed **sequentially** in the order defined. ### Step 3: Builtin Destination Function The final function that sends the processed event to the configured destination (e.g. Mixpanel, Amplitude, Webhook, etc.) All data warehouse destinations uses the same bridge function that passes event payload to the bulker component. Since they are functions as any other user defined functions, they report logs and errors in the similar way. You can check logs and errors of functions attached to certain connection in the Data - Live Events section of the Workspace. ## Unrecoverable Errors Unrecoverable Errors are function or destination errors that cannot be resolved by retrying an operation, as well as retriable errors that have exceeded the maximum number of retry attempts. Summing up, Unrecoverable Errors include: - Any standard error thrown in a builtin function (e.g. destination functions) - `NoRetryError` thrown in a user defined function - `RetryError` thrown in any function that has exhausted all retry attempts All events that encounter Unrecoverable Errors are sent to the **Unrecoverable Events storage**, where they can be viewed in the **Live Events** section of the UI. ## Error Handling in Builtin Functions Builtin transformation and destination functions follow strict error handling: ### Any Standard Error - Error is **logged** to destination log - Event is sent to **Unrecoverable Events storage** (viewable in UI Live Events section) ### RetryError ```javascript throw new RetryError("Temporary failure"); ``` - Error is **logged** to destination log - Event is sent to **retry queue**. After retry, processing resumes from the failed step with payload changes retained. ## Error Handling in User Defined Functions User-defined functions have **more flexible error handling** to support partial enrichment and graceful degradation: ### Any Standard Error ```javascript throw new Error("Enrichment API failed"); ``` - Error is **logged** to destination log - Event **continues to next function** in pipeline - Processing is **NOT stopped** - Use case: Allows non-enriched events to reach destination ### RetryError (default) ```javascript throw new RetryError("Temporary enrichment failure"); ``` - Error is **logged** to destination log - Event goes to **retry queue** (will be retried later) - Event **continues to next function** in pipeline - Use case: Enrichment that can be retried later, allows partial data now, full data after retry ### RetryError with drop option ```javascript throw new RetryError("Critical enrichment failed", { drop: true }); ``` - Error is **logged** to destination log - Event goes to **retry queue** (will be retried later) - Pipeline **STOPS** immediately - Event does **NOT** reach destination - Use case: When partial/unenriched data should not be stored ### NoRetryError ```javascript throw new NoRetryError("Invalid data format"); ``` - Error is **logged** to destination log - Event goes to **Unrecoverable Events storage** - Pipeline **STOPS** immediately - Event does **NOT** reach destination - No retries - Use case: Permanent errors that won't be fixed by retrying ### Error Handling Summary Table | Error Type | Location | Retry Queue | Unrecoverable Events | Continue Pipeline | Use Case | |-------------------|------------------|-------------|-------------|-------------------|------------------------------------------| | Standard Error | Builtin Function | ❌ | ✅ | ❌ | Permanent builtin failure | | RetryError | Builtin Function | ✅ | ❌ | ❌ (after retry) | Temporary builtin failure | | Standard Error | User Function | ❌ | ❌ | ✅ | Non-critical UDF failure | | RetryError | User Function | ✅ | ❌ | ✅ | Retriable enrichment, allow partial data | | RetryError (drop) | User Function | ✅ | ❌ | ❌ | Retriable enrichment, no partial data | | NoRetryError | User Function | ❌ | ✅ | ❌ | Permanent data issue | ## Retry Attempts and Delay ### Default Retry Policy When a function throws a `RetryError`, Jitsu implements automatic retry logic with the following defaults: - **Retry attempts**: 3 attempts maximum - **Delays between attempts**: 10 minutes, 100 minutes, and 1000 minutes (16.7 hours) - **Maximum delay**: 1440 minutes (24 hours) - any delay exceeding this will be capped **Important**: When all retry attempts are exhausted, the event is sent to **Unrecoverable Events storage** where it can be viewed in the Live Events section of the UI. ### Custom Retry Policy Configuration You can override the default retry policy by adding a configuration block to your function code: ```javascript export const config = { retryPolicy: { attempts: 2, // Number of retry attempts (max 3) delays: [60, 1440] // Delays in minutes before each attempt (max 1440 per delay) } } export default async function(event, ctx) { // Your function logic here } ``` #### Configuration Constraints - **`attempts`**: Cannot exceed 3 (system limit) - **`delays`**: - Individual delays cannot exceed 1440 minutes (24 hours) - Array length must match the `attempts` count - Delays are specified in minutes ### How Retries Work 1. **First Failure**: When a function throws `RetryError`, the event is sent to the retry queue 2. **Scheduled Retry**: The event is scheduled for retry after the delay specified in `delays[0]` 3. **Subsequent Failures**: If the function fails again, the event is retried after `delays[1]`, then `delays[2]`, etc. 4. **Exhausted Attempts**: After all retry attempts are exhausted, the event is moved to **Unrecoverable Events storage** 5. **Success**: If any retry succeeds, the event continues through the pipeline normally ### Retry Behavior with Pipeline Execution - **Retried events skip already processed steps**: When an event is retried, it resumes from the failed pipeline step, not from the beginning - **Payload changes are retained**: Any modifications made in previous successful steps are preserved - **User Defined Function Pipeline** considered as a single step: If a function in the UDF pipeline fails and event is retried, the entire UDF pipeline is re-executed - **Pipeline continuation**: - With `RetryError` (no drop): Event continues to next function while also being queued for retry - With `RetryError({ drop: true })`: Event is queued for retry but does NOT continue to next function ### Retry Mechanism Flow Diagram {/* Background */} {/* Title */} Retry Mechanism Flow {/* Initial Event */} Event {/* Step 1 */} Step 1 {/* Step 1 Success */} {/* Step 2 UDF */} Step 2 {/* Step 2 Fails */} RetryError {/* Retry Queue */} Retry Queue Retry Count: 0 {/* Wait for delay[0] */} Wait delays[0] (10 min) {/* Retry 1 - Skip Step 1 */} Retry Attempt 1 Skip Step 1 ✓ {/* Retry 1 runs Step 2 */} Step 2 {/* Retry 1 Fails */} RetryError {/* Retry Queue 2 */} Retry Queue Retry Count: 1 {/* Dots indicating more retries */} {/* Retry 3 */} Retry Attempt 3 Skip Step 1 ✓ {/* Retry 3 runs Step 2 */} Step 2 {/* Success Branch (Right) */} Continue to Step 3 {/* Failure Branch (Bottom) */} RetryError {/* Unrecoverable Events (Bottom) */} Unrecov. Events {/* Legend */} Key Points • Retries skip completed steps • Payload changes retained • UDF pipeline = 1 step • Default: 3 attempts • Delays: 10, 100, 1000 min • Max delay: 1440 min (24h) • No retries left → Unrecov. Events {/* Arrow markers */} --- Source: https://jitsu.com/docs/functions/advanced # Advanced This page describes advanced details of Jitsu Functions such as: * [Return types](#return-types) * [Order of execution](#order-of-execution) * [Multiplying events](#multiplying-events) * [Errors handling and Retries](#errors-handling) * [Built-in functions](#built-in-functions) ## Return types Function can return one of the following types: * `undefined` (or missing `return` statement) – event will be passed to the next function in pipeline without changes. * `"drop"`, `null`, `[]`, `false` – event will be dropped and won't be passed to the next function in pipeline and won't be passed to the destination. * `object` – event will be replaced with the returned object and passed to the next function in pipeline. * `array` of objects – event will be replaced with the returned array and each object will be passed to the destination. This way multiple events can be generated from one event. See [Multiplying events](#multiplying-events) section for details. Function can also throw errors. See [Errors handling](#errors-handling) section for details. ## Order of execution It is possible to attach multiple functions to the same connection. In such case functions will be executed in the order chosen in Connection Editor and each function will receive the result of the previous one as an input unless function returns `"drop"` or throws a corresponding error. ## Multiplying events You can produce multiple events from one event by returning array of objects from the function. :::caution One connection can have only one function that multiplies events (returns array with more than one object) and that function must be
**the last in the pipeline**. If function that is not the last one in the pipeline returns array with more than one object – processing will be aborted with error and no events will be passed to the destination. ::: Example: create separate event for each product in purchase: ```javascript export default async function(event, { log, fetch }) { if (event.event == "purchase" && event.properties?.products?.length > 0) { let results = [] for (const product of event.properties.products) { results.push({ event_type: "purchase", product_id: product.id, price: product.price }) } return results } else { //skip events without any purchase return "drop" } } ``` ## SQL column type override To set specific SQL column type for the field, you can use `__sql_type_` key in the returned object: ```javascript export default async function(event, ctx) { return { ...event, event_date: event.timestamp, __sql_type_event_date: "date", event_time: event.timestamp, __sql_type_event_time: "time", } } ``` It is possible to store any nested object as JSON in the Data Warehouse where JSON format is supported: ```javascript export default async function(event, ctx) { return { ...event, user_traits_json: { ...event.context.traits, ...event.traits }, __sql_type_user_traits_json: "json" } } ``` Sql types with extra parameters: Some Data Warehouses support extra parameters for column types during table creation. For such cases, Transform uses the following syntax to provide data type and column type separately: ```javascript export default async function(event, ctx) { return { ...event, title: event.context.page.title, __sql_type_title: ["varchar(256)", "varchar(256) encode zstd"] } } ``` ## Errors handling If function doesn't throw an error, Jitsu considers it as a **successful**. If function throws an error, Jitsu considers it as **failed** one. The error will be logged and can be inspected in **Data - Live Events** section of the Workspace. ### Errors retry By default, failed functions don't interrupt functions pipeline and don't get retried. E.g. if functions `A` and `B` are attached to the same connection and `A` fails, `B` will still be executed and event will reach the destination That behavior can be changed by throwing an error of special type `RetryError`: ```javascript throw new RetryError(errorMessage, options); ``` where as `options` can be passed an object with following properties: * `drop` – boolean flag that indicates whether to drop event or not (equivalent of `drop` response). If `true`, event will be dropped and won't be processed by any other function. If `false`, event will be passed to the next function in pipeline (event modifications performed by failed function before throwing an error will be lost). By default: `false` e.g: ```javascript throw new RetryError("Failed to enrich event", { drop: true }); ``` :::caution When multiple user defined functions are attached to the same connection and one of them throws the `RetryError` – all user defined functions will be retried. If just one of the function throws `RetryError` with `drop` parameter set to `true` – event will be dropped and won't reach the destination. ::: ### Retry attempts and delay By default, when function throws `RetryError`, Jitsu will retry it no more than 3 times with the following delays: 10, 100 and 1000 minutes. You can change default retry attempts and delays by setting the following config block in the function code: ```javascript export const config = { retryPolicy: { attempts: 2, //retry attempts. cannot be greater than 3 delays: [60, 1440] // delay in minutes before each consequent attempt. cannot be greater than 1440 } } ``` ### Example Let's say we have the following logic parts in our pipeline: * `filter` - filters events by some condition. * `enrich` - enriches event with crucial data using fetch request to external service API. No side effect - can be repeated. But step is critical, events without enrichment has no business value. * `business logic` - increments some business metric using fetch request to external service API. Has side effect - cannot be repeated, because retrying will lead to incorrect metric value. We need to organize our functions in a such way to ensure that even after retries we will have only one successful run of `business logic` block. In general, function with side effect should be the last one in the connection: `filter`: ```javascript export default async function(event, { log, fetch }) { if (event.type !== "track") { // this destination accepts only track events all other types must be dropped return "drop"; } } ``` `enrich`: ```javascript export const config = { retryPolicy: { attempts: 2, delays: [60, 1440] // 1st delay - 1 hour, 2nd delay - 1 day } }; export default async function(event, { log, fetch }) { let error; try { const url = `https://ip-api.com/json/${event.context.ip}`; const result = await fetch(url); if (result.ok) { const geo = await result.json(); if (geo.status === "success") { // remove status fields delete geo.query; delete geo.status; // add geo information to context event.context.geo = geo; } else { error = `GeoIP status: ${geo.status}: ${geo.message}`; } } else { error = `Failed to fetch GeoIP data ${url}: ${result.status} ${result.statusText}`; } } catch (e) { error = `Failed to fetch response from ${url}: ${e?.message}`; } if (error) { // enrich is critical step, if it fails - we need drop processing and schedule retry // `drop` set to true throw new RetryError(error, { drop: true }); } return event; } ``` `business logic`: ```javascript export const config = { retryPolicy: { attempts: 2, delays: [60, 1440] // 1st delay - 1 hour, 2nd delay - 1 day } }; export default async function(event, { log, fetch }) { try { const metricUrl = "https://rareanimals.example.com/increment" const bisResult = await fetch(url, { method: "POST", body: { country: event.context.geo.country, animal: event.properties.animal } }); if (!bisResult.ok) { error = `Failed to increment metric ${metricUrl}: ${bisResult.status} ${bisResult.statusText}`; } } catch (e) { error = `Failed to increment metric: ${e?.message}`; } if (error) { // business metric is critical step, if it fails - we need schedule retry // but it is ok to pass event to the destination since it is already in its final state // and jitsu will handle deduplication after retry. So we don't set `drop` flag throw new RetryError(error); } // pass enriched event to the destination return event; } ``` ## Built-in functions Jitsu has certain features implemented as built-in functions: * All data warehouse destinations uses the same bridge function that passes event payload to the [bulker](https://github.com/jitsucom/bulker) component. * API based destinations (e.g. Mixpanel, Amplitude, Webhook, etc) are implemented as built-in functions. * [Identity Stitching](/docs/features/identity-stitching) feature is also implemented as built-in function. Since they are functions as any other user defined functions, they report logs and errors in the similar way. You can check logs and errors of functions attached to certain connection in the **Data - Live Events** section of the Workspace. --- Source: https://jitsu.com/docs/functions/sdk # Functions CLI SDK Functions CLI is a tool that allows to develop and deploy [Functions](/docs/functions) to Jitsu. ## Installing Jitsu CLI To download and install Jitsu CLI, run the following command: ```bash npm i -g jitsu-cli ``` ```bash yarn global add jitsu-cli ``` ```bash pnpm i -g jitsu-cli ``` ## Creating a new project Run the following command to initialize the project ```bash jitsu-cli init ``` arguments: * `--name` – the name of the project. (Optional). By default, interactive prompt is shown to enter the name. * `--displayname` – human-readable function name that will be used in Jitsu. (Optional). By default, interactive prompt is shown to enter the name. * `--parent` – the parent directory of project. (Optional). By default, interactive prompt is shown to enter the parent directory. ### Project structure ```shell myfunc ├── src │ ├── __tests__ │ │ └── functions │ │ └── myfunc.test.ts │ ├── functions │ │ └── myfunc.ts │ └── profiles │ └── profile.ts ├── tsconfig.json └── package.json ``` ## Development Use following commands to develop and test your functions locally: **Install dependencies** ```bash cd myfunc # go to the project directory npm install # install dependencies with 'npm' or any other compatible package manager ``` **Build functions** ```bash jitsu-cli build ``` **Run tests** ```bash jitsu-cli test ``` Test provided by project template is a simple test that checks that function doesn't crash and returns some event. You may need to extend test to check that function returns correct result. [//]: # (### Check function on custom event) [//]: # () [//]: # (You can copy any incoming event from `Data` – `Live Events` section in Jitsu UI and use it to test your function locally.) [//]: # () [//]: # (To check function on provided event, config or persistent storage state run:) [//]: # (```bash) [//]: # (jitsu-cli run --event '{"type": "pageview", ...}' --props '{}' --store '{"key1": "value1"}') [//]: # (```) [//]: # (or:) [//]: # (```bash) [//]: # (jitsu-cli run --event ./event.json --props ./config.json --store ./state.json) [//]: # (```) [//]: # (arguments:) [//]: # (* `--name` – name of function to check (optional). Required if multiple functions are defined in project) [//]: # (* `--type` – entity type to run (optional). Default: `function`) [//]: # (* `--event` – path to file with event json or event json as a string) [//]: # (* `--props` – path to file with config json or config json as a string (optional)) [//]: # (* `--store` – path to file with state json or state json as a string (optional)) [//]: # () [//]: # (Command outputs function result.) ## Adding Functions to Jitsu **Login to Jitsu (Run once)** ```bash jitsu-cli login ``` arguments: * `--host` – Jitsu host (optional). Default: `https://use.jitsu.com` * `--apikey` – Jitsu user's Api Key (optional). Disables interactive login. `login` command remembers credentials in `~/.jitsu/jitsu-cli.json` file. **Deploy functions to Jitsu project** ```bash jitsu-cli deploy ``` arguments: * `--workspace` – Id of workspace where to deploy function (Optional). By default, interactive prompt is shown to select workspace * `--type` – entity type to deploy (optional). Default: `function` Deploy command creates new functions or updates existing functions in Jitsu project. --- Source: https://jitsu.com/docs/destinations/catalog # All Destinations Jitsu sends your event data to data warehouses, block storage, and downstream analytics and marketing tools. Browse every available destination below. --- Source: https://jitsu.com/docs/destinations/warehouse/bigquery
}>BigQuery
BigQuery is a cloud-based SQL data warehouse service developed by Google. ## Features | Feature | Supported | |-----------------------------------------------|-----------| | [Batch Mode](#batch-mode) | ✅ | | [Stream Mode](#stream-mode) | ❌ | | [Deduplication](#deduplication) | ✅ | | [Queries Optimization](#queries-optimization) | ✅ | ## Configuration ## Advanced: Implementation Details This section describes how Jitsu implements various modes and features for BigQuery. ### Batch Mode - Jitsu collects events batches in tmp file on file system. - Using Loader API Jitsu loads data from tmp file into BigQuery tmp_table. - Using Copier API Jitsu copy data from tmp_table to target_table ### Stream Mode :::danger[Not supported] It's possible to implement stream mode for BigQuery, but data Deduplication cannot be supported in this mode. So it is currently disabled in Jitsu. ::: ### Deduplication Data deduplication in BigQuery is based on [MERGE statement](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement). Merge condition is based on primary key column configured on connection level.
Details - Write to tmp file - Deduplicate rows in tmp file - Use Loader API to load to tmp_table from tmp file - `MERGE into target_table T using tmp_table TMP on T.pk_field=TMP.pk_field when matched then UPDATE ... when not matched them INSERT ...`
### Queries Optimization **Timestamp** connection setting is used to optimize SELECT queries. Jitsu creates [time-unit column-partitioned](https://cloud.google.com/bigquery/docs/partitioned-tables#date_timestamp_partitioned_tables) table with specified timestamp column and daily partitioning. --- Source: https://jitsu.com/docs/destinations/warehouse/clickhouse
}>ClickHouse
ClickHouse is an open-source column-oriented database management system specialized for online analytical processing of queries (OLAP). There's two ways to use ClickHouse: * [Clickhouse Cloud](https://clickhouse.com/cloud) * [Self-hosted Clickhouse](https://clickhouse.com/docs/en/install) ## Features | Feature | Supported | |-----------------------------------------------|--------------------------------| | [Batch Mode](#batch-mode) | ✅ | | [Stream Mode](#stream-mode) | ❌ [*](#stream-mode) | | [Deduplication](#deduplication) | ✅ | | [Queries Optimization](#queries-optimization) | ✅ | | [Cluster Support](#cluster-support) | ✅ | | [Distributed Tables](#distributed-tables) | ✅ | ## Configuration ## Advanced: Implementation Details This section describes how Jitsu implements various modes and features for ClickHouse ### Batch Mode In Batch Mode Jitsu use temporary table with `Memory` engine to store batch events before they are inserted into the destination table.
Details ```sql -- Jitsu collects events batches in tmp file on file system. INSERT INTO tmp_table (...) VALUES (...); -- bulk load data from tmp file into tmp_table using prepared statement in transaction INSERT INTO target_table(...) SELECT ... FROM tmp_table ```
### Stream Mode :::danger[Do not use in production] Stream mode in ClickHouse is [limited by MergeTree engine capabilities](https://clickhouse.com/docs/knowledgebase/exception-too-many-parts). ClickHouse is not designed to handle a large number of individual inserts. Stream Mode is not available by default and can be enabled only in advanced mode. Use it only for testing purposes. ::: ### Deduplication Jitsu relies on underlying table engine. By default `ReplacingMergeTree` will be used. **Primary key** column configured on connection level will be used both as `PRIMARY KEY` and `ORDER BY`. ReplacingMergeTree engine performs deduplication by primary key in background during some time after insertion So it's still possible to get rows with duplicated primary key columns using ordinary SELECT. To make sure that no duplicates are present in query results use `FINAL` modifier, e.g: ```sql SELECT * FROM target_table FINAL ``` :::tip `ReplacingMergeTree` is not only way to deduplicate data in ClickHouse. There [other approaches](https://kb.altinity.com/altinity-kb-schema-design/row-level-deduplication/) too. To implement them, create destination table before Jitsu starts inserting the data. In this case Jitsu will respect table engine and primary key columns you specified. ::: ### Queries Optimization **Timestamp** connection setting is used to optimize SELECT queries. Jitsu creates tables [partitioned](https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/custom-partitioning-key) by specified timestamp column and monthly partitioning, e.g. `PARTITION BY toYYYYMM(_timestamp)` ### Cluster Support Jitsu supports both ClickHouse clusters and single node instances. To use cluster, specify cluster name in `Cluster` connection setting. When working in cluster mode, Jitsu creates all tables in [Replicated](https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication) mode and all DDL operations are performed `ON CLUSTER`. ### Distributed Tables When working in cluster mode, Jitsu creates destination tables as [Distributed](https://clickhouse.com/docs/en/engines/table-engines/special/distributed/) tables. On each cluster node Jitsu creates local table with the `local_` prefix that contains actual data. --- Source: https://jitsu.com/docs/destinations/warehouse/mongodb
}>MongoDB
MongoDB is a cross-platform NoSQL document-oriented database. Jitsu supports both self-hosted Mongo and MongoDB Atlas. ## Features | Feature | Supported | |-----------------------------|-----------| | [Stream Mode](#stream-mode) | ✅ | ## Configuration ## Advanced: Implementation Details ### Stream Mode This implementation uses `insertOne` for each event. --- Source: https://jitsu.com/docs/destinations/warehouse/duckdb
}>MotherDuck (DuckDB)
DuckDB-powered cloud data warehouse scaling to terabytes with ease. ## Features | Feature | Supported | |-----------------------------------------------|-----------| | [Batch Mode](#batch-mode) | ✅ | | [Stream Mode](#stream-mode) | ✅ | | [Deduplication](#deduplication) | ✅ | | [Queries Optimization](#queries-optimization) | ✅ | ## Configuration ## Advanced: Implementation Details This section describes how Jitsu implements various modes and features for DuckDB. ### Batch Mode
Algorithm ```sql -- Jitsu collects events batches in tmp file on file system. ATTACH ':memory:' as jitsu_memdb INSERT into jitsu_memdb.tmp_table INSERT into target_table select from jitsu_memdb.tmp_table ```
### Stream Mode `INSERT INTO target_table (...) VALUES (..)` ### Deduplication For batch mode the following algorithm is used:
Algorithm ```sql -- Jitsu collects events batches in tmp file on file system. -- Deduplicate rows in tmp file ATTACH ':memory:' as jitsu_memdb INSERT into jitsu_memdb.tmp_table INSERT OR REPLACE into target_table select from jitsu_memdb.tmp_table ```
For stream mode: `INSERT OR REPLACE INTO target_table (...) VALUES (..)` ### Queries Optimization **Timestamp** connection setting is used to optimize SELECT queries. Regular index is created on specified timestamp column. --- Source: https://jitsu.com/docs/destinations/warehouse/mysql
}>MySQL
MySQL is a popular open source object-relational database system. ## Features | Feature | Supported | |-----------------------------------------------|-----------| | [Batch Mode](#batch-mode) | ✅ | | [Stream Mode](#stream-mode) | ✅ | | [Deduplication](#deduplication) | ✅ | | [Queries Optimization](#queries-optimization) | ✅ | ## Configuration ## Advanced: Implementation Details This section describes how Jitsu implements various modes and features for Mysql. ### Batch Mode
Algorithm ```sql -- Jitsu collects events batches in tmp file on file system. BEGIN -- start transaction INSERT into tmp_table -- load tmp file into tmp_table INSERT into target_table select from tmp_table COMMIT -- commit transaction ```
### Stream Mode `INSERT INTO target_table (...) VALUES (..)` ### Deduplication For batch mode the following algorithm is used:
Algorithm ```sql -- Jitsu collects events batches in tmp file on file system. -- Deduplicate rows in tmp file BEGIN -- start transaction INSERT into tmp_table ... ON DUPLICATE KEY UPDATE ... -- load tmp file into tmp_table INSERT into target_table select from tmp_table ... ON DUPLICATE KEY UPDATE ... COMMIT -- commit transaction ```
For stream mode: `INSERT INTO target_table ... ON DUPLICATE KEY UPDATE ...` ### Queries Optimization **Timestamp** connection setting is used to optimize SELECT queries. Regular index is created on specified timestamp column. --- Source: https://jitsu.com/docs/destinations/warehouse/postgres
}>PostgreSQL
Postgres is a powerful, open source object-relational database system. ## Features | Feature | Supported | |-----------------------------------------------|-----------| | [Batch Mode](#batch-mode) | ✅ | | [Stream Mode](#stream-mode) | ✅ | | [Deduplication](#deduplication) | ✅ | | [Queries Optimization](#queries-optimization) | ✅ | ## Configuration Jitsu supports database username/password based authentication for postgres. For Google Cloud SQL for PostgreSQL, Private Service Connect connection is also supported. ### General parameters | Parameter name | Description | |---------------------------|-----------------------------------------------------------------------------------------------------------------------------------| | **Authentication Method** | `password` - Username/Password based authentication, `google-psc` - Private Service Connect for Google-managed postgres instances | | **Database** | Postgres database name | | **Schema** | Postgres schema | Configuration settings depend on the selected authentication method. ### Username/Password based authentication | Parameter name | Description | |---------------------|---------------------------------------------------------------------------------| | **Host** | Postgres host | | **Port** | Postgres port | | **Username** | Postgres username | | **Password** | Postgres password | | **SSL Mode** | SSL mode for Postgres connection: `disable`,`require`,`verify-ca`,`verify-full` | | **SSL Server CA** | SSL Certificate Authority for `verify-ca`,`verify-full` SSL Modes | | **SSL Client Cert** | SSL Client Certificate for `verify-ca`,`verify-full` SSL Modes | | **SSL Client Key** | SSL Client Key for `verify-ca`,`verify-full` SSL Modes | ### Google Cloud Private Service Connect mode | Parameter name | Description | |------------------------------|---------------------------------------------------------------------------------------------| | **Instance Connection Name** | Google Cloud SQL instance connection name in the format `project-name:region:instance-name` | ## Advanced: Private Service Connect for Google Cloud SQL Private Service Connect (PSC) is a Google Cloud networking feature that allows to connect to Google manages services from multiple VPC networks that belong to different groups, teams, projects, or organizations. Private Service Connect allows you to grant access to your Google Cloud SQL instances to the Jitsu service account **without exposing the instances to the public internet**. This is particularly useful for organizations that need to maintain strict security and access controls. More on [Private Service Connect](https://cloud.google.com/vpc/docs/private-service-connect) More on [Private Service Connect for Cloud SQL](https://cloud.google.com/sql/docs/postgres/about-private-service-connect) For the full step-by-step setup guide — configuring Google Cloud SQL, source connectors, and destinations (password or IAM authentication) — see [Google Private Service Connect](https://jitsu.notion.site/Google-Private-Service-Connect-208737892e4780ac871be300d9e68d3a). ## Advanced: Implementation Details This section describes how Jitsu implements various modes and features for Postgres. ### Batch Mode
Algorithm ```sql -- Jitsu collects events batches in tmp file on file system. BEGIN -- start transaction COPY from STDIN to tmp_table -- load tmp file into tmp_table INSERT into target_table select from tmp_table COMMIT -- commit transaction ```
### Stream Mode `INSERT INTO target_table (...) VALUES (..)` ### Deduplication For batch mode the following algorithm is used:
Algorithm ```sql -- Jitsu collects events batches in tmp file on file system. -- Deduplicate rows in tmp file BEGIN -- start transaction COPY from STDIN to tmp_table -- load tmp file into tmp_table INSERT into target_table select from tmp_table ON CONFLICT UPDATE ... COMMIT -- commit transaction ```
For stream mode: `INSERT INTO target_table (...) VALUES (..) ON CONFLICT UPDATE ...` ### Queries Optimization **Timestamp** connection setting is used to optimize SELECT queries. Regular index is created on specified timestamp column. --- Source: https://jitsu.com/docs/destinations/warehouse/redshift
}>Redshift
Amazon Redshift is a cloud data warehouse that is optimized for the analytical workloads of business intelligence (BI) and data warehousing (DWH). Jitsu supports both Serverless and Classic Redshift ## Features | Feature | Supported | |-----------------------------------------------|------------------------------------| | [Batch Mode](#batch-mode) | ✅ | | [Stream Mode](#stream-mode) | ⚠️ [slow](#stream-mode) | | [Deduplication](#deduplication) | ✅ | | [Queries Optimization](#queries-optimization) | ✅ | ## Configuration Jitsu supports both database username/password based authentication and IAM Role based authentication for Redshift data warehouse. ### General parameters | Parameter name | Description | |---------------------------|--------------------------------------------------------------------------------------------| | **Authentication Method** | `password` - Username/Password based authentication, `iam` - IAM Role based authentication | | **Region** | AWS Region of Redshift Cluster and S3 bucket | | **Database** | Redshift database name | | **Schema** | Redshift schema | | **S3 Bucket Name** | S3 Bucket Name | Configuration settings depend on the selected authentication method. ### Username/Password based authentication | Parameter name | Description | |--------------------------|---------------------------------------------| | **Host** | Redshift public endpoint | | **Username** | Redshift username | | **Password** | Redshift password | | **S3 Access Key Id** | S3 Access Key Id | | **S3 Secret Access Key** | S3 Secret Access Key | ### IAM Role based authentication | Parameter name | Description | |-------------------------|---------------------------------------------------------| | **Redshift Serverless** | `true` if connecting to Redshift Serverless instance | | **Cluster Identifier** | Redshift cluster identifier (**Redshift Cluster** only) | | **Workgroup name** | Redshift workgroup name (**Redshift Serverless** only) | | **Role ARN** | IAM role ARN | | **Username** | Redshift username (**Redshift Cluster** only) | To setup IAM Role based authentication for Redshift, follow the [Advanced: IAM Role for Jitsu](#advanced-iam-role-for-jitsu) section. ## Advanced: IAM Role for Jitsu To allow Jitsu to connect to Redshift using IAM Role, the following steps should be performed in AWS Console: * Create a new IAM Policy * Create a new IAM Role * Attach the new IAM role to the Redshift cluster * Setting user permissions in Redshift ### Create a new IAM Policy * Sign in to your AWS Management Console and open the [IAM console](https://console.aws.amazon.com/iam/). * Go to Policies > Create policy. * Choose the JSON option. Then, paste the JSON below depending on whether you use Redshift Cluster or Redshift Serverless * Assign a unique and descriptive name to the policy, provide a clear description, and then select Create Policy. ```json { "Version": "2012-10-17", "Statement": [ { "Action": "redshift:GetClusterCredentials", "Effect": "Allow", "Resource": [ "arn:aws:redshift:${Region}:${AccountId}:dbuser:${ClusterIdentifier}/${Username}", "arn:aws:redshift:${Region}:${AccountId}:dbname:${ClusterIdentifier}/${Database}" ] }, { "Action": [ "redshift-data:BatchExecuteStatement", "redshift-data:ExecuteStatement" ], "Effect": "Allow", "Resource": [ "arn:aws:redshift:${Region}:${AccountId}:cluster:${ClusterIdentifier}" ] }, { "Action": [ "redshift-data:GetStatementResult", "redshift-data:CancelStatement", "redshift-data:DescribeStatement" ], "Effect": "Allow", "Resource": "*" }, { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:ListBucket", "s3:DeleteObject" ], "Resource": [ "arn:aws:s3:::${S3BucketName}", "arn:aws:s3:::${S3BucketName}/*" ] } ] } ``` :::tip Make Sure to replace `${...}` macros with appropriate values from the Configuration section,
`${AccountId}` with your AWS Account ID :::
```json { "Version": "2012-10-17", "Statement": [ { "Action": "redshift-serverless:GetCredentials", "Effect": "Allow", "Resource": [ "${WorkgroupARN}" ] }, { "Action": [ "redshift-data:BatchExecuteStatement", "redshift-data:ExecuteStatement" ], "Effect": "Allow", "Resource": [ "${WorkgroupARN}" ] }, { "Action": [ "redshift-data:GetStatementResult", "redshift-data:CancelStatement", "redshift-data:DescribeStatement" ], "Effect": "Allow", "Resource": "*" }, { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:ListBucket", "s3:DeleteObject" ], "Resource": [ "arn:aws:s3:::${S3BucketName}", "arn:aws:s3:::${S3BucketName}/*" ] } ] } ``` :::tip Make Sure to replace `${S3BucketName}` macros with appropriate value from the Configuration section,
`${WorkgroupARN}` with the Workgroup ARN value from your Redshift Serverless workgroup page :::
### Create a new IAM Role * Sign in to your AWS Management Console and open the [IAM console](https://console.aws.amazon.com/iam/). * Go to Roles > Create role. * Under **Trusted entity type**, select **Custom trust policy**. * Paste the JSON below into the **Custom trust policy** field and replace `${WorkspaceId}` macro with your Jitsu **Workspace ID** (Jitsu UI -> Settings -> Workspace Settings). * In the policy selection screen, find and check the policy created in the [Create policy](#create-a-new-iam-policy) section. * Assign a unique and descriptive name to the role, provide a clear description, and then select Create role. * Find the newly created role in the list and click on it. * Copy the **ARN** value from the **Summary** section and use in Jitsu Redshift Configuration. **Custom trust policy:** ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::907458119157:root" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId": "${WorkspaceId}" } } }, { "Effect": "Allow", "Principal": { "Service": [ "redshift-serverless.amazonaws.com", "redshift.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] } ``` :::info `907458119157` - Jitsu AWS Account Id ::: ### Attaching the new IAM role to the Redshift cluster Redshift will use the same role to Copy data from S3 Bucket. #### To attach the role to the Redshift Cluster: * Open [Provisioned clusters dashboard](https://console.aws.amazon.com/redshiftv2/home) * Select your cluster * In the **Actions** dropdown, select **Manage IAM roles** * In the **Available IAM roles**, select the role you [created](#create-a-new-iam-role) and click **Associate IAM role** * Click **Save changes** #### To attach the role to the Redshift Serverless: * Open [Serverless dashboard](https://console.aws.amazon.com/redshiftv2/home) * In the **Namespaces / Workgroups** section, select your namespace * Open the **Security and encryption** tab and click **Manage IAM roles** * Click the **Associate IAM roles** button and select the role you [created](#create-a-new-iam-role) * Click the **Associate IAM roles** button * Click **Save changes** ### Setting user permissions in Redshift * Open [Redshift Query Editor](https://console.aws.amazon.com/sqlworkbench/home) * Run the following SQL queries to create a new user and grant it the necessary permissions : ```sql -- create a new user CREATE USER ${Username} PASSWORD disable; -- grant user schema creation permissions on the database GRANT CREATE ON DATABASE ${Database} TO ${Username}; -- if you want to grant permissions to the existing schema GRANT ALL ON SCHEMA ${Database}.${Schema} to ${Username}; ``` ```sql -- create new user with the Redshift IAM role name CREATE USER "IAMR:${RoleName}" PASSWORD disable; -- grant user schema creation permissions on the database GRANT CREATE ON DATABASE ${Database} TO "IAMR:${RoleName}"; -- if you want to grant permissions to the existing schema GRANT ALL ON SCHEMA ${Database}.${Schema} to "IAMR:${RoleName}"; ``` :::tip Make Sure to replace `${...}` macros with appropriate values from the Configuration section,
`${RoleName}` with your the name of role you [created](#create-a-new-iam-role) ::: ## Advanced: Implementation Details ### Batch Mode S3 is used as a intermediate storage for batched events.
Algorithm ```sql -- Write to tmp file -- Load tmp file to s3 BEGIN -- start transaction COPY from s3 to tmp_table INSERT into target_table select from tmp_table COMMIT -- commit transaction ```
### Stream Mode :::caution[Performance considerations] Supported as plain insert statements. Don't use at production scale (more than 10 records per minute) ::: ### Deduplication For batch mode the following algorithm is used:
Algorithm ```sql -- Write to tmp file -- Deduplicate rows in tmp file -- Load tmp file to s3 BEGIN -- start transaction COPY from s3 to tmp_table DELETE from target_table T using tmp_table TMP where T.pk_field = TMP.pk_field INSERT into target_table select from tmp_table COMMIT -- commit transaction ```
For stream mode (⚠️Don't use at production scale (more than 10 records per minute)): - SELECT by primary key - Either INSERT or UPDATE depending on result ### Queries Optimization **Timestamp** connection setting is used to optimize SELECT queries. Selected timestamp column will be used as [sort key](https://docs.aws.amazon.com/redshift/latest/dg/t_Sorting_data.html) for target table. --- Source: https://jitsu.com/docs/destinations/warehouse/snowflake
}>Snowflake
Snowflake is an independent cloud data warehouse with compute-based pricing. ## Features | Feature | Supported | |-----------------------------------------------|-----------| | [Batch Mode](#batch-mode) | ✅ | | [Stream Mode](#stream-mode) | ✅ | | [Deduplication](#deduplication) | ✅ | | [Queries Optimization](#queries-optimization) | ✅ | ## Configuration ## Advanced: Implementation Details ### Batch Mode [User Stages](https://docs.snowflake.com/en/user-guide/data-load-local-file-system-create-stage#user-stages) is used as a intermediate storage for batched events.
Algorithm ```sql -- Write to tmp file -- Load tmp file to user stage BEGIN -- start transaction COPY from stage to tmp_table INSERT into target_table select from tmp_table COMMIT -- commit transaction ```
### Stream Mode `INSERT INTO target_table (...) VALUES (..)` ### Deduplication For batch mode the following algorithm is used:
Algorithm ```sql -- Write to tmp file -- Deduplicate rows in tmp file -- Load tmp file to user stage BEGIN -- start transaction COPY from stage to tmp_table MERGE into target_table using (select from tmp_table) ... COMMIT -- commit transaction ```
For stream mode: - SELECT by primary key - Either INSERT or UPDATE depending on result ### Queries Optimization **Timestamp** connection setting is used to optimize SELECT queries. Jitsu sets [clustering key](https://docs.snowflake.com/en/user-guide/tables-clustering-keys.html#what-is-a-clustering-key) to the month part of specified timestamp column values, e.g. `CLUSTER BY (DATE_TRUNC('MONTH', _timestamp))` --- Source: https://jitsu.com/docs/destinations/block-storage/gcs
}>Google Cloud Storage
Google Cloud Storage is a cloud file storage service by Google ## Features | Feature | Supported | |------------------------------------------------|-----------| | [Batch Mode](#batch-mode) | ✅ | | [Deduplication](#deduplication) | ℹ️️ | | [Folder Macros](#organizing-data-into-folders) | ✅ | ## Configuration ## Advanced: Implementation Details This section describes how Jitsu implements various modes and features for Google Cloud Storage. ### Batch Mode Each batch run produces at least one file at GCS bucket with the following name format: ``` /__. ``` `file_number` in case when number of available of events is greater than max batch size (default 10_000) Jitsu splits batch into multiple files. `batch_number` is a number of file in batch (starting from 1). ### Deduplication :::info Deduplication is happening only in the context of a single batch. Jitsu doesn't guarantee deduplication across batches. ::: ### Organizing data into folders You can use macros in **Folder** configuration parameter to organize data into folders. Macros are replaced with corresponding values during the batch run. Supported macros: | Macro | Description | |---------------|----------------------------------------------| | `[DATE]` | Date of the batch run in `YYYY-MM-DD` format | | `[TIMESTAMP]` | Batch run time in unix timestamp format | You can use multiple macros in a single folder path. For example, `events/[DATE]/[TIMESTAMP]` will create a folder with the current date and time. :::note Macros values are based on the batch start time and don't depend on timestamp of the events in the batch. So it is possible that events from different days will be placed into the one date folder. See [Accurate organization of data into folders](#accurate-organization-of-data-into-folders) for an example of how to organize data into folders based on event timestamps. ::: #### Accurate organization of data into folders It is possible to use **Functions** to organize data into folders based on event timestamps. Using functions it is possible to [change the destination table](/docs/functions/#change-destination-table) for a particular event. Table name is used as a prefix for batch file names in GCS. Slashes (`/`) in file name works as directory separator and automatically creates corresponding directory structure in GCS bucket. So it is possible to use functions to organize data into folders based on event timestamps or other event criteria. Example: ```javascript export default async function(event, { log, fetch, props: config }) { // Change destination table to /events. E.g: 2023-01-01/events. // After batch run GCS will contain folder 2023-01-01 with batch files inside. const date = event.timestamp.split('T')[0]; event.JITSU_TABLE_NAME = `${date}/events`; return event; } ``` --- Source: https://jitsu.com/docs/destinations/block-storage/s3
}>S3
S3 is a cloud file storage service by Amazon ## Features | Feature | Supported | |------------------------------------------------|-----------| | [Batch Mode](#batch-mode) | ✅ | | [Deduplication](#deduplication) | ℹ️️ | | [Folder Macros](#organizing-data-into-folders) | ✅ | ## Configuration Jitsu supports both Access Key based authentication and IAM Role based authentication for Redshift data warehouse. ### General parameters | Parameter name | Description | |---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------| | **Authentication Method** | `accessKey` - Access Key based authentication, `iam` - IAM Role based authentication | | **S3 Region** | AWS Region of S3 bucket | | **S3 Bucket Name** | S3 Bucket Name | | **Folder** | Folder in the block storage bucket where files will be stored | | **Format** | Format of the files stored in the block storage: `ndjson` - Newline Delimited JSON, `ndjson_flat` - Newline Delimited JSON flattened, `csv` - CSV | | **Compression** | Compression algorithm used for the files stored in the block storage: `gzip` - GZIP, `none` - no compression. | Configuration settings depend on the selected authentication method. ### Access Key based authentication | Parameter name | Description | |--------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------| | **S3 Access Key Id** | S3 Access Key Id. | | **S3 Secret Access Key** | S3 Secret Access Key | | **Endpoint** | Custom endpoint of S3-compatible server (Optional) | ### IAM Role based authentication | Parameter name | Description | |-------------------------|---------------------------------------------------------| | **Role ARN** | IAM role ARN | To setup IAM Role based authentication for S3, follow the [Advanced: IAM Role for Jitsu](#advanced-iam-role-for-jitsu) section. ## Advanced: IAM Role for Jitsu To allow Jitsu to connect to S3 using IAM Role, the following steps should be performed in AWS Console: * Create a new IAM Policy * Create a new IAM Role ### Create a new IAM Policy * Sign in to your AWS Management Console and open the [IAM console](https://console.aws.amazon.com/iam/). * Go to Policies > Create policy. * Choose the JSON option. Then, paste the JSON below * Assign a unique and descriptive name to the policy, provide a clear description, and then select Create Policy. ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:ListBucket", "s3:DeleteObject" ], "Resource": [ "arn:aws:s3:::${S3BucketName}", "arn:aws:s3:::${S3BucketName}/*" ] } ] } ``` :::tip Make Sure to replace `${S3BucketName}` macros with the value from the Configuration section ::: ### Create a new IAM Role * Sign in to your AWS Management Console and open the [IAM console](https://console.aws.amazon.com/iam/). * Go to Roles > Create role. * Under **Trusted entity type**, select **Custom trust policy**. * Paste the JSON below into the **Custom trust policy** field and replace `${WorkspaceId}` macro with your Jitsu **Workspace ID** (Jitsu UI -> Settings -> Workspace Settings). * In the policy selection screen, find and check the policy created in the [Create policy](#create-a-new-iam-policy) section. * Assign a unique and descriptive name to the role, provide a clear description, and then select Create role. * Find the newly created role in the list and click on it. * Copy the **ARN** value from the **Summary** section and use in Jitsu S3 Configuration. **Custom trust policy:** ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::907458119157:root" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId": "${WorkspaceId}" } } } ] } ``` :::info `907458119157` - Jitsu AWS Account Id ::: ## Advanced: Implementation Details This section describes how Jitsu implements various modes and features for S3. ### Batch Mode Each batch run produces at least one file at s3 bucket with the following name format: ``` /__. ``` `file_number` in case when number of available of events is greater than max batch size (default 10_000) Jitsu splits batch into multiple files. `batch_number` is a number of file in batch (starting from 1). ### Deduplication :::info Deduplication is happening only in the context of a single batch. Jitsu doesn't guarantee deduplication across batches. ::: ### Organizing data into folders You can use macros in **Folder** configuration parameter to organize data into folders. Macros are replaced with corresponding values during the batch run. Supported macros: | Macro | Description | |---------------|----------------------------------------------| | `[DATE]` | Date of the batch run in `YYYY-MM-DD` format | | `[TIMESTAMP]` | Batch run time in unix timestamp format | You can use multiple macros in a single folder path. For example, `events/[DATE]/[TIMESTAMP]` will create a folder with the current date and time. :::note Macros values are based on the batch start time and don't depend on timestamp of the events in the batch. So it is possible that events from different days will be placed into the one date folder. See [Accurate organization of data into folders](#accurate-organization-of-data-into-folders) for an example of how to organize data into folders based on event timestamps. ::: #### Accurate organization of data into folders It is possible to use **Functions** to organize data into folders based on event timestamps. Using functions it is possible to [change the destination table](/docs/functions/#change-destination-table) for a particular event. Table name is used as a prefix for batch file names in S3. Slashes (`/`) in file name works as directory separator and automatically creates corresponding directory structure in S3 bucket. So it is possible to use functions to organize data into folders based on event timestamps or other event criteria. Example: ```javascript export default async function(event, { log, fetch, props: config }) { // Change destination table to /events. E.g: 2023-01-01/events. // After batch run S3 will contain folder 2023-01-01 with batch files inside. const date = event.timestamp.split('T')[0]; event.JITSU_TABLE_NAME = `${date}/events`; return event; } ``` --- Source: https://jitsu.com/docs/destinations/amplitude # Amplitude Amplitude is a product analytics platform that provides insights into user behavior. ## Configuration --- Source: https://jitsu.com/docs/destinations/google-ads
}>Google Ads
[Google Ads](https://ads.google.com) measures which ads produce results. Its conversion tracking tag only sees what happens in the browser, so anything that happens afterwards — a lead that closes weeks later, a subscription that renews, a purchase confirmed in your backend — never reaches it. Jitsu's Google Ads destination sends those conversions server-side, from data you already collect. ## What it can send Pick the **Conversion Type** that matches what you're measuring. ### Conversion — report new conversions The default. Jitsu creates a conversion in Google Ads for each matching event. This covers both **offline conversions**, matched to the original ad click through a `gclid`, and **Enhanced Conversions for Leads**, matched through hashed customer data such as an email address or phone number. They're the same upload; the difference is only which identifier is available. Use a conversion action of type **Import** (upload from clicks). ### Enhancement — Enhanced Conversions for Web Use this when your Google tag already records the conversion on your website, and you want to improve how many of those conversions Google can attribute. Jitsu doesn't create a conversion here. It attaches hashed customer data to the conversion the tag already reported, matched on **order ID**. Events without an order ID are skipped, because there's nothing to attach them to. Use a conversion action of type **Website**, and make sure your tag sends the same order ID (`transaction_id`) that appears in your Jitsu events. ## Choosing which events convert Jitsu sends `track`, `page` and `screen` events. Two settings control what goes where: - **Events** — which events to send at all. Leave empty to send only `track` events, list event names separated by commas, or use `*` for everything. - **Conversion Actions** — route individual events to different conversion actions, one `eventName=conversionActionId` per line. Anything not listed uses the default **Conversion Action ID**. ``` Order Completed=111222333 Signed Up=444555666 ``` ## How conversions get matched Google needs at least one signal to connect a conversion to an ad. Jitsu supplies whatever the event has: | Signal | Where it comes from | |---|---| | `gclid`, `gbraid`, `wbraid` | the landing page URL, or Google's own conversion-linker cookie | | Email, phone number | `traits.email`, and the trait named in **Phone Trait Name** | | First and last name, country, postal code | user traits, falling back to Jitsu's IP geolocation | | IP address and user agent | the event itself | Events with none of these are skipped rather than sent — Google would have no way to attribute them. ### Click IDs are remembered People rarely click an ad and convert in the same visit. When someone arrives from a Google ad, the click ID is in the landing page URL — and by the time they buy something, days later, it's long gone. So Jitsu watches every event for a click ID and keeps it against that user for 90 days. When the conversion finally happens, the ad that earned it is still credited. Turn this off with **Remember Click IDs** if you'd rather only use click IDs present on the converting event itself. This works with any recent version of `@jitsu/js`. Version 2.x and later also reads Google's conversion-linker cookies, which helps when a visitor returns directly rather than through a new ad click. ## Conversion value Jitsu reads the conversion value from `properties.total`, then `properties.value`, then `properties.revenue`, and the currency from `properties.currency`. The order ID comes from `properties.orderId` or `properties.order_id`. If your events name these differently, map them with a [Function](/docs/functions) before the destination. ## Consent If you collect consent signals, set `ad_user_data` and `ad_personalization` under `context.consent.categoryPreferences` and Jitsu forwards them with every conversion, as required for users in the EEA and UK. ## Privacy Email addresses, phone numbers and names are normalized and SHA-256 hashed before they leave Jitsu — Google never receives them in plain text, and neither does anything in between. Country and postal code are sent as-is, which is what Google's matching expects. Phone numbers must be in [E.164](https://en.wikipedia.org/wiki/E.164) format. If yours are stored locally, set **Default Phone Country Code** and Jitsu will normalize them; without it, numbers that have no country code are left out rather than sent in a form Google rejects. ## Testing a new setup Enable **Validate Only** to have Google check every request and report problems without recording any conversions. Watch Live Events for the responses, then turn it off once the setup looks right. ## Configuration --- Source: https://jitsu.com/docs/destinations/ga4
}>Google Analytics 4 (Cloud Mode)
Google Analytics 4 is a service offered by Google that reports website traffic data and marketing trends. This destinations sends jitsu events to Google Analytics 4 using The Google Analytics Measurement Protocol. ## Configuration ## Limitations :::caution[Note] The intent of the Measurement Protocol is to augment automatic collection via gtag, Tag Manager, and Google Analytics for Firebase not to replace them. ::: Using this destination only will result in partial reporting. Not provided by this destination: - UTM parameters: `source`, `medium`, `campaign`, `term`, `content` - Geographic data: `country`, `region`, `city`, ... - User device data: `device`, `operating system`, `browser`, ... Read more about the limitations of the Measurement Protocol [here](https://developers.google.com/analytics/devguides/collection/protocol/ga4#caveats_to_measurement_protocol). --- Source: https://jitsu.com/docs/destinations/ga4-tag
}>Google Analytics 4 (Device Mode)
Google Analytics 4 is a service offered by Google that reports website traffic data and marketing trends. This destinations tracks users in Google Analytics with client side code snippet. ## Configuration --- Source: https://jitsu.com/docs/destinations/hotjar
}>Hotjar
[Hotjar](https://www.hotjar.com) is a heatmap, session-recording and survey tool. This is a **device (client-side) destination**: Jitsu loads the Hotjar tag in the visitor's browser and forwards events to it, so recordings and surveys can be filtered by user attribute. Event mapping: - **identify** — calls `hj('identify', userId, attributes)`, forwarding traits as user attributes. An anonymous user (`null` id) is allowed. - **track** — calls `hj('event', name)`. Event names are truncated to 250 characters and spaces are replaced with underscores, per Hotjar's rules. Hotjar events carry only a name; enable **Track event properties → attributes** to merge properties into the identified user's record. - **page** — no-op by default. Enable **SPA page views** to emit `hj('stateChange', path)` on each Jitsu `page` event (recommended for single-page apps). If you already load Hotjar on your site (via your own snippet or a tag manager), disable **Load Hotjar** — Jitsu will forward events to your existing Hotjar instance instead of loading a second copy. In that case the Site ID is only used when Jitsu loads the tag. ## Configuration --- Source: https://jitsu.com/docs/destinations/hubspot # Hubspot --- Source: https://jitsu.com/docs/destinations/clarity
}>Microsoft Clarity
[Microsoft Clarity](https://clarity.microsoft.com) is a free heatmap and session-recording tool. This is a **device (client-side) destination**: Jitsu loads the Clarity tag in the visitor's browser and forwards events to it, so recordings and heatmaps are attributed and filterable. Event mapping: - **identify** — calls `clarity('identify', userId)` and forwards traits as filterable Clarity custom tags (`clarity('set', key, value)`). `name` / `email` traits are used as the session's friendly name. - **track** — calls `clarity('event', name)`. Clarity events carry only a name; enable **Track event properties → tags** to also forward properties as custom tags. - **page** — no-op; Clarity records page views automatically. If you already load Clarity on your site (via your own snippet or a tag manager), disable **Load Clarity** — Jitsu will forward events to your existing Clarity instance instead of loading a second copy. In that case the Project ID is only used when Jitsu loads the tag. ## Configuration --- Source: https://jitsu.com/docs/destinations/mixpanel
}>Mixpanel
Mixpanel is a product analytics platform that provides insights into user behavior. ## Configuration --- Source: https://jitsu.com/docs/destinations/posthog
}>Posthog
Posthog is an open-source product analytics tool. Jitsu supports both self-hosted Posthog and Posthog Cloud. ## Configuration --- Source: https://jitsu.com/docs/destinations/resend
}>Resend
[Resend](https://resend.com) is an email API for developers. This destination syncs your Jitsu users to Resend as **contacts**, so you can email or broadcast to them and manage their segment membership. ## How it works Resend identifies contacts by **email address** — a contact is a global entity keyed by its email. Resend has no concept of a `userId`, so email is the only stable key. Jitsu maps events to Resend contacts as follows: | Event | What Jitsu does | |------------|---------------------------------------------------------------------------------------------------| | `identify` | Creates the contact if new, updates it otherwise, and reconciles its audience membership. | | `track` / `page` / `screen` | Updates the existing contact's custom properties (never creates a contact). | | `group` | Updates the existing contact with the group id and group traits as `group_*` properties. | ### identify Each `identify()` upserts a contact: * **Email** is read from `traits.email` (falling back to `context.traits.email`). Events without an email are skipped — Resend requires an email. * **First / last name** come from `traits.firstName` / `traits.lastName`, or are split from `traits.name`. * **`unsubscribed`** is set from `traits.unsubscribed` when it is a boolean. * **All other traits** are stored as Resend contact [properties](https://resend.com/docs/dashboard/audiences/introduction) (custom fields). Resend properties are strings, so non-string values are coerced (objects are JSON-encoded). The Jitsu `userId` and `anonymousId` are stored as `jitsu_user_id` and `jitsu_anonymous_id`. Resend requires every property to be defined before it can be set, so Jitsu automatically creates a string-typed property definition for each new trait key the first time it sees it. * The contact's [audience](#audiences) membership is reconciled to match the configured set. A repeated `identify()` for the same email **updates** the existing contact rather than creating a duplicate. ### Other events `track`, `page` and `screen` events **update** an existing contact but never create one. An update happens only when the event carries custom contact fields (typically added by a [transformation function](/docs/functions)) — a plain event with just an email does nothing. The email is resolved from the event's traits, or from the `userId` cache (see [below](#matching-events-by-userid)). If no contact matches, the event is skipped. `group` events fold the `groupId` and group traits into the contact's properties, namespaced as `group_`. They don't change audience membership. ## Audiences Set **Audiences** in the destination config to a **comma-separated list of audience names** (not IDs) — for example `Customers, Beta`. Jitsu resolves each name to a Resend audience, **creating any that don't exist yet**. Leave it empty to add contacts without an audience. You can override the audiences per event by setting a `resendAudiences` trait (also comma-separated names) from a [function](/docs/functions): ```javascript export default async function (event) { if (event.traits?.plan === "enterprise") { event.traits.resendAudiences = "Customers, Enterprise"; } return event; } ``` ### Membership is reconciled On each `identify()`, Jitsu makes the contact's membership match the desired set. Audiences it **previously added** that are no longer listed are **removed**. For example, if a function returns `A, B` on one event and `B, C` on the next, the contact is removed from `A`, kept in `B`, and added to `C`. To do this safely, the connector records which audiences it added in a contact property named `jitsu_managed_audiences` — so it only ever removes audiences **it** added, never ones you added manually or via another tool. There's no separate state stored in Jitsu; the record lives on the contact. Notes: * Reconciliation runs only when a desired set is provided for the event — i.e. the `resendAudiences` trait is present, or the destination has configured Audiences. An event with neither leaves membership untouched. * Setting `resendAudiences` to an empty string explicitly clears the connector-managed audiences. * Only `identify()` manages membership; `track` / `page` / `group` events never change it. ## Matching events by userId Resend cannot look a contact up by `userId` — only by email or Resend's own contact id. So by default, only events that carry an email are matched to a contact. Enable **Resolve email from userId** to change this. When on, every `identify()` caches a `userId → email` mapping, and later `track` / `page` / `group` events that carry only a `userId` are matched to the contact via that cache. Notes: * The cache is only populated from `identify()` calls seen **after** the flag is enabled — historical mappings are not backfilled. * If neither an email nor a cached `userId` resolves, the event is skipped (no contact is created or updated). ## Configuration --- Source: https://jitsu.com/docs/destinations/salesforce [//]: # (sidebar_class_name: hidden)
}>Salesforce
Salesforce is a leading customer relationship management (CRM) platform that provides a comprehensive suite of tools for managing customer relationships, sales, and marketing. Jitsu’s Salesforce destination allows you to **create**, **update**, **upsert** and **delete** records for any object type. ## Mapping ### Automatic Mapping :::tip Most integrations will require setup of [custom mapping](#custom-mapping) using JavaScript Functions. ::: Salesforce supports a wide range of object types, including custom objects created by clients. Jitsu provides limited automatic mapping of event types to Salesforce object types: * identify → Lead * group → Account * chosen event* → Contact * Jitsu can automatically populate fields of the Contact object type if the `SALESFORCE_SOBJECT` is set to Contact within a Function. See [Custom Mapping](#custom-mapping) section below for more details. Jitsu automatically searches for properties whose names match the field names of the corresponding Salesforce Object (as listed in the Field Name column in Salesforce Object Manager’s Fields & Relationships table). These properties are resolved from the following paths: `event.traits`, `event.properties`, `event.context.traits`. | Salesforce 'Lead' property | Jitsu event path | Jitsu alternative event path | |----------------------------|----------------------------|--------------------------------| | Company | traits.company | properties.company | | LastName | traits.last_name | properties.last_name | | FirstName | traits.first_name | properties.first_name | | Email | traits.email | properties.email | | City | traits.address.city | properties.address.city | | State | traits.address.state | properties.address.state | | Country | traits.address.country | properties.address.country | | PostalCode | traits.address.postal_code | properties.address.postal_code | | Street | traits.address.street | properties.address.street | | Salesforce 'Account' property | Jitsu event path | Jitsu alternative event path | |-------------------------------|----------------------------|--------------------------------| | Name | traits.name | | | AccountNumber | groupId | | | NumberOfEmployees | traits.employees | properties.employees | | BillingCity | traits.address.city | properties.address.city | | BillingState | traits.address.state | properties.address.state | | BillingCountry | traits.address.country | properties.address.country | | BillingPostalCode | traits.address.postal_code | properties.address.postal_code | | BillingStreet | traits.address.street | properties.address.street | | Phone | traits.phone | properties.phone | | Description | traits.description | properties.description | | Website | traits.website | properties.website | | Salesforce 'Contact' property | Jitsu event path | Jitsu alternative event path | |-------------------------------|----------------------------|--------------------------------| | LastName | traits.last_name | properties.last_name | | FirstName | traits.first_name | properties.first_name | | Email | traits.email | properties.email | | MailingCity | traits.address.city | properties.address.city | | MailingState | traits.address.state | properties.address.state | | MailingCountry | traits.address.country | properties.address.country | | MailingPostalCode | traits.address.postal_code | properties.address.postal_code | | MailingStreet | traits.address.street | properties.address.street | ### Custom Mapping Using Functions unlocks the full capabilities of Jitsu’s Salesforce destination, including: * Support for all Salesforce object types, including custom objects. * Ability to **create**, **update**, **upsert** and **delete** records. * Full control over object payload sent to Salesforce REST API. Custom mapping is controlled with the set of properties that can be added to the root level of the event object: | Property Name | Description | Default | |------------------------------|---------------------------------------------------------------------------------------------------|----------------------------------------------------------------------| | SALESFORCE_SOBJECT | Salesforce object type to work with. | `Lead` for `identify`, `Account` for `group`, otherwise **Required** | | SALESFORCE_PAYLOAD | Salesforce Object payload | See [Default Payload](#default-payload) | | SALESFORCE_OPERATION | API operation: **insert**, **update**, **upsert** or **delete** | insert | | SALESFORCE_MATCHERS | For **update**, **upsert** and **delete** operation, matchers used to find record to operate on. | `{}` | | SALESFORCE_MATCHERS_OPERATOR | Logical operator used to combine matchers: `OR` or `AND` | `OR` | #### Selecting Object Type The `SALESFORCE_SOBJECT` property specifies the Salesforce object type to work with. It can be any standard or custom object type in Salesforce: ```javascript export default async function(event, context) { event.SALESFORCE_SOBJECT = "Contact"; // Specify the Salesforce object type return event } ``` For **custom object** types, use the API name of the custom object (as listed in the API Name column in Salesforce Object Manager table). Typically, custom object names end with `__c` suffix. ```javascript export default async function(event, context) { event.SALESFORCE_SOBJECT = "My_Custom__c"; // Specify the Salesforce object type return event } ``` #### API Operations The `SALESFORCE_OPERATION` property specifies the API operation to perform on the Salesforce object type defined in the `SALESFORCE_SOBJECT` property. The supported operations are: * **insert**: Create a new record. * **update**: Update an existing record. * **upsert**: Update an existing record or create a new one if it doesn't exist. * **delete**: Delete an existing record. **update**, **upsert** and **delete** operations require the `SALESFORCE_MATCHERS` property to be specified to find the **Record ID** of the record to operate on. See [Object Matchers](#object-matchers) section below for more details. #### Customizing Object Payload The `SALESFORCE_PAYLOAD` property allows you to specify the payload for the Salesforce object type defined in the `SALESFORCE_SOBJECT` property. This property should be an object containing the fields and values to be sent to Salesforce. For example, to create a new Contact record, you can specify the payload as follows: ```javascript export default async function(event, context) { event.SALESFORCE_SOBJECT = "Contact"; event.SALESFORCE_PAYLOAD = { LastName: event.traits.last_name || "John", FirstName: event.traits.first_name || "Doe", Email: event.traits.email || "johndoe@example.com", MailingCity: "Springfield", MailingState: "CA", MailingCountry: "USA", MailingPostalCode: "90210", MailingStreet: "123 Example Street" }; return event } ``` Jitsu won't add any additional properties to the provided payload, but it can remove properties that are not present in the Salesforce object type specified in the `SALESFORCE_SOBJECT` property. ##### Computed Values For `update` or `upsert` API operations, it may be necessary to compute some field values based on the values already stored in Salesforce. For example, you may want to accumulate a numeric field value or concatenate a string field. Jitsu supports operators that use both the existing stored value and the value from `SALESFORCE_PAYLOAD` to compute the final value sent to Salesforce. To apply an operator on a field value, instead of providing a direct value, you should specify an object with `op` and `value` properties inside the `SALESFORCE_PAYLOAD`, as shown below: ```javascript export default async function(event, context) { event.SALESFORCE_SOBJECT = "Lead"; event.SALESFORCE_OPERATION = "update"; event.SALESFORCE_PAYLOAD = { // add value of `cost` property to the existing value of `Total_Spend__c` field Total_Spend__c: { op: "add", value: event.properties.cost } }; return event } ``` ###### Supported operators: * `setOnce` - sets the field value to the provided value only if the field does not have value yet. * `add` - adds the provided value to the stored field value. * `subtract` - subtracts the provided value from the stored field value. * `multiply` - multiplies the stored field value by the provided value. * `divide` - divides the stored field value by the provided value. * `concat` - concatenates the stored string field value with the provided string value. * `prefix` - prepends the provided string value to the stored string field value. * `dateAdd` - adds the provided number of **seconds** to the stored date field value. * `dateSubtract` - subtracts the provided number of **seconds** from the stored date field value. For binary operators, if the stored field value is not yet set, Jitsu uses safe default values: `0` for numbers, an empty string for strings, and the current date for date fields. ##### Default Payload When no `SALESFORCE_PAYLOAD` property specified, Jitsu uses [Automatic Mapping](#automatic-mapping) to populate the payload for the Salesforce object type specified in the `SALESFORCE_SOBJECT` property. Additionally, Jitsu automatically searches for properties whose names match the field names of the corresponding Salesforce Object (as listed in the Field Name column in Salesforce Object Manager’s Fields & Relationships table). These properties are resolved from the following paths: `event.traits`, `event.properties`, `event.context.traits`. #### Object Matchers Required for **update**, **upsert** and **delete** operations. The `SALESFORCE_MATCHERS` property should contain a javascript object with properties that will be used to find the record to operate on. The properties should match the field names of the corresponding Salesforce Object (as listed in the Field Name column in Salesforce Object Manager’s Fields & Relationships table). If provided matchers do not match any record or match multiple records, Jitsu will log an error and skip the event. **Example**: Update Contact with LastName "Doe" and set MailingPostalCode to "90211": ```javascript export default async function(event, context) { event.SALESFORCE_SOBJECT = "Contact"; event.SALESFORCE_OPERATION = "update"; event.SALESFORCE_MATCHERS = { LastName: "Doe" } event.SALESFORCE_PAYLOAD = { MailingPostalCode: "90211", }; return event } ``` :::tip If you already have the **Record ID** of the record to operate on, you can use it directly in the `SALESFORCE_MATCHERS` property: ```javascript //... event.SALESFORCE_MATCHERS = { Id: "0012300000abcdefGHI" // Record ID of the Contact } //... ``` That allows to avoid extra API call to find the Record ID by matchers. ::: ##### Combining Matchers `SALESFORCE_MATCHERS` object can contain multiple properties to match against the record in Salesforce. By default, Jitsu uses `OR` operator to combine matcher properties. E.g. if you specify `SALESFORCE_MATCHERS` as follows: ```javascript //... event.SALESFORCE_MATCHERS = { LastName: "Doe", FirstName: "John" } //... ``` It will match any record that has either `LastName` equal to "Doe" or `FirstName` equal to "John". You can change the operator to `AND` by setting the `SALESFORCE_MATCHERS_OPERATOR` property: ```javascript //... event.SALESFORCE_MATCHERS_OPERATOR = "AND"; event.SALESFORCE_MATCHERS = { LastName: "Doe", FirstName: "John" } //... ``` This will match only record that has both `LastName` equal to "Doe" and `FirstName` equal to "John". ## Configuration --- Source: https://jitsu.com/docs/destinations/sendgrid
}>SendGrid
[SendGrid](https://sendgrid.com) (Twilio SendGrid) is an email delivery and marketing platform. This destination syncs your Jitsu users to SendGrid as **marketing contacts** and manages their list membership, using the [Marketing Contacts API](https://www.twilio.com/docs/sendgrid/api-reference/contacts/add-or-update-a-contact). ## How it works SendGrid identifies contacts by **email address**. Jitsu maps events to SendGrid contacts as follows: | Event | What Jitsu does | |------------|---------------------------------------------------------------------------------------------------| | `identify` | Upserts the contact (create or update) and reconciles its list membership. | | `track` / `page` / `screen` | Updates the existing contact's fields (never creates a contact). | | `group` | Updates the existing contact with the group id and group traits as `group_*` fields. | Contact upserts in SendGrid are **asynchronous** — the API accepts the request (HTTP 202) and processes it in the background, so changes may take a moment to appear in the dashboard. ### identify Each `identify()` upserts a contact via `PUT /v3/marketing/contacts`: * **Email** is read from `traits.email` (falling back to `context.traits.email`). Events without an email are skipped. * **First / last name** come from `traits.firstName` / `traits.lastName`, or are split from `traits.name`. * The Jitsu `userId` and `anonymousId` are stored as the custom fields `jitsu_user_id` and `jitsu_anonymous_id`. (Email is kept as the contact's only SendGrid identifier — SendGrid rejects an upsert that omits any identifier a matched contact already has, so `userId`/`anonymousId` are not used as SendGrid identifiers.) * **All other traits** are stored as SendGrid [custom fields](https://www.twilio.com/docs/sendgrid/api-reference/custom-fields). SendGrid requires custom fields to be defined before use and references them by an internal field ID, so Jitsu automatically creates a `Text` field definition for each new trait key it sees and maps values to the right IDs. Traits whose names match a writable SendGrid reserved field (`city`, `country`, `postal_code`, …) are written to that reserved field instead. * The contact's [list](#lists) membership is reconciled to match the configured set. A repeated `identify()` for the same email **updates** the existing contact rather than creating a duplicate. ### Other events `track`, `page`, `screen` and `group` events **update** an existing contact but never create one. Because the SendGrid upsert would otherwise create a contact, Jitsu first checks that the contact exists (via the [search API](https://www.twilio.com/docs/sendgrid/api-reference/contacts/search-contacts-by-email)) and skips the event if it doesn't. An update happens only when the event carries fields explicitly set on it (typically added by a [transformation function](/docs/functions)) — ambient identify traits echoed on the event are ignored so the contact isn't rewritten on every event. ## Lists Set **Lists** in the destination config to a **comma-separated list of list names** (not IDs) — for example `Customers, Beta`. Jitsu resolves each name to a SendGrid Marketing list, **creating any that don't exist yet**. Leave it empty to add contacts without a list. You can override the lists per event by setting a `sendgridLists` trait (also comma-separated names) from a [function](/docs/functions): ```javascript export default async function (event) { if (event.traits?.plan === "enterprise") { event.traits.sendgridLists = "Customers, Enterprise"; } return event; } ``` ### Membership is reconciled On each `identify()`, Jitsu makes the contact's membership match the desired set. Lists it **previously added** that are no longer listed are **removed**. For example, if a function returns `A, B` on one event and `B, C` on the next, the contact is removed from `A`, kept in `B`, and added to `C`. To do this safely, the connector records which lists it added in a custom field named `jitsu_managed_lists` — so it only ever removes lists **it** added, never ones you added manually or via another tool. There's no separate state stored in Jitsu; the record lives on the contact. Notes: * Reconciliation runs only when a desired set is provided for the event — i.e. the `sendgridLists` trait is present, or the destination has configured Lists. An event with neither leaves membership untouched. * Setting `sendgridLists` to an empty string explicitly clears the connector-managed lists. * Only `identify()` manages membership; `track` / `page` / `group` events never change it. ## Matching events by userId SendGrid matches contacts by email. Enable **Resolve email from userId** to have every `identify()` cache a `userId → email` mapping, so later `track` / `page` / `group` events that carry only a `userId` can be matched to the contact via that cache. The cache is only populated from `identify()` calls seen after the flag is enabled. If neither an email nor a cached `userId` resolves, the event is skipped. ## Notes * Unsubscribe / suppression state is managed separately in SendGrid (suppression groups) and is not set by this destination. * Contact upserts are processed asynchronously by SendGrid and can take up to a minute or two to appear. The first event that introduces a brand-new custom field is retried once (a just-created field definition isn't usable immediately); it lands automatically on the retry. * SendGrid's free tier is a time-limited trial; a paid Marketing Campaigns plan is required for ongoing use. ## Configuration --- Source: https://jitsu.com/docs/destinations/tag
}>Tag
Inserts any html or javascript into your page. Use this to add any third party tracking code as Google Analytics, Facebook Pixel, Twitter Pixel, etc. ## Configuration --- Source: https://jitsu.com/docs/destinations/webhook
}>Webhook
Send data to any HTTP endpoint. You can use this destination to send data to Slack, Discord, or any other service that accepts HTTP requests. ## Configuration --- Source: https://jitsu.com/docs/features/custom-domains # Custom Domains :::info Custom Domains is the feature of [Jitsu Cloud](https://use.jitsu.com). To implement custom domains for [self-hosted](/docs/self-hosting/) Jitsu you need to use 3rd party solutions such as [Cloudflare](https://www.cloudflare.com/) or [Caddy](https://caddyserver.com/). ::: ## What are Custom Domains? If you're implementing client side-tracking with Jitsu, the javascript code on your web page will be sending data to a dedicated subdomain `.d.jitsu.com`. While this works fine, some data may be lost due following reasons: * Ad Blockers are widely used they may block requests to `*.jitsu.com` domains. Depending on nature of your business, up to 30% of your data may be lost * The other reason for data loss is [Safari's tracking prevention](https://support.apple.com/guide/safari/sfri40732/mac) feature. Safari will detect that the script is loaded from `d.jitsu.com` domain and will block it from accessing certain data such as analytics cookies. As a result, the precision of your analytics will be affected. ### Custom Domains to the Rescue To solve this problem, Jitsu Cloud allows you to attach custom domains to your Sites. Instead of using `.d.jitsu.com`, the tracking will be loaded from `.your-site.com`, such as `events.your-site.com`. In that case, both Ad Blockers and Tracking Prevention will not affect your tracking. :::tip Consider moving as much of your tracking to [server-side](https://jitsu.com/blog/server-side-tracking) as possible. This will improve the quality of your data even further. ::: ## How to Configure Custom Domains When you create a new Site, add your subdomain name to the `Custom Domains` field. Then point your subdomain to `cname.jitsu.com` --- Source: https://jitsu.com/docs/features/deduplication # Deduplication In real life scenario it is hard to guarantee that site events never be duplicated. That may happen due to various reasons: - connectivity issues especially in mobile networks (device browser may repeat network request in cases of bad reception) - connection errors while interacting with the destination - client may wish to reprocess some part of events from the past to fix some data issues. - and others duplicates in a data warehouse may cause various issues: - incorrect metrics calculation - incorrect attribution - incorrect user segmentation That is why it is important to collect events in a way that prevents data duplication. Jitsu provides a **Deduplication** feature that is enabled by default for all data warehouse connections. ## How it works For each destination Jitsu uses deduplication approach that is based on the destination capabilities. To find out details about each destination please refer to the corresponding destination documentation in **Destinations» Warehouses** section. E.g. for ClickHouse deduplication is built on top of [ReplacingMergeTree](/docs/destinations/warehouse/clickhouse#deduplication) engine. ## How to enable You can find Deduplication feature on the **Connection** editing page in the **Advanced** section: It is enabled by default for all data warehouse connections. --- Source: https://jitsu.com/docs/features/event-backups # Event Backups & Data Retention :::info Event Backups is a feature of [Jitsu Cloud](https://use.jitsu.com). Backups are kept for up to 7 days on the Free plan and up to 90 days on paid plans. [Self-hosted](/docs/self-hosting/) Jitsu doesn't archive events — configure a warehouse or file-storage destination if you need a raw copy of incoming events. ::: ## What are Event Backups? Jitsu keeps a raw copy of every event it accepts in a dedicated Google Cloud Storage bucket, separate from your destinations. If a destination or warehouse fails, drops a table, or silently loses data, the backup is the copy Jitsu can replay events from. Backups are written as compressed newline-delimited JSON, one file per batch, grouped by date. Backups are governed by a **retention window**: backups older than the window are deleted automatically. The window is set per workspace in **Settings → Data Retention & Backups**. Workspaces on the Free plan keep **7 days** of backups by default; paid plans default to **90 days**. ## Choosing a retention window | Window | Availability | When it fits | | --- | --- | --- | | **No backups** | All plans | You keep your own raw copy (e.g. a file-storage destination) or must not retain data outside your destinations | | **7 days** | All plans | Enough to replay a short destination outage | | **30 days** | Paid plans | Covers a month of destination or warehouse issues | | **90 days** | Paid plans | Maximum self-serve window — replay a full quarter | Longer or custom windows are available on Enterprise plans — [contact us](https://jitsu.com/contact). Changes are applied to the backup bucket within an hour. Shortening the window applies to existing backups too: anything older than the new window is deleted and cannot be restored. :::caution Setting the window to **No backups** stops archiving and deletes existing backups. From then on, Jitsu keeps no copy of your events beyond the pipeline stages listed below: if a destination or warehouse fails or loses data, those events cannot be recovered. The console asks for an explicit acknowledgement before saving this setting. ::: ## How long Jitsu keeps your data Event data passes through several stages of the pipeline, each with its own limited retention. Once data has aged out of every stage, Jitsu no longer has a copy of it. | Stage | Retention | What it holds | | --- | --- | --- | | Event stream | up to 16 hours | All incoming events, in the internal message queue (Kafka) during normal processing | | Batched destinations | up to 2 days | Events destined for warehouses and other batch-mode destinations, until delivered | | Failed events | 7 days | Undeliverable events, in a dead-letter queue for retries and troubleshooting | | [Identity stitching](/docs/features/identity-stitching) | up to 30 days | When enabled, anonymous events waiting to be associated with a user profile | | **Backups** | **your retention window** | Raw event backups in Google Cloud Storage — the only copy Jitsu can restore from | | [Live Events](/docs/features/live-events) | most recent 200,000 entries | Events and function logs per configured entity (source, destination, …) for operational visibility | ## Restoring from a backup Replays from the backup bucket are performed by the Jitsu team. If you need events restored into a destination, [contact support](https://jitsu.com/contact) with the workspace, the destination and the time range. --- Source: https://jitsu.com/docs/features/identity-stitching # Identity Stitching **Identity Stitching** is a feature that allows you to attribute all events received from an anonymous user to a known user after logging in. Jitsu retroactively updates anonymous events in supported destinations with user traits. Identity Stitching relies on Deduplication feature and is available for the following destinations: - [ClickHouse](/docs/destinations/warehouse/clickhouse) - [BigQuery](/docs/destinations/warehouse/bigquery) - [PostgreSQL](/docs/destinations/warehouse/postgres) - [Redshift](/docs/destinations/warehouse/redshift) - [Snowflake](/docs/destinations/warehouse/snowflake) - [MySQL](/docs/destinations/warehouse/mysql) ## How it works For each unidentified user Jitsu client library generates a unique anonymous ID and tries to store it in browser's cookies (If cookies consent was granted). If it is allowed to store anonymous ID, Jitsu client will add `anonymousId` field to all events produced with that. Jitsu server uses separate intermediate storage to keep the last **30 days** of events for each anonymous user. When `identify` method is called Jitsu client adds `userId` field and `traits` to the events while keeping `anonymousId` in place. Jitsu server loads all events for that `anonymousId` from storage, enriches them with `userId` field and user `traits`, and sends them to the destination. Thanks to the [Deduplication](/docs/features/deduplication) feature, instead of producing duplicated events, Jitsu will update existing events with enriched data. ### Example Let's say we have a website with a following events flow: Anonymous user had some activity on the website at Jan 1, then 23 days later he decided to signed up. Without Identity Stitching, we could have the following table in a data warehouse: | timestamp | type | path | anonymous_id | user_id | context_traits_email | |---------------------|----------|---------|--------------|---------|----------------------| | 2023-01-01 00:00:00 | page | /shop | abc123 | | | | 2023-01-01 00:01:00 | page | /order | abc123 | | | | 2023-01-23 00:00:00 | page | /signup | abc123 | | | | 2023-01-23 00:01:00 | identify | /signup | abc123 | 1001 | test@example.com | With **Identity Stitching**, Jitsu will reprocess events with `anonymous_id=abc123` and we will have the following table: | timestamp | type | path | anonymous_id | user_id | context_traits_email | |---------------------|----------|---------|--------------|---------|----------------------| | 2023-01-01 00:00:00 | page | /shop | abc123 | 1001 | test@example.com | | 2023-01-01 00:01:00 | page | /order | abc123 | 1001 | test@example.com | | 2023-01-23 00:00:00 | page | /signup | abc123 | 1001 | test@example.com | | 2023-01-23 00:01:00 | identify | /signup | abc123 | 1001 | test@example.com | ## How to enable You can enable Identity Stitching on the **Connection** editing page in the **Advanced** section: (For a self-hosted Jitsu, there are [additional requirements](http://localhost:4112/self-hosting/production-deployment#mongodb_url-optional). ) --- Source: https://jitsu.com/docs/features/live-events # Live Events Jitsu UI has a **Live Events** tool that allows you to see in real time: - Events coming from your site - [Functions](/docs/functions) execution logs - Data warehouse interaction statuses In each section you can select entities you want to see events for, date range and choose to show errors only. ## Error monitoring Option to show errors only is very useful for making sure that your functions are working correctly, and there are no errors while writing data to the warehouses. If something goes wrong, you can see error description in 'Events details' pane. --- Source: https://jitsu.com/docs/features/observability-exports # Observability Exports :::info Observability Exports is available on **Enterprise plans** of [Jitsu Cloud](https://use.jitsu.com) only. [Contact us](https://jitsu.com/contact) to enable it for your workspace. ::: ## What are Observability Exports? Everything you can see in [Live Events](/docs/features/live-events) — function logs, warehouse delivery statuses, sync results and processing errors — can be streamed to your own monitoring stack in near real time. Records are delivered as [OpenTelemetry](https://opentelemetry.io/) (OTLP) log records over HTTP, so they work with any OTLP-compatible backend: Datadog, Grafana Cloud, New Relic, Elastic, or your own OpenTelemetry Collector. Typical use cases: - Alert on delivery errors and failing [functions](/docs/functions) with the monitoring tools your team already uses - Keep operational logs beyond the Live Events retention window - Correlate Jitsu pipeline health with the rest of your infrastructure ## What is exported | Record type | Contents | | --- | --- | | `function` | [Functions](/docs/functions) execution logs — `log.info` / `log.warn` / `log.error` calls from your code | | `bulker_batch` | Data warehouse batch statuses: target table, processed rows, statistics, errors | | `bulker_stream` | Streaming-mode delivery results per event | | `dead-letter` | Events that could not be processed, with the error that caused it | The incoming event stream itself is not exported — Observability Exports carry operational logs about your pipelines, not your raw event traffic. Each log record is tagged with resource attribute `service.name: jitsu-live-events` and per-record attributes you can filter on: `jitsu.workspace.id`, `jitsu.live_event.type`, `jitsu.connection.id` or `jitsu.destination.id`, and `jitsu.message_id` where applicable. Log severity is mapped from the record's level. ## Setting it up Open **Settings → Observability exports** in your workspace: 1. Set the **endpoint** — your backend's OTLP/HTTP logs URL 2. Add **headers** for authentication. Header values are write-only: after saving they are masked and can be replaced but not read back 3. Press **Send test log** and check that the test record reached your backend 4. Toggle **Enabled** and save ### Example: Datadog Datadog accepts OTLP logs directly, no Agent or Collector needed: - Endpoint: `https://otlp.datadoghq.com/v1/logs` (host is site-specific — for example `https://otlp.us5.datadoghq.com/v1/logs` for the US5 site; use the one matching your Datadog site) - Header: `dd-api-key` with an **API Key** from your Datadog **Organization** settings After sending a test log, find it in Logs Explorer with the query `service:jitsu-live-events`. ### Datadog display notes - Records carry the full structured payload in the log body. The record's own status field is exported as `record_status` (Datadog reserves the `status` attribute for log severity), and records without a natural log message get a short synthesized one, e.g. `bulker_batch COMPLETED: 2 rows → events`. - Datadog's OTLP intake keeps top-level numeric fields typed (`processedRows`, `processingTimeSec`), but converts numbers nested deeper in the payload (for example `statistics.timeProcessedMs`) to strings. All values are present; add a pipeline processor in Datadog if you need typed facets on nested fields. ## Delivery semantics Records are delivered in batches with a delay of up to a minute. Failed deliveries are retried with at-least-once semantics, so your backend may occasionally receive duplicates — the `jitsu.live_event.id` attribute identifies them. Export delivery never affects event processing: if your endpoint is down, events keep flowing to their destinations. ## Billing Exported records count toward your plan's event volume, the same way incoming events do. The actual amount depends on how much logging your pipelines generate. --- Source: https://jitsu.com/docs/features/profiles # Profiles Create customer profiles records in your warehouse using Jitsu Profile Builder. :::info Profile builder is an enterprise feature. Please [contact support](mailto:support@jitsu.com) to enable it for your account. ::: ## What are Profiles? Profiles are customer records stored in your warehouse, based on the events data you send to Jitsu. Profile Builder generates profiles based on the `traits` object in `identify` events. You can also define custom logic for profile generation using a JavaScript function, allowing you to leverage up to a year’s worth of user events data. ## Configuring Profile Builder To set up Profile Builder, navigate to the `Customers` -> `Profile Builder` section in the Jitsu UI. Profile builder is an enterprise feature and initially will appear in a LOCKED state. Please [contact support](mailto:support@jitsu.com) to enable it for your account. :::info While in a locked state, Profile Builder will not create profiles in your warehouse. However, you can still configure it, debug your custom profile generation function, and preview profile results based on example events data. ::: Profile Builder configuration consists of the following sections: * **Profile Function** - code of a JavaScript function that generates profiles based on the events data. * **Transformation** - allows setting up a chain of functions to filter or transform events before they are used for building a profile. * **Environment Variables** - environment variables that can be used in your profile generation function. * **Settings** - destination and other settings. ### Events Sources All sites added to the workspace act as event sources for Profile Builder. By default, only non-anonymous events—those containing the `userId` field—are used to build profiles. With [Transformation](#transformation), you can exclude events from profile building or, conversely, assign a profile ID to events that originally lacked a userId field. ### Profile Function The Code section is where you define the JavaScript function that generates profiles based on the events data. Profile generation function has the following signature: ```javascript export default async function(events, user, context) { context.log.info("Profile Id: " + user.profileId) const profile = {} for (const event of events) { //count events by type profile[event.type] = (profile[event.type] ?? 0) + 1 } profile.anonId = user.anonymousId return { traits: profile } } ``` where: * `events` is an Iterable of events. Use the `for (const event of events)` loop to iterate over events. * `user` is a user object. It contains the following fields: * `profileId` - ID of profile * `userId` - user ID * `anonymousId` - anonymous ID * `traits` - user traits collected using built-in profile generation logic * `context` the function context. I contain various services that can be used in the function * `context.log` - [logging service](/docs/functions/runtime#logging) * `context.store` - [persistent storage](/docs/functions/runtime#persistent-storage) * `context.fetch` - [a standard fetch API](/docs/functions/runtime#fetch-api) to make HTTP requests * `context.getWarehouse` - [Warehouse API](/docs/functions/runtime#warehouse-api) to query your data warehouses * `context.profileBuilder` - contains meta information about the current Profile Builder: `id`, `version` **Return value** of the function is a profile object: ```typescript type ProfileResult = { profileId?: string; destinationId?: string; tableName?: string; traits: Record; } ``` where: * `profileId` - Allows to override the profile ID. If not specified, Jitsu will use profile ID assigned in [Transformation](#transformation) or `userId` by default. * `destinationId` - Allows to override the default destination. If not specified, Jitsu wil use the Default Destination from the Profile Builder settings. * `tableName` - Allows to override the default table name. If not specified, Jitsu will use the Default Table Name from the Profile Builder settings. * `traits` - object with profile properties. All custom profile properties should be placed in the `traits` property. Traits returned by the function will be merged with the traits collected using built-in profile generation logic. #### SDK It is also possible to work on Profile Builder function code in your IDE and then sync it with Jitsu. [Jitsu SDK](/docs/functions/sdk) supports Profile Builder functions.
Profile Builder function should be located in the `src/profiles` directory and configured with the `profileBuilderId` config property within the function code.
Id of Profile Builder can be obtained from the `Settings` tab. ```typescript import { ProfileFunction } from "@jitsu/protocols/profile"; export const config = { profileBuilderId: "[Profile Builder ID]", // Required: id of Profile Builder where this function will be used. Can be found in the Profile Builder Settings UI slug: "profile.ts", //id (uniq per workspace) used to identify function in Jitsu }; const profileExample: ProfileFunction = async (events, user, { log }) => { //TODO } ``` ### Transformation Transformation allows you to filter or transform events before they are used for building a profile. In the Transformation section you can set up the chain of standard Jitsu [functions](/docs/functions) to process events before they are passed to the Profile Builder function. Use `return "drop"` to exclude an event from profile building. #### Custom Profile ID Using transformation, you can assign a profile ID to events that originally lacked a `userId` field or had a different `userId` field. To assign a profile ID to an event, add `JITSU_PROFILE_ID` property to the returned event object: ```javascript export default async function transform(event, { log, props, store }) { event.JITSU_PROFILE_ID = event.propeties.internalId; return event; } ``` ## Publishing Profile Builder Collecting of customers events history and profile-building logic starts after the initial version of Profile Builder is published. To publish the Profile Builder, click the `Publish` button in the top right corner of the Profile Builder editor. :::tip You can continue to work on your profile generation function after the initial version is published by utilizing the **Drafts**. Saved Drafts can be used for debugging and previewing profile results based on example events data and don't affect the production profile generation process. ::: Each time you publish a new version of the Profile Builder, the following happens: 1. Profile Builder's numeric `version` is incremented. 2. Profile Builder starts rebuilding profiles of all tracked customers based on the new version of the profile generation function.
This process may take some time depending on the number of customers and events and can be monitored on the `Build Progress` tab. 3. After the rebuild is complete, the new version of the profile generation function is used to rebuild profiles in real-time based on new events coming. --- Source: https://jitsu.com/docs/features/provisioned-warehouse # Provisioned ClickHouse [Jitsu.Cloud](https://use.jitsu.com) comes with preconfigured ClickHouse data warehouse. It is ready to use and requires no additional setup. ## Query Runner All Jitsu.Cloud plans (including a free one) allows to run SQL queries against ClickHouse data warehouse without any additional setup. --- Source: https://jitsu.com/docs/features/schema # Automatic Schema Management Jitsu automatically managers a schema for [data warehouse destinations](/docs/category/warehouses). Based in incoming event JSON, it creates a table with columns that correspond to JSON properties. It's done in a few steps: #### Flatenning ```json { "userProperties": { "id": 1, "name": "John" } } ``` becomes ```json { "user_properties_id": 1, "user_properties_name": "John" } ``` In addition to flatenning, jitsu converts `camelCase` name into `camel_case`. Then Jitsu make sure that colums `user_id` and `user_name` are created in the table. ```sql ALTER TABLE user_id ADD COLUMN user_properties_id INT; ALTER TABLE events ADD COLUMN user_properties_name TEXT; ``` Jitsu selects the most appropriate column type for specific Data Warehouse based on JSON data type. User can override that selection using [SQL column type override](/docs/functions/advanced#sql-column-type-override) function, see details below. ## Customization Certain aspects of schema management can be customized in Jitsu UI. ### Primary Key selection When Jitsu creates a table in Data Warehouse, it must know a primary key column that identifies each row. By default, Jitsu uses `message_id` column as primary key. `messageId` (transformed to `message_id`) is root field of every event payload and is guaranteed to be unique. ### Timeseries Database Optimization Jitsu can create destination tables optimized for time series queries. Concrete implementation will depend on [Data Warehouse](/docs/category/warehouses) you use. Some datawarehouses, like ClickHouse or BigQuery, have special column types for time series data. By default, Jitsu uses `timestamp` column for that purpose. This column is a root field of every event payload and is guaranteed to be present. It's generated by Jitsu SDK and equals to the time when event was sent to Jitsu. Alternatively, users can set the value explicitly ### Schema Freeze Sometimes, it's not desirable to let Jitsu add additional columns to the table. For example, you might not want to store all properties of incoming JSON in the table. Inf this case you can create all necessary columns in the destination table ahead of time and enable Schema Freeze in Jitsu UI. Incoming data for any properties that don't have corresponding columns will be stored in `_unmapped_data` column in JSON format. ### SQL column type override Sometimes, Jitsu might select a column type that is not optimal for your use case. You can override Jitsu default type selection for specific column using Functions. When creating a new column, Jitsu tries its best to pick an appropriate column type based on the data type of the incoming JSON. However, sometimes it's not going to work. For example, if the fist value of the column is an Integer, Jitsu will create an `Integer` column. But if the second value is a `Double`, Jitsu will fail to insert it into the Integer column. You can override Jitsu default type selection for specific column using Functions. Please refer to [SQL column type override](/docs/functions/advanced#sql-column-type-override) for detailed explanations Functions documentation: Example: ```javascript export default async function(event, ctx) { return { ...event, properties: { ...event.properties, __sql_type_number: "DOUBLE PRECISION", }, } } ``` ## Column Number Limitations :::note This feature is under development and not available yet in the latest release ::: Jitsu will inflate table column up to the point when it reaches `1500` columns. After that all properties that exceed that limit will be stored in `_unmapped_data` column in JSON format. ## Unmapped Data If Jitsu won't able to store data in the table by any reason, it will store it in `_unmapped_data` column in JSON format. --- Source: https://jitsu.com/docs/features/sessions # Sessions Jitsu does not count sessions for every event by default because there is no universal definition of a session that applies to all use cases. Instead, Jitsu provides a flexible way to define sessions using [Jitsu Functions](/docs/functions). Below is an example function that adds a `session_id` field to each event: ```javascript import { randomUUID } from 'crypto'; //adjust as needed, see https://www.npmjs.com/package/parse-duration#available-unit-types-are for available values const maxInactivityPeriod = '1h' export default async function(event, { log, store }) { const userId = event.userId || event.anonymousId; if (userId) { const storeKey = `session_id::${userId}`; const newSessionId = `s_${randomUUID()}`; // get existing session id or set new one if there is no active session for user const activeSessionId = await store.getOrSet(storeKey, newSessionId, maxInactivityPeriod) if (activeSessionId === newSessionId) { log.info(`Assigned new session id to ${userId} -> ${activeSessionId}`); } else { log.debug(`Using existing session id for ${userId} -> ${activeSessionId}`); } if (!event.properties) { //sometimes the node does not exist event.properties = {} } event.properties.sessionId = activeSessionId; } return event; } ``` --- Source: https://jitsu.com/docs/jitsu-cli # Jitsu CLI `jitsu-cli` is the command-line interface for Jitsu. It manages workspace configuration objects (destinations, streams, connections, …) and runs the [Functions](/docs/functions) dev workflow. The CLI calls the same [Management API](/docs/api) the Jitsu UI uses. ## Install ```bash npm i -g jitsu-cli ``` Verify: ```bash jitsu-cli --version ``` ## First steps ```bash jitsu-cli login # log in once jitsu-cli set-default-workspace my-workspace # optional — saves you typing -w on every command jitsu-cli config destinations list # uses the saved workspace ``` See [Authentication](/docs/jitsu-cli/auth) for login options and [Config](/docs/jitsu-cli/config) for managing configuration objects. ## Command groups | Command | Description | |---|---| | [`login` / `logout` / `whoami`](/docs/jitsu-cli/auth) | Authenticate and inspect session | | [`set-default-workspace` / `unset-default-workspace`](/docs/jitsu-cli/auth#default-workspace) | Pin a workspace so `-w` becomes optional | | [`config `](/docs/jitsu-cli/config) | Manage workspaces, destinations, streams, services, functions, connections, … | | `init` / `build` / `test` / `deploy` | Functions extension dev workflow — see [Functions SDK](/docs/functions/sdk) | ## Global options Most commands accept these: - `-h, --host ` — Jitsu host (default `https://use.jitsu.com`). Overrides the saved auth file. - `-k, --apikey ` — API key. Overrides the saved auth file. - `-w, --workspace ` — Target workspace. Falls back to the default workspace if set. - `-o, --output ` — Output format for read commands (e.g. `json`, `yaml`, `table`). When `--host` and `--apikey` are both omitted, the CLI uses the credentials saved by `jitsu-cli login`. --- Source: https://jitsu.com/docs/jitsu-cli/auth # Authentication Most CLI commands need a Jitsu host and an API key. Either pass them on every invocation (`-h`, `-k`) or save them once with `login`. ## `login` Saves credentials so subsequent commands don't need `-h` / `-k`. ```bash jitsu-cli login # interactive jitsu-cli login -h https://use.jitsu.com -k keyId:secret jitsu-cli login -f # overwrite an existing session ``` Generate API keys in the Jitsu UI on the [user settings page](https://use.jitsu.com/user). The key has the format `{keyId}:{keySecret}` — the secret is shown once at creation, copy it then. ## `logout` ```bash jitsu-cli logout jitsu-cli logout -f # do not ask for confirmation ``` ## `whoami` Verifies the saved (or supplied) credentials and prints the user info. ```bash jitsu-cli whoami jitsu-cli whoami -k keyId:secret ``` ## Default workspace Most `config` commands take `-w `. To avoid passing it every time, save a default: ```bash jitsu-cli set-default-workspace my-workspace-slug jitsu-cli unset-default-workspace ``` When set, any `config` command without `-w` uses this workspace. Per-command `-w` still wins. --- Source: https://jitsu.com/docs/jitsu-cli/config # Manage configuration The `config` command manages workspace configuration objects — the same things you create in the Jitsu UI. Under the hood it calls `/api/{workspaceId}/config/{type}` (see [Management API](/docs/api)). ## Two equivalent invocation styles Both forms produce identical results — pick whichever reads better: ```bash jitsu-cli config [args] # noun-first jitsu-cli config [args] # verb-first ``` ```bash jitsu-cli config destinations list -w my-ws jitsu-cli config list destinations -w my-ws ``` ## Resources | Noun | Aliases | Kind | |---|---|---| | `workspaces` | `workspace` | Workspace | | `destinations` | `destination`, `dest` | Config object | | `streams` | `stream` | Config object | | `functions` | `function`, `fn` | Config object | | `services` | `service` | Config object — Airbyte connectors | | `domains` | `domain` | Config object | | `misc` | — | Config object — free-form | | `notifications` | `notification` | Config object | | `connections` | `connection`, `link`, `links` | Connection (link) | | `profile-builders` | `profile-builder` | Profile builder | ## Verbs | Verb | Applies to | Notes | |---|---|---| | `list` | All | | | `get ` | Config objects, workspaces | Connections have no per-id GET; use `list` | | `create` | All | Body via `-f`, `--json`, or `--field=value` | | `update ` | All | Deep-merges into the existing object | | `delete ` | All | Aliased as `rm`. Config objects accept `--cascade` and `--strict` | | `test` | `destinations`, `streams`, `services` | Test connectivity for a config | ## Providing the body `create`, `update`, and `test` need a body. There are three sources, merged in this order: file → JSON → ad-hoc field flags. **From a file** (YAML or JSON, `-` for stdin): ```bash jitsu-cli config destinations create -f ./dest.yaml -w my-ws cat dest.json | jitsu-cli config destinations create -f - -w my-ws ``` **Inline JSON:** ```bash jitsu-cli config streams create --json '{"name":"website","domain":"example.com"}' -w my-ws ``` **Ad-hoc field flags** (any `--=`): ```bash jitsu-cli config destinations create -w my-ws \ --name=warehouse \ --destinationType=postgres \ --credentials.host=db.example.com \ --credentials.port=5432 \ --credentials.password=secret ``` Values that start with `[`, `{`, `"`, or look like a number / boolean / null are parsed as JSON. Everything else is a plain string. Combine sources to override file defaults from the command line: ```bash jitsu-cli config destinations create -f ./template.yaml --credentials.host=prod-db ``` ## Connection-specific options `connections delete` lets you identify a link either by id or by endpoints: ```bash jitsu-cli config connections delete -w my-ws jitsu-cli config connections delete --from --to -w my-ws ``` `connections update` accepts an optional id — if omitted, the link is identified by `fromId` + `toId` in the body (upsert semantics). ## Config object delete options ```bash jitsu-cli config destinations delete -w my-ws --cascade # also delete linked connections jitsu-cli config destinations delete -w my-ws --strict # refuse if linked connections exist ``` ## Output formats ```bash jitsu-cli config destinations list -w my-ws -o json # default depends on command jitsu-cli config destinations get -w my-ws -o yaml jitsu-cli config destinations list -w my-ws -o table ``` Pipe to `jq` for further filtering — the decorative banner is written to stderr, so `... | jq '.[].name'` works without filtering. --- Source: https://jitsu.com/docs/self-hosting # Self-hosting There are several ways to use Jitsu: * [Jitsu.Cloud](https://use.jitsu.com) - a hosted version of Jitsu. We manage the infrastructure, it's free for up to 100k events per month * Jitsu is 100% open-source and can be deployed to any on-premise infrastructure. You can find the instructions below. A **feature-complete Jitsu deployment requires a Kubernetes cluster**: functions and profile builders run in dedicated function-server deployments managed by the Jitsu operator, and connector syncs run as Kubernetes CronJobs. See the [Production Deployment](/docs/self-hosting/production-deployment) guide for details. The recommended way to run Jitsu locally is the [development Helm chart](https://github.com/jitsucom/jitsu/tree/newjitsu/helm) shipped in the Jitsu repository — it deploys the full Kubernetes architecture (including the operator and function servers) to Minikube. See the [Quick Start](/docs/self-hosting/quick-start) guide. The legacy Docker Compose setup is **deprecated** in favor of the development Helm chart and will be removed in a future release — it cannot run functions, profile builders or connector syncs. --- Source: https://jitsu.com/docs/self-hosting/quick-start # Quick Start :::info This guide covers running Jitsu locally for exploration and development. For production deployments, please see [Production Deployment](/docs/self-hosting/production-deployment) guide. ::: Since Jitsu 2.14, a feature-complete Jitsu requires Kubernetes: functions and profile builders run on function servers managed by the [Jitsu operator](/docs/self-hosting/production-deployment#functions-and-the-jitsu-operator), and [connector syncs](https://jitsu.com/integrations/connectors) run as Kubernetes CronJobs. There are two ways to run Jitsu locally: * **Development Helm chart** *(recommended)* — deploys the full Kubernetes architecture to Minikube, including the operator and function servers. * **Docker Compose** *(deprecated)* — starts the core services without Kubernetes. Functions, profile builders and connector syncs are not available. It will be removed in a future release. ## Development Helm chart (recommended) The Jitsu repository ships development Helm charts ([`helm/`](https://github.com/jitsucom/jitsu/tree/newjitsu/helm) for the services, [`helm-deps/`](https://github.com/jitsucom/jitsu/tree/newjitsu/helm-deps) for the dependencies) that deploy the full stack to [Minikube](https://minikube.sigs.k8s.io/docs/start/) with **zero configuration**: all Jitsu services — console, ingest, bulker, rotor, syncctl, the operator and function servers — plus single-node PostgreSQL, ClickHouse, MongoDB and Kafka (Redpanda) in-cluster. Services are built inside init containers from the repository sources, so no local build step is required, and no host services are needed. **Requirements** * [Git](https://git-scm.com/downloads) * [Minikube](https://minikube.sigs.k8s.io/docs/start/) and [Helm](https://helm.sh/docs/intro/install/) v3+ * Give the Minikube VM at least 8 GB of memory: `minikube config set memory 8192` (applies when the cluster is created) **Steps** ```shell # Clone the repository git clone -b newjitsu --single-branch https://github.com/jitsucom/jitsu cd jitsu/helm # 1. Start minikube minikube start # 2. Deploy: dependencies first (waits until they are healthy), then the services. # Secrets are generated automatically; the console DB schema is applied on every # deploy; a checkmark is printed as each component becomes ready. ./dev-deploy.sh deploy # 3. Start tunnel for localhost access (in a separate terminal) ./dev-deploy.sh tunnel ``` To use an external instance of a dependency, set `enabled: false` for it in `helm-deps/values.yaml` and point the matching `env.common.KAFKA_BOOTSTRAP_SERVERS` / `DATABASE_URL` / `CLICKHOUSE_URL` / `MONGODB_URL` of the main chart at it. See [`helm/README.md`](https://github.com/jitsucom/jitsu/blob/newjitsu/helm/README.md) for configuration, per-service scaling and troubleshooting. ## Docker Compose (deprecated) :::warning The Docker Compose setup is **deprecated in favor of the development Helm chart** and will be removed in a future release. It does not run the operator or function servers, so functions, profile builders and connector syncs are not available with it. ::: **Requirements** * [Git](https://git-scm.com/downloads) * [Docker Engine](https://docs.docker.com/engine/install/) >= 19.03.0 **Run** ```shell # Clone the repository git clone -b newjitsu --single-branch --depth 1 https://github.com/jitsucom/jitsu cd jitsu docker compose -f ./docker/docker-compose.yml up --force-recreate ``` This starts the core Jitsu services along with their dependencies (PostgreSQL, MongoDB, ClickHouse and Redpanda), plus a few admin UIs (Redpanda Console, PgWeb, Mongo Express). Once running, open [http://localhost:3000](http://localhost:3000) in your browser and login with `admin@jitsu.com` / `admin123`. Data ingestion instructions are available right in the UI. | Service | URL | |---------|-----| | Console (UI) | http://localhost:3000 | | Ingest (events API) | http://localhost:3049 | | Bulker | http://localhost:3042 | | Rotor | http://localhost:3401 | | Syncctl (optional, requires Kubernetes) | http://localhost:3043 | To start only the third-party dependencies (e.g. to run Jitsu services from your IDE): ```shell docker compose -f ./docker/docker-compose.yml --profile jitsu-dependencies up --force-recreate ``` ### Configuration Configuration is overridden via environment variables or a `docker/.env.local` file (do not edit `docker/.env` — it holds the defaults). Commonly changed variables: Initial admin user credentials. The very first user is created with those credentials. Please change the password right after first login. Creates a demo stream and destination on first start. Docker image tag of Jitsu services to run. URL where Jitsu Ingest is reachable by the clients sending events. When Jitsu is deployed on a remote server, set it to your server URL, e.g.: {'http://your-domain:3049'}. Passwords of the bundled dependency services. See docker/README.md for the defaults. Secrets used for internal communication between Jitsu components. Generate random values if the installation is reachable from outside your machine. Use GitHub OAuth instead of (or in addition to) password auth. Set to true if you don't want to allow new users to sign up. See the full list of variables in `docker/README.md` and in the [Configuration Reference](/docs/self-hosting/configuration). ### Connector syncs with Docker Compose Sync jobs require a Kubernetes cluster even in the compose setup — a cluster can be attached via the `syncctl` service, see [Connector Syncs](/docs/self-hosting/quick-start/syncs). --- Source: https://jitsu.com/docs/self-hosting/quick-start/syncs # Connector Syncs [Syncs](https://jitsu.com/integrations/connectors) rely on a Kubernetes cluster: each sync run executes as a Kubernetes pod, and scheduled syncs are managed as Kubernetes **CronJobs** created and reconciled by the `syncctl` service. :::warning This page applies to the **deprecated Docker Compose setup**. With the recommended [development Helm chart](https://github.com/jitsucom/jitsu/tree/newjitsu/helm), `syncctl` runs inside the cluster and no extra configuration is needed. ::: The setup below attaches a cluster to the compose stack for testing. ## How to set up Connect the `syncctl` service to a Kubernetes cluster with the following variables in `docker/.env.local`: * `SYNCS_ENABLED` - set to `true` to enable syncs in the UI * `SYNCCTL_KUBERNETES_CLIENT_CONFIG` - kubeconfig of the cluster: either a path to a kubeconfig file mounted into the container, or the kubeconfig content itself in YAML format. The special value `local` (default) means in-cluster config and only works when syncctl itself runs inside Kubernetes. * `SYNCCTL_KUBERNETES_CONTEXT` - (optional) kubeconfig context to use, if not the current one Without `SYNCCTL_KUBERNETES_CLIENT_CONFIG` set, the `syncctl` container exits gracefully and the rest of the stack works without syncs. ## Minikube example **Pre-requisites** * `minikube` [installed](https://minikube.sigs.k8s.io/docs/start/) * `kubectl` [installed](https://kubernetes.io/docs/tasks/tools/) **Start minikube** ```shell minikube start ``` **Get the kubeconfig** ```shell kubectl config view --raw=true --minify=true --flatten=true ``` Copy the output into the `SYNCCTL_KUBERNETES_CLIENT_CONFIG` variable in `docker/.env.local` (keep the YAML indentation intact: use a line break right after the opening quotation mark). Note that the API server address in the kubeconfig must be reachable from inside the syncctl container — with minikube on the same machine, replace `127.0.0.1` with `host.docker.internal`. **Adjust `docker/.env.local`** ```bash SYNCS_ENABLED=true SYNCCTL_KUBERNETES_CLIENT_CONFIG=" apiVersion: v1 kind: Config ... " ``` **Start Jitsu** ```shell docker compose -f ./docker/docker-compose.yml up --force-recreate ``` ## After it's running The `Connectors` menu item appears in the Jitsu Console UI. Scheduled syncs materialize as CronJobs named `sync-` (labeled `jitsu.com/managed-by=syncctl`) in the configured namespace; ad-hoc runs are started as regular pods. To fill the connectors catalog, trigger the `catalog-refresh` console API endpoint in a browser with an authorized Jitsu user: ``` http://localhost:3000/api/admin/catalog-refresh ``` --- Source: https://jitsu.com/docs/self-hosting/production-deployment # Production Deployment Guide :::tip This guide is intended for production deployments. For local exploration and development, please see the [Quick Start](/docs/self-hosting/quick-start) guide — the recommended path is the development Helm chart (the legacy Docker Compose setup is deprecated). ::: :::warning Starting with Jitsu 2.14, a **feature-complete production deployment requires a Kubernetes cluster**: * [Functions](/docs/functions) and profile builders run on dedicated **function servers** provisioned by the **Jitsu operator** as Kubernetes deployments. The operator is a required part of the event pipeline — Rotor routes events based on the function-server registry the operator maintains. * [Connector syncs](https://jitsu.com/integrations/connectors) run as Kubernetes pods, and scheduled syncs are managed as Kubernetes **CronJobs**. ::: ## Components and services Jitsu consists of several components and services. The main components are: * **🔄 Console** is responsible for management UI. Console is written in Next.js. Resources: [`jitsucom/console` (docker image)](https://hub.docker.com/r/jitsucom/console), [source code](https://github.com/jitsucom/jitsu/tree/newjitsu/webapps/console), [configuration reference](/docs/self-hosting/configuration#console) * **🕸️ Ingest** provides [events ingestion API](/docs/sending-data). Ingest is written in Go. Resources: [`jitsucom/ingest` (docker image)](https://hub.docker.com/r/jitsucom/ingest), [source code](https://github.com/jitsucom/jitsu/tree/newjitsu/bulker/ingest), [configuration reference](/docs/self-hosting/configuration#ingest) * **⚙️ Rotor** consumes events from Kafka, runs the event pipeline and dispatches events to destinations. Rotor is written in Node.js. Resources: [`jitsucom/rotor` (docker image)](https://hub.docker.com/r/jitsucom/rotor), [source code](https://github.com/jitsucom/jitsu/tree/newjitsu/services/rotor), [configuration reference](/docs/self-hosting/configuration#rotor) * **λ Function Servers** run user [functions](/docs/functions) and profile builders. A function server is the [`jitsucom/functions-server` (docker image)](https://hub.docker.com/r/jitsucom/functions-server) started with `ROTOR_MODE=functions`. Function servers are **not deployed manually** — they are provisioned per workspace (or shared for the `free` class) by the **Operator**. * **🤖 Operator** is a Kubernetes controller that provisions and scales function-server deployments, services, config maps, autoscalers and disruption budgets, and maintains the `FunctionsServer` routing table in Postgres. Written in Go. Resources: [`jitsucom/operator` (docker image)](https://hub.docker.com/r/jitsucom/operator), [source code](https://github.com/jitsucom/jitsu/tree/newjitsu/bulker/operator), [configuration reference](/docs/self-hosting/configuration#operator) * **🚚 Bulker** is responsible for batching and sending events to the warehouse destinations. Bulker is written in Go. Resources: [`jitsucom/bulker` (docker image)](https://hub.docker.com/r/jitsucom/bulker), [source code](https://github.com/jitsucom/jitsu/tree/newjitsu/bulker), [configuration reference](https://github.com/jitsucom/jitsu/blob/newjitsu/bulker/.docs/server-config.md) * **🔌 Syncctl** is responsible for running [Jitsu Connectors](https://jitsu.com/integrations/connectors) on a Kubernetes cluster: it schedules recurring syncs as Kubernetes CronJobs and runs ad-hoc sync tasks as pods. Resources: [`jitsucom/syncctl` (docker image)](https://hub.docker.com/r/jitsucom/syncctl), [source code](https://github.com/jitsucom/jitsu/tree/newjitsu/bulker/sync-controller), [configuration reference](/docs/self-hosting/configuration#syncctl). * Syncctl is *optional*. It is required only if you want to use the [Jitsu Connectors](https://jitsu.com/integrations/connectors) sync feature. **Syncctl requires a Kubernetes cluster.** :::info All Jitsu services are: * Available as Docker images (built together from the [jitsucom/jitsu](https://github.com/jitsucom/jitsu) monorepo) * Stateless and can be scaled horizontally * Configured with environment variables exclusively ::: In addition to internal services, Jitsu has a number of 3rd-party dependencies: * [PostgreSQL](https://www.postgresql.org/) - for configuration and metadata storage * [Kafka](https://kafka.apache.org/) (or [Redpanda](https://redpanda.com/)) - the main transport layer for sending events between components * [ClickHouse](https://clickhouse.com/) - stores the events log and metrics * [MongoDB](https://www.mongodb.com/) - required for [Persistent Storage](/docs/functions/runtime#persistent-storage), [Identity Stitching](/docs/features/identity-stitching) and profile builders * [Redis](https://redis.io/) - optional; supported as an alternative functions state store ### Service Diagram See the diagram below for a better understanding of the architecture: ## Functions and the Jitsu Operator Since Jitsu 2.14, user functions and profile builders do not run inside Rotor. They run on dedicated **function servers** — Kubernetes deployments managed by the **Operator** service: 1. The Operator polls the Console export API (`OPERATOR_REPOSITORY_BASE_URL` → `https://$console/api/admin/export`) for workspaces, connections and functions. 2. For each workspace (functions class `dedicated`) or shard of workspaces (class `free`), it creates a `fs-` Deployment + Service on port `3456`, config maps with the workspace's functions, an optional HorizontalPodAutoscaler and a PodDisruptionBudget. 3. Once a deployment is rolled out, the Operator records it in the `FunctionsServer` table in Postgres. The Console export uses this table to tell Ingest and Rotor which function server serves each connection. 4. Ingest, Rotor and Console reach function servers by URL template — the `FUNCTIONS_SERVER_URL_TEMPLATE` env var, default `http://fs-${workspaceId}:3456`, which matches the services the Operator creates. **The Operator is required for event delivery**: connections without function-server routing information are dropped by Rotor. Deploy the Operator even if you don't use custom functions. The Operator needs Kubernetes RBAC permissions for: `pods`, `services`, `configmaps`, `secrets`, `apps/deployments`, `apps/statefulsets`, `autoscaling/horizontalpodautoscalers` and `policy/poddisruptionbudgets`. See the [configuration reference](/docs/self-hosting/configuration#operator). ## Enabling Jitsu Connectors [Jitsu Connectors](https://jitsu.com/integrations/connectors) require a Kubernetes cluster. **🔌 Syncctl** is the service responsible for running connector sync jobs. It is enough for `syncctl` to have a valid kubeconfig of a Kubernetes cluster — the other Jitsu components don't have to run in the same cluster. Scheduled syncs are materialized as Kubernetes **CronJobs** (named `sync-`, labeled `jitsu.com/managed-by=syncctl`): syncctl polls the Console's `/api/admin/export/syncs` endpoint and reconciles the CronJobs to match the configured schedules. Sync runs execute as autonomous pods. ### How to enable To enable connector sync support, [configure](/docs/self-hosting/configuration#syncctl) the `syncctl` service — including `SYNCCTL_REPOSITORY_BASE_URL` / `SYNCCTL_REPOSITORY_AUTH_TOKEN`, which drive the CronJob scheduling. For the `console` service set `SYNCS_ENABLED`, `SYNCCTL_URL` and `SYNCCTL_AUTH_KEY` env variables. After starting all Jitsu components, you will see the `Connectors` menu item in the top bar of the Jitsu Console UI. To fill the connectors catalog, trigger the `catalog-refresh` console API endpoint in a browser with an authorized Jitsu user: ``` http://JITSU_PUBLIC_URL/api/admin/catalog-refresh ``` ## Running Jitsu on a Kubernetes cluster Running all Jitsu components in a Kubernetes cluster is the recommended production setup — this is how [Jitsu Cloud](https://use.jitsu.com) runs (on [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine)), and it's the only setup where all features (functions, profile builders, connector syncs) are available. There is no official production Helm chart yet: * The Jitsu repository ships a [development Helm chart](https://github.com/jitsucom/jitsu/tree/newjitsu/helm) that deploys development builds of all services (including the Operator and function servers) to Minikube. It's the best reference for how the full Kubernetes architecture fits together, but it is not intended for production use. * A community-driven chart is available at [stafftastic/jitsu-chart](https://github.com/stafftastic/jitsu-chart); note it may not yet cover the Operator/function-server architecture introduced in Jitsu 2.14. ## Configuration Reference See [Configuration Reference](/docs/self-hosting/configuration) for the list of all environment variables and their descriptions, and use the [development Helm chart](https://github.com/jitsucom/jitsu/tree/newjitsu/helm) as a reference for service wiring. --- Source: https://jitsu.com/docs/self-hosting/configuration # Configuration Reference Each service is configured via environment variables. Please see a GitHub readme section for each service for the full list of configuration options: ## Ingest HTTP port where Ingest will be available. Public url where ingest service is deployed and available from internet, usually it's load balancer or reverse proxy. E.g. https://data.jitsu.mycompany.com. Should contain protocol and port (if it's not default).

INGEST_REPOSITORY_URL is an URL of console's export endpoint that returns configuration of `streams-with-destinations` entities: https://$console-endpoint/api/admin/export/streams-with-destinations.

INGEST_REPOSITORY_AUTH_TOKEN is used to authorize request to console. It must start with service-admin-account: prefix. E.g.: service-admin-account:console-token

See also CONSOLE_AUTH_TOKENS of console configuration

Period in seconds for refreshing configuration from console's INGEST_REPOSITORY_URL. List of Kafka brokers separated by comma. Each broker should be in format host:port. If SSL should be enabled for Kafka Skip SSL verification of kafka server certificate. Kafka authorization as JSON object. E.g.: {'{"mechanism": "SCRAM-SHA-256", "username": "user", "password": "password"}'}

URL template of function servers managed by the Operator. The default value matches the Kubernetes services the Operator creates. Used for running functions of device destinations. Replaces INGEST_ROTOR_URL of previous Jitsu versions.

Used to authorize HTTP-requests to function servers.
See ROTOR_AUTH_TOKENS, ROTOR_TOKEN_SECRET and ROTOR_RAW_AUTH_TOKENS in the Rotor section

Functions class assumed for workspaces without an explicit setting. See the Operator section. Timeout in milliseconds for running functions of device destinations synchronously during ingestion. Maximum allowed size of an ingested payload in bytes. ClickHouse host and port to store incoming events log. E.g.: clickhouse.example.com:9440 ClickHouse database to store incoming events log. ClickHouse username and password. Enable SSL for Clickhouse connection Format of application logs (that are written to stdout). Possible values: `text` or `json` Failover logger is used to store events that failed to be sent to Kafka. Default values:
  • INGEST_FAILOVER_LOGGER_ENABLED - false
  • INGEST_FAILOVER_LOGGER_ROTATION_PERIOD_MINUTES - 60
  • INGEST_FAILOVER_LOGGER_MAX_SIZE_MB - 100
  • INGEST_FAILOVER_LOGGER_COMPRESS - true
Controls how failover logger files stored on local disk. Default values:
  • INGEST_FAILOVER_LOGGER_BASE_PATH - /tmp/kafka_failover
  • INGEST_FAILOVER_LOGGER_LOCAL_MAX_OLD_FILES - 10
Allows to set S3 destination for failover logger files.
## Bulker See list of all options on [Bulker github](https://github.com/jitsucom/bulker/blob/main/.docs/server-config.md) ## Rotor Rotor runs the `jitsucom/rotor` image with `ROTOR_MODE=rotor` (the default): it consumes events from Kafka and runs the event pipeline. Function servers run the separate `jitsucom/functions-server` image with `ROTOR_MODE=functions` — they are deployed automatically by the [Operator](#operator), not manually. HTTP port where Rotor will be available. Role of this instance: rotor (Kafka consumer, the main event pipeline), functions (function server — set by the Operator) or profiles (profile builder server). URL template of function servers managed by the Operator. Rotor calls function servers to execute user functions and profile builders. Timeout in milliseconds for requests to function servers. Timeout in milliseconds for fetch() calls made from user functions.

ROTOR_AUTH_TOKENS is a list of hashed auth tokens that authorizes user in HTTP interface separated by comma. Each must have format: $salt.$hash where $salt should be random string. Hash is hex(sha512($token + $salt + ROTOR_TOKEN_SECRET).

To hash token, use following command in the root of this repository: pnpm install && ROTOR_TOKEN_SECRET=xxxx pnpm tool:hash $token

ROTOR_RAW_AUTH_TOKENS can be used instead of ROTOR_AUTH_TOKENS to provide a comma-separared list of raw takens instead of hases. It offers simplicity at cost of lower security.

REPOSITORY_BASE_URL is an URL of console's export endpoint that returns configuration of `streams-with-destinations` entities: https://$console-endpoint/api/admin/export.

REPOSITORY_AUTH_TOKEN is used to authorize request to console. It must start with service-admin-account: prefix. E.g.: service-admin-account:console-token

See also CONSOLE_AUTH_TOKENS of console configuration

Period in seconds for refreshing configuration from console's export endpoint. ClickHouse host and port to store Events Log. E.g.: clickhouse.example.com:9440 Only HTTP(s) protocol is supported. ClickHouse database to store Events Log. ClickHouse username and password. Enable SSL for Clickhouse connection

BULKER_URL is an URL of Bulker service. Rotor will use it to send event intended to warehouse destinations

BULKER_AUTH_KEY is user to authentificated HTTP-request to Bulker. Should be one of{" "} BULKER_AUTH_KEYS you configured in Bulker

Used for Events Logs feature.
List of Kafka brokers separated by comma. Each broker should be in format host:port. If SSL should be enabled for Kafka Custom CA certificate for verifying the Kafka server certificate. KAFKA_SSL_CA is a certificate in PEM format, KAFKA_SSL_CA_FILE is a path to the file with the certificate. Skip SSL verification of kafka server certificate. Kafka authorization as JSON object. E.g.: {'{"mechanism": "SCRAM-SHA-256", "username": "user", "password": "password"}'}

MongoDB is used for Functions Persistent Storage and Identity Stitching.

The value starts with mongodb:// and has the following format: mongodb://$user:$password@$host:$port/$database

To smoothly migrate from REDIS_URL you need to set REDIS_URL and MONGODB_URL at the same time. Rotor will look for records both in MongoDB and Redis, but new records will be added only to MongoDB. When you are sure that MongoDB is populated with enough data, you can remove REDIS_URL from the configuration.

Redis connection string. Always start with redis://: redis://$user:$password:localhost:6379.
Redis Sentinel Address: `sentinel1:26379,sentinel2:26379,sentinel3:26379`
Can be used for Functions Persistent Storage as alternative to MongoDB.
Id of Bulker destination where Rotor will send event metrics.{" "} Only clickhouse destination is supported.{" "} If you don't want to send metrics, you can skip this option. Number of events processed concurrently by each consumer. Maximum number of retries for failed messages. Base value for exponential backoff for failed messages.{" "} For example, if MESSAGES_RETRY_COUNT is 3 and base is 10, then retry delays will be 10, 100, 1000 minutes Defines maximum possible retry delay in minutes. Default: 24 hours MaxMind database used for GeoIP enrichment.{" "} MaxMind database can be obtained from MaxMind servers using MAXMIND_LICENSE_KEY, from custom `MAXMIND_URL` or from S3 or compatible storage. Use localized geographic names.{" "} Supported locales Format of rotor application logs. Possible values: text or json
## Console HTTP port where Console will be available. This is a URL where Jitsu console will be publicly available, usually a load-balancer / reverse proxy address. E.g.: https://your-domain/

CONSOLE_AUTH_TOKENS is a list of hashed auth tokens that authorizes user in HTTP interface separated by comma. Each must have format: $salt.$hash where $salt should be random string. Hash is hex(sha512($token + $salt + CONSOLE_TOKEN_SECRET).

To hash token, use following command in the root of this repository: pnpm install && CONSOLE_TOKEN_SECRET=xxxx pnpm tool:hash $token

CONSOLE_RAW_AUTH_TOKENS can be used instead of CONSOLE_AUTH_TOKENS to provide a comma-separared list of raw takens instead of hases. It offers simplicity at cost of lower security.

PostgreSQL connection string. postgres://$user:$password@localhost:$port/database?sslmode=no-verify&schema=newjitsu

schema must be newjitsu, and sslmode must be no-verify

BULKER_URL is an url of Bulker service. Used for pulling event logs. E.g.: https://bulker.your-domain.com

BULKER_AUTH_KEY is used to authorized HTTP-request to Bulker. See BULKER_AUTH_TOKENS, BULKER_TOKEN_SECRET and BULKER_RAW_AUTH_TOKENS in Bulker configuration

ROTOR_URL is an url of the Rotor service. E.g.: http://rotor:3401

ROTOR_AUTH_KEY is used to authorized HTTP-request to Rotor. See ROTOR_AUTH_TOKENS, ROTOR_TOKEN_SECRET and ROTOR_RAW_AUTH_TOKENS in the Rotor section

To enable GitHub OAuth for Jitsu.

You'll need to create a GitHub OAuth application to get those values:

  1. Go to GitHub Developer settings » OAuth Apps » New OAuth App.
  2. Put any value to Application name.
  3. Set Homepage URL and Authorization callback URL with value of JITSU_PUBLIC_URL.
  4. Press Register application button.
  5. Press Generate a new client secret button.
  6. Copy Client ID and Client Secret values to .env file to GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET variables respectively.

To enable OpenID Connect based authentication for Jitsu.

Expected json object with the following properties: issuer (the issuer domain in valid URL format), clientId, and clientSecret

The well-known configuration endpoint for the provider is automatically set based on the issuer, and the default authorization request includes scopes for OpenID, email, and profile information.

Auth0 Example: {'{"issuer":"https://{yourDomain}.us.auth0.com/","clientId":"***","clientSecret":"***"}'}

Optionally scopes the auth session cookie (jitsu-auth) to a parent domain so that sibling apps running on its subdomains can read the logged-in session. Example: jitsu.com makes the cookie available to use.jitsu.com, app.jitsu.com, etc.

If not set, the cookie is scoped to the Console's own host (e.g. use.jitsu.com) and is shared with that host's subdomains.

Security: widening the cookie to a parent domain shares the session across all of that domain's subdomains — only set this to a domain you fully control.

Url where Ingest service is publicly available. See INGEST_PUBLIC_URL param of `ingest` service for details. Whet set to true enables Connectors Syncs feature in Jitsu Console UI. Requires syncctl service. Scheduled syncs are managed by syncctl as Kubernetes CronJobs — no external scheduler is needed (the GOOGLE_SCHEDULER_KEY variable of previous Jitsu versions was removed). Required if SYNCS_ENABLED=true. SYNCCTL_URL is an URL of the main endpoint of Syncctl service. SYNCCTL_AUTH_KEY is Syncctl service authentication key: one of SYNCCTL_AUTH_KEYS or SYNCCTL_RAW_AUTH_TOKENS configured in Syncctl Functions class assigned to workspaces that don't have an explicit setting: free (shared, sharded function-server deployments) or dedicated (one deployment per workspace). See the Operator section.

Maintenance mode: a JSON descriptor that switches the console API to read-only mode and shows a maintenance page. MAINTENANCE takes the JSON inline; MAINTENANCE_CONFIG_FILE takes a path to a JSON file (e.g. a mounted ConfigMap), so the mode can be toggled without restarting the console.

Replaces the JITSU_CONSOLE_READ_ONLY_UNTIL variable of previous Jitsu versions.

Per-minute sliding-window rate limiting of the console API. Enabled by default (MINUTE_RATE_LIMIT_ENABLED=true); MINUTE_RATE_LIMIT_BASE (default 60) is the base per-minute budget from which limits per auth type and HTTP method are derived. When set to true, rejects signups with personal email addresses (gmail.com, etc.) — a work email is required. ClickHouse host and port where Events Log is stored. E.g.: clickhouse.example.com:9440 Only HTTP(s) protocol is supported. ClickHouse database to where Events Log is stored. ClickHouse cluster id to properly create replicate tables for Events Log. E.g.: jitsu_cluster ClickHouse username and password. Enable SSL for Clickhouse connection [//]: # () [//]: # () [//]: # ( Name of ClickHouse schema where Rotor service sends events metrics.) [//]: # () Email sending configuration:
SMTP_CONNECTION_STRING is a connection string to SMTP server in format: smtp://user:password@localhost:587
EMAIL_TRANSACTIONAL_SENDER is an email address that will be used as sender.
EMAIL_TRANSACTIONAL_REPLY_TO (optional) is an email address that will be used as Reply-To.
BCC_EMAIL (optional) is an email address where all emails will be sent as BCC.
Format of console application logs. Possible values: text or json
## Syncctl HTTP port where Syncctl will be available.

A list of hashed auth tokens that authorizes user in HTTP interface separated by comma. Each must have format: $salt.$hash where $salt should be random string. Hash is hex(sha512($token + $salt + SYNCCTL_TOKEN_SECRET). $token must consist only of letters, digits, underscore and dash

SYNCCTL_RAW_AUTH_TOKENS can be used if you want to provide a comma-separared list of raw takens instead of hases. It offers simplicity at cost of lower security.

PostgreSQL connection string. postgres://$user:$password@localhost:$port/database?sslmode=no-verify&search_path=newjitsu. Should be the same as DATABASE_URL for console

search_path must be newjitsu, and sslmode must be no-verify

URL of the same PostgreSQL instance as it is reachable from kubernetes cluster. Required only if it is different from SYNCCTL_DATABASE_URL. E.g. if you use localhost in SYNCCTL_DATABASE_URL Path to kubernetes config file or kubernetes config in yaml format. If syncctl service itself runs in kubernetes cluster, you can skip this option or use local value. Name of kubernetes context if not the default one is used and name of kubernetes namespace where sync jobs will be created. Maximum time in hours that sync job can run. After that time sync job will be terminated.

SYNCCTL_REPOSITORY_BASE_URL is the URL of the console's export endpoint: https://$console-endpoint/api/admin/export. Syncctl polls the syncs export and reconciles Kubernetes CronJobs (one per scheduled sync, named sync-<syncId>) to match the configured schedules. If not set, scheduled syncs are not managed — only ad-hoc runs work.

SYNCCTL_REPOSITORY_AUTH_TOKEN is used to authorize requests to console. It must start with the service-admin-account: prefix. E.g.: service-admin-account:console-token

Period in seconds for refreshing the syncs export from console. Docker image of the sidecar container that accompanies each sync pod. CronJob tuning: maximum job runtime in seconds (default 180000), Kubernetes retry limit for failed jobs (default 0) and maximum random start jitter in seconds (default 60). Manual lever to force re-creation of all managed CronJobs (bump when the pod template must be re-rendered). Sync task log retention: maximum number of log entries per task (default 3000) and retention in days (default 60). These settings were moved from console to syncctl. OAuth token refresh for sources that authorize via Nango; the refresh runs as an init container of each sync pod. Format of syncctl application logs. Possible values: text or json
## Operator The Operator provisions and scales [function servers](/docs/self-hosting/production-deployment#functions-and-the-jitsu-operator) — Kubernetes deployments running user functions and profile builders. It polls the console export, creates a `fs-` Deployment + Service (port `3456`) per workspace (or per shard for the `free` class) along with ConfigMaps, an optional HorizontalPodAutoscaler and a PodDisruptionBudget, and maintains the `FunctionsServer` routing table in Postgres that Rotor and Ingest rely on. The Operator requires Kubernetes RBAC permissions for `pods`, `services`, `configmaps`, `secrets`, `apps/deployments`, `apps/statefulsets`, `autoscaling/horizontalpodautoscalers` and `policy/poddisruptionbudgets`. See the [development Helm chart](https://github.com/jitsucom/jitsu/blob/newjitsu/helm/templates/operator.yaml) for a reference manifest. HTTP port of the operator's status endpoints (/health, /ready, /status).

OPERATOR_REPOSITORY_BASE_URL is the URL of the console's export endpoint: https://$console-endpoint/api/admin/export. The operator polls workspaces, connections and functions from it.

OPERATOR_REPOSITORY_AUTH_TOKEN is used to authorize requests to console. It must start with the service-admin-account: prefix.

PostgreSQL connection string — the same database console uses. The operator maintains the FunctionsServer table there. Kubernetes access: path to a kubeconfig file or inline kubeconfig YAML (default local — in-cluster config), context name, and the namespace where function-server deployments are created. Docker image of function servers. The operator starts it with ROTOR_MODE=functions. Port of the function-server Service. Must match the FUNCTIONS_SERVER_URL_TEMPLATE configured on Rotor, Ingest and Console. Functions class assigned to workspaces without an explicit setting: free (shared, sharded deployments) or dedicated (one deployment per workspace). Scaling of function-server deployments: number of shared deployments for the free class (default 1) and minimum replicas per deployment (default 2). HorizontalPodAutoscaler settings for function-server deployments (disabled by default; max replicas 16; target CPU utilization 100). Pod-level settings applied to function-server deployments: resource requests/limits (JSON), node selector, tolerations and service account. MongoDB connection string used by function servers for Functions Persistent Storage and profile builders. When set, the operator adds a mongobetween connection-pooling sidecar to each function-server pod.
## Admin Admin service is used to perform maintenance tasks like reprocessing failover logger files. HTTP port where Ingest will be available. Public url where admin service is deployed and available from internet, usually it's load balancer or reverse proxy. E.g. https://data.jitsu.mycompany.com. Should contain protocol and port (if it's not default).

ADMIN_REPOSITORY_URL is an URL of console's export endpoint that returns configuration of `streams-with-destinations` entities: https://$console-endpoint/api/admin/export/streams-with-destinations.

ADMIN_REPOSITORY_AUTH_TOKEN is used to authorize request to console. It must start with service-admin-account: prefix. E.g.: service-admin-account:console-token

See also CONSOLE_AUTH_TOKENS of console configuration

Period in seconds for refreshing configuration from console's ADMIN_REPOSITORY_URL. List of Kafka brokers separated by comma. Each broker should be in format host:port. If SSL should be enabled for Kafka Skip SSL verification of kafka server certificate. Kafka authorization as JSON object. E.g.: {'{"mechanism": "SCRAM-SHA-256", "username": "user", "password": "password"}'} Format of application logs (that are written to stdout). Possible values: `text` or `json`
--- Source: https://jitsu.com/docs/cloud/ip-addresses # IP addresses to whitelist If you are using Jitsu Cloud, you may need to whitelist our IP addresses in your data warehouse destination. Jitsu Cloud uses the following IP addresses to send data to your **data warehouse** destinations: ```plaintext 104.154.19.121 35.225.194.146 ``` --- Source: https://jitsu.com/docs/migration-from-classic # Jitsu Classic In August 2024, we introduced Jitsu 2.0, the successor to the original version of Jitsu (now referred to as Jitsu Classic). Since then, we have been supporting both versions concurrently to ensure a smooth transition for our customers. However, maintaining two versions limits our ability to focus on innovation and delivering the best possible experience. To prioritize Jitsu 2.0’s continued growth and improvements, we’ve made the decision to sunset Jitsu Classic. We’re here to support you with detailed migration guides and assistance to make this transition as seamless as possible. :::info On **1st of March 2025** Jitsu Classic will be sunset, and services will be shut down. If you are using Jitsu Classic, please migrate to Jitsu 2.0 by that date. ::: To streamline the transition, Jitsu 2.0 introduces backward compatibility with Jitsu Classic at the events endpoint level. The effort required to migrate to Jitsu 2.0 requires minimal changes on the client side — specifically, updating the **Jitsu host** and **write key**. That being said, if you have more resources, you can switch to a native Jitsu 2.0 client and protocol. To start with migration, please see: * [What Changed](/docs/migration-from-classic/what-changed) — summary of differences between Jitsu Classic and Jitsu 2.0 * [Step by Step Guide using Compatibility Endpoints](/docs/migration-from-classic/step-by-step) - detailed step by step guide on how to migrate from Jitsu Classic to Jitsu 2.0 using compatibility endpoints * [Sending Data](/docs/sending-data) - detailed guide on how to send data to Jitsu 2.0, if you're willing to switch to native Jitsu 2.0 client and protocol --- Source: https://jitsu.com/docs/migration-from-classic/step-by-step # Compatibility Endpoint Migration Guide This guide will help you migrate from Jitsu Classic to Jitsu 2.0 using compatibility endpoints. You don't need to change your client code, but you need to update the the configuration. Alternatively, you can switch to a native Jitsu 2.0 client and protocol. See the [Sending Data](/docs/sending-data) guide for more details. ## Set up a new Jitsu 2.0 project Open https://use.jitsu.com and create a new project. ## Migrate API Keys For each **API Key** in Jitsu Classic project, a corresponding **Site** entity must be created in Jitsu 2.0. To add a new **Site** entity, open the **Event Streaming** -> **Sites** section of the main menu and click the **Add new site** button. There you will obtain a new **Write Key** that should be used in the client configuration. Also `t.jitsu.com` host must be changed to the `legacy.d.jitsu.com` in all your client configurations. ### Custom domains In Jitsu 2.0 custom domains are managed on the **Site** entity level. To direct you exising custom domain to Jitsu 2.0 you need to add it to the Jitsu 2.0 site and change CNAME to `cname.jitsu.com` :::caution Issuing a new certificate can take a few minutes. To avoid downtime, you can use the `legacy.d.jitsu.com` host in the meantime or choose a different subdomxain for Jitsu 2.0. ::: ## Migrate Destinations ### Destination Credentials In Jitsu 2.0, the **Destination** entity only sets credentials and destination-specific settings. To migrate the destination credentials, you need to create a new **Destination** entity in Jitsu 2.0 and set up the same credentials as in Jitsu Classic. To add a new **Destination** entity, open the **Destinations** section of the main menu and click the **Add new destination** button. ### Migrate Connections In Jitsu Classic links between an API Key and Destinations can be found in the **Connected Destinations** property of an **API Key**. For each connected destination, you need to create a new **Connection** entity in Jitsu 2.0. To add a new **Connection** entity, open the **Event Streaming** -> **Connections** section of the main menu and click the **Connect site and destination** button. In the Connection editor choose the corresponding **Site(Source)** and **Destination** entities. Here you can also set parameters like **Sync Mode**, **Data Layout**, **Functions**, etc... ### Migrate Transformation In Jitsu 2.0, the **Transform** Destination setting is replaced by the **Function** entity. To replicate the same transformation logic, you need to create a new function and add it to the Connection settings. To add a new **Function** entity, open the **Event Streaming** -> **Functions** section of the main menu and click the **Add new function** button. There are two ways to adjust existing transformation logic to Jitsu 2.0: 1. Rewrite the transformation logic to work with the new event format. 2. Use the `toJitsuClassic`, `fromJitsuClassic` methods to convert the event to the Jitsu Classic format and back: ```javascript export default async function(event, ctx) { const $ = toJitsuClassic(event, ctx) // your existing transformation logic here return fromJitsuClassic($) } ``` Don't forget to use `fromJitsuClassic($)` method unless you apply transformation for the Webhook destination. Open the **Connection** entity editor and add the function in the **Functions** section. ### Data Warehouses specific settings To make Jitsu 2.0 use the same table layout as Jitsu Classic, you need to set up a **Connection** entity with the **Data Layout** set to **Legacy Jitsu**. Jitsu 2.0 doesn't have the **Table Name** setting. To customize the table name, you can use the [Function](/docs/functions/#change-destination-table) The **User Recognition** settings in Jitsu 2.0 was renamed to **Identity Stitching** and can be found in the **Advanced** section of the connection settings. ### Webhook specific settings In Jitsu 2.0 webhook destination doesn't have the **HTTP JSON Body** setting. To customize the request body, you need use the [Functions](/docs/functions) In Classic project, the webhook request body is derived from classic event layout. To replicate that in Jitsu 2.0 project you need to set up a function that transforms the event back into classic format: ```javascript export default async function(event, ctx) { const $ = toJitsuClassic(event, ctx) // request body transformation logic if any return $ } ``` Add that function to the connection settings. :::caution In Jitsu 2.0, the webhook destination doesn't support the JSON array as the root object of the request body. If it is strictly required, please contact the Jitsu support team. ::: ## Change Client configuration After all API keys, destinations, and connections are migrated, you need to update the client configuration to send event to Jitsu 2.0. ### HTML Snippet Change the hostname of `src` attribute to `legacy.d.jitsu.com` and replace the `data-key` attribute with the `Write Key` of your Site: Snippet for Jitsu Classic: ```html ``` Snippet for Jitsu 2.0: ```html ``` ### Other clients For other clients, you need to: 1. Replace the `key` parameter with the `Write Key` of your Site 2. Replace or add `tracking_host` parameter with the `https://legacy.d.jitsu.com` value. NPM package: ```javascript const { jitsuClient } = require('@jitsu/sdk-js'); const jitsu = jitsuClient({ key: "[SITE_WRITE_KEY]", tracking_host: "https://legacy.d.jitsu.com" , ...params }); ``` If you're writing data directly into Jitsu HTTP API, you need to update the endpoint to `https://legacy.d.jitsu.com/api/v1/s2s/event` and change the write key(token) to the new one. ## Migrate Sources In Jitsu 2.0, the **Sources** entity is replaced by: * the **Service Connections** entity which keeps the credentials for the data sources * the **Sync** entity which works as a link between **Service Connections** and **Destinations** and defines the data import settings and stream selection. Both entities can be found in the **Connectors** section of the Jitsu 2.0 main menu. ## Make sure that events are flowing In Jitsu 2.0 you can check the event flow in the **Data** -> **Live Events** section of the main menu. There you can find 3 tabs: * **Incoming Events** - see the incoming events by Sites * **API Destinations & Functions Logs** - logs of API Based Destinations and Functions * **Batches & Data Warehouse Events** - logs of Data Warehouse and Batch Destinations To check how migrated **Sources** are running you can use the **Connectors** -> **Syncs** or **Connectors** -> **All Logs** sections of the main menu. --- Source: https://jitsu.com/docs/migration-from-classic/what-changed # What has changed in Jitsu 2.0 ## Event format Jitsu 2.0 uses the Segment-compatible [events specifications](https://segment.com/docs/connections/spec/) instead of the custom format in Jitsu Classic. :::info When Jitsu Classic compatibility endpoints are used with Jitsu 2.0 incoming events are automatically transformed into the new format. However, additional adjustments may be needed to ensure all data is processed in the same way as it was in Jitsu Classic. Check the [Step by Step Guide](/docs/migration-from-classic/step-by-step) for more details. ::: ## New APIs and Client Library Jitsu 2.0 introduces new APIs for events [ingestion](/docs/sending-data/http) while maintaining backward compatibility with Jitsu Classic at the endpoint level. With the new APIs, Jitsu 2.0 provides a [new client library](/docs/sending-data/npm). Jitsu 2.0 implements the Segment-compatible HTTP API [Batch endpoint](/docs/sending-data/http#batch-endpoint). That allows to use the most of Segment client libraries with Jitsu 2.0. :::info Jitsu 2.0 maintains backward compatibility with Jitsu Classic at the events endpoint level. This means that migrating to Jitsu 2.0 requires minimal changes on the client side — specifically, updating the Jitsu host and write key. See the [Client Configuration](/docs/migration-from-classic/step-by-step#change-client-configuration) section. ::: ## Project Layout In Jitsu Classic, the event processing pipeline is configured through a link: **API Key** → **Destination** or **Source** → **Destination**. Jitsu 2.0 introduces a redesigned project layout that is more modular and flexible.: * **API Key** entity is replaced by the **Site** entity. * **Source** entity is replaced by **Service Connector** * **Destination** is split into **Destination**, **Connection**, **Function**, **Sync** entities: * New **Destination** entity only sets credentials and destination-specific settings. * **Connection**: These entities link Sites to Destinations, configure the transformation pipeline, and manage all data processing settings. * **Function**: Dedicated entities for defining transformation and enrichment logic. * **Sync**: Entities that link Service Connectors to Destinations and define data import settings. For more details see [Core Concepts](/docs/core-concepts). ## Data Warehouses Data Layout Jitsu 2.0 uses the Segment-compatible event format. This means that the table layout in the data warehouse will be also similar to layout produced by Segment. To make Jitsu 2.0 use the same table layout as Jitsu Classic, you need to set up a **Connection** entity with the **Data Layout** set to **Legacy Jitsu**. --- Source: https://jitsu.com/compare # Compare Jitsu Jitsu is an open-source event pipeline under the MIT licence, billed on active events rather than per tracked user, and self-hostable on every tier. These pages compare it head to head against the five products it comes up against most often. Each one names the cases where the other product is the better choice, because a comparison page written by a vendor is assumed to be biased before it is read, and the only currency it has is evidence of fairness. Every price on every page comes from that vendor's own published pricing. Where a vendor publishes nothing, the page says so rather than estimating. ## The comparisons **[Jitsu vs Segment](/compare/jitsu-vs-segment)** Segment bills per tracked user, Jitsu bills per active event. At 50,000 tracked users generating about 5 million events, that is **$575 against $219**, both at published rates. **[Jitsu vs RudderStack](/compare/jitsu-vs-rudderstack)** Same billing model, so the difference is licence and price. MIT against Elastic License 2.0, and at 5 million events **$219 against $995**, both published. **[Jitsu vs Snowplow](/compare/jitsu-vs-snowplow)** Snowplow asks for schemas, a pipeline in your own cloud and a separate warehouse before your first event. Jitsu asks you to open an account. Snowplow publishes no pricing; Jitsu's free tier is **permanent at 200,000 active events a month**. **[Jitsu vs PostHog](/compare/jitsu-vs-posthog)** PostHog is where data lands. Jitsu is what moves it. Their analytics free tier is larger — **1 million events a month against 200,000** — and most teams should run both. **[Jitsu vs Hightouch](/compare/jitsu-vs-hightouch)** Hightouch added event collection in 2024. One module of a closed sales-led platform, or the pipe on its own at a **published $99**. **[Self-hosted vs managed CDP](/compare/self-hosted-vs-managed-cdp)** What running the pipeline yourself actually costs against paying somebody to run it. ## What each page contains Every comparison follows the same structure, so reading two of them is not reading two different arguments. - A verdict in the first fifteen seconds, naming who should pick which. - A neutral description of the other product, with no dig in it. - One argument, stated, evidenced and linked. Not five. - A feature table with qualified answers rather than ticks and crosses, and rows that say so where the two are equal. - Cost worked at three volumes, so you can find the row nearest your own bill. - A section on where the other product is genuinely ahead, named and unhedged. - What moving would actually involve, including the cases where it is not worth it. ## If you are still deciding There is a ranked list of [open-source and self-hosted CDPs](/blog/open-source-cdp) that does not put Jitsu first, and [Jitsu's own pricing](/pricing) is published for every self-serve volume. --- Source: https://jitsu.com/compare/jitsu-vs-segment # Jitsu vs Segment: Open-Source CDP or Managed Pipeline? **Segment charges for every person it sees. Jitsu charges for the data you actually move.** If your product has more visitors than customers, which describes almost every consumer product, you are funding a bill for people who will never buy anything. If the opposite is true, and a small number of users each generate thousands of events, their model is cheaper and this page says so. **Choose Jitsu if:** - You have meaningful anonymous traffic, so you are paying for people rather than data. - You want the option to run the pipe on your own infrastructure, which Segment does not offer at any price. - You want events in your warehouse in seconds rather than on a schedule. - You want to lower your own bill by filtering events in flight, which per-person billing gives you no way to do. **Choose Segment if:** your events per active user run into the thousands, which makes per-person billing genuinely cheaper; you depend on a destination Jitsu does not have; you are buying their audience-building product rather than a pipe; or your procurement team will only sign with a name they already know. **The part that makes this easy:** you do not have to choose. Jitsu can run alongside Segment today, changing nothing on your site, so you can compare the two on your own data before deciding anything. ## What Segment is Segment is the product that created this category. You install one library, send your events to Segment once, and Segment forwards them to every tool you use, from your warehouse to your email platform to your ad accounts. It has been owned by Twilio since 2020. Its [destination catalog](https://segment.com/docs/connections/destinations/catalog/) is the largest in the category, and for a marketing team that is the entire argument for it. Segment bills on monthly tracked users, usually shortened to MTUs. You pay for every distinct person seen in a calendar month, whether or not they signed up or bought anything. ## What Jitsu is Jitsu is an open-source event pipeline under the MIT licence, the most permissive licence in common use, which puts no restriction on what you do with the code. The repository is at [github.com/jitsucom/jitsu](https://github.com/jitsucom/jitsu). It collects events from your site, app or servers, lets you transform them in JavaScript as they pass through, and delivers them to your warehouse and your tools in seconds. It runs as a hosted service, as a dedicated deployment Jitsu manages, or entirely on your own servers. It is built for volume. It runs on Go, is backed by ClickHouse, and deploys cleanly on Kubernetes, and individual customers push billions of events a month through it. It also ingests webhooks directly, so events from a payment processor or your own backend arrive through the same pipe rather than needing a second vendor. Jitsu is smaller than Segment and is not trying to be a marketing platform. It is trying to be the best pipe you can buy. ## Segment bills per person, Jitsu bills per event
Segment's billing unit is the monthly tracked user. Every distinct person it sees in a calendar month costs money, whether they signed up, bought something, or bounced off the homepage in four seconds. The same person tracked across three months counts three times. Work through what that means. A tracked user is anybody who fires at least one event, so the people this catches are not dormant signups — an MTU has to fire an event, so a dormant account costs nothing. They are anonymous visitors. A company with 200,000 people reading its blog every month and 2,000 paying customers is billed on the 200,000. A publisher with heavy organic traffic pays for its entire audience and monetises a fraction of it. Segment publishes the rate. Their [pricing page](https://www.twilio.com/en-us/products/connections/pricing) gives a free tier up to 1,000 tracked users, a Team plan at $120 a month including 10,000, and then a per-user rate in three bands: $12 per 1,000 to 25,000, $11 per 1,000 to 100,000, and $10 per 1,000 above that. All three of those bands sit in their Team column, so Team scales at a published rate at any volume. Only the Business tier is genuinely a quote, and it carries no per-1,000 rate at all. So the rate is not the problem. The input is. You cannot know your tracked-user count in advance, because it counts anonymous traffic, and anonymous traffic is the part of your audience you are trying hardest to grow. Jitsu bills on **active events** — Jitsu's own term, and [captured events are unlimited on every tier](/pricing). Three consequences follow: - Anonymous visitors cost nothing unless their events are actually put to work. - Your bill tracks the data you use rather than the size of your audience. - You can cut your own bill by filtering events in a [Jitsu function](/docs/functions), a few lines of JavaScript that run on each event in flight. No vendor lets you do that when they are billing per person, because there is nothing to filter. The second half of the argument is simpler. Segment cannot be self-hosted at any tier, at any price. Jitsu can, under a licence that lets you do whatever you want with the code. ## What Jitsu's own billing data shows Jitsu runs the pipeline and sends the invoices, so unlike anybody writing this comparison from the outside, it can describe how real event pipelines behave. Three patterns from the paying base, with no company named. **The largest bills do not belong to the customers with the most users.** They belong to the ones whose events arrive whether or not anybody is present. Per-person pricing has no unit to count in that situation. Per-event pricing charges for exactly what moved. **Some of the highest-volume accounts pay very little**, because the volume comes from a product embedded inside other companies' websites. Under per-tracked-user billing those companies would be charged for their clients' users, none of whom are their own customers. **The accounts that cross the free limit and then stop growing** are almost always the ones where a person has to click for an event to exist. Same audience size as the accounts above them, a fraction of the data, and a bill that stays flat under either model. The pattern underneath all three is the same. Per-person and per-event pricing diverge hardest wherever the volume comes from something other than a person clicking. ## Side by side | | Jitsu | Segment | | -- | -- | -- | | Billing basis | Per active event, captured events unlimited | Per person seen | | Cost of anonymous traffic | Nothing unless their events are put to work | Billed as a tracked user | | Can you lower your own bill | Yes, filter events in a function | No, the count is people not events | | Published pricing | Every self-serve tier | Free and Team, including per-user rates at any volume. Business is a quote | | Free tier | 200,000 active events a month | 1,000 tracked users, 2 sources | | Self-hosting | Yes, MIT, no restrictions | Not available at any tier | | Run in your own cloud | Yes | No | | Warehouse delivery | Streaming, arrives in seconds | Scheduled syncs | | Webhook and SaaS ingestion | Built in, same pipe | Limited, usually a second vendor | | Identity resolution | Real-time profile builder on the stream | Yes, on a fixed profile model | | Transformations | JavaScript functions in flight | Functions, higher tiers | | Command-line tooling and MCP server | Both shipped | No | | Warehouse needed before you start | No, ClickHouse is included on the free tier | Yes, bought separately | | Schema enforcement | In a function you write, see below | Protocols, higher tiers only | | Destination catalog | Smaller. Check your own list against it | Largest in the category | **Being straight about schema enforcement.** Jitsu does not ship a configuration screen where you declare in advance what an event must contain. What it ships is a [function](/docs/functions) that runs on every event in flight, so you write the validation yourself, keep it in your repository, and review it in a pull request like any other code. Segment's equivalent, Protocols, sits behind their higher tiers. Different shape, same outcome, and Jitsu's is not held behind a plan. ## What it costs Both sides are published, so both columns are arithmetic rather than estimates. The events-per-person assumption is roughly 100 events per tracked user and it will differ for your product. | Your scale | Jitsu | Segment | | -- | -- | -- | | 10,000 tracked users, ~1M events | Inside the $99 plan | $120, the Team base | | 50,000 tracked users, ~5M events | $219, that is $99 plus $40 for each additional million | $575 | | 100,000 tracked users, ~10M events | $419 | $1,125 | | Above 100,000 tracked users | Published tiers continue | $10 per 1,000, still published under Team | Across the published range that is roughly 2.6 to 2.7 times, and you can check every figure on both pricing pages before speaking to anybody. That is not a discount anybody is offering. It is what happens when one vendor bills for data and the other bills for people. **Work out which side you are on.** Divide your monthly events by your monthly active users. If that number runs into the thousands, per-person billing is working in your favour and you should stay where you are. If it sits in the tens or the low hundreds, you are paying for the size of your audience rather than for the data you collect. That holds once you are paying for something. Segment is free up to 1,000 tracked users and Jitsu is free up to 200,000 active events a month, so below both of those lines the ratio is choosing between two bills of nothing. Between them it can point the wrong way: 900 users sending 300 events each is 270,000 events a month, which is inside Segment's free tier and outside Jitsu's. ## Where Segment is genuinely ahead **For some companies their pricing model is genuinely cheaper.** Per-person billing rewards depth of instrumentation and punishes breadth of audience. Per-event billing does the opposite. A B2B product with 500 seats where each seat fires 10,000 events a month is 5 million events, which is $219 on Jitsu's pricing and 500 tracked users on theirs — and 500 fits inside their free tier. If your product is heavily instrumented for a small known set of users, their model is on your side and you should keep it. **They dedupe a person across your sources**, so one human is one tracked user rather than several. [Segment's billing docs](https://www.twilio.com/docs/segment/guides/usage-and-billing/mtus-and-throughput) count unique userIds, plus anonymousIds never associated with a userId, across every call from every source in a workspace, for the billing period. Events inside a tracked user are unlimited, so a user who fires five events costs the same as one who fires five thousand. **Their destination catalog is larger.** If your stack depends on a marketing tool Jitsu does not support, that is a real blocker and no argument about billing fixes it. Worth checking first though: most teams actively use five to ten destinations, and the ones that matter most — warehouses, ad platforms and the big analytics tools — Jitsu already has. **They ship Protocols for schema enforcement**, gated behind their higher tiers. Jitsu's is a function you write rather than a screen you configure, set out in full above. **Their brand.** If you need a procurement team to recognise the name, that is worth something. **They build audiences and Jitsu does not.** If you are buying marketing activation rather than a pipe, buy theirs. ## You do not have to switch to try this The [Segment compatibility page](/features/segment-compatibility) sets out three routes, and only one of them is a migration. 1. **Run Jitsu alongside what you have.** Add configuration to your existing Segment setup that sends Jitsu a copy of every event. Nothing changes on your site, your Segment implementation is untouched, and you watch both outputs side by side before deciding anything. 2. **Point Segment's own webhook at Jitsu.** Your events reach your warehouse in seconds rather than waiting for a scheduled sync, while everything else stays as it is. 3. **Switch the endpoint.** Jitsu's JavaScript integration is compatible with the Segment API and uses the open-source analytics.js library, so existing tracking calls work without being rewritten. The first route asks you to decide nothing, which is why it is listed first. Jitsu can also [run on your own servers](/docs/self-hosting) on any tier. Other head-to-heads are on the [comparison hub](/compare), and there is a longer piece on [how MTUs are counted](/blog/segment-pricing-mtus). ## Questions people ask **What does Segment cost at 50,000 tracked users?** $575 a month at their published rates: $120 for the base 10,000, $12 per 1,000 for the next 15,000, and $11 per 1,000 for the last 25,000. The same traffic is $219 on Jitsu at roughly 5 million events. **Do I have to rewrite my tracking code?** No. Jitsu's JavaScript integration is compatible with the Segment API, so existing analytics.js calls work unchanged. **Can I run both at once?** Yes, and for most companies that is the sensible first step rather than a migration. **Is Jitsu really free to self-host?** Yes. The code is MIT licensed with no restriction on commercial use and no feature held back for paying customers. Segment cannot be self-hosted at any tier. **What happens to my historical data?** It stays wherever you already send it. Running Jitsu alongside Segment does not move or delete anything. **Who owns Segment now?** Twilio acquired Segment in 2020. ## Get started with Jitsu Jitsu's free tier is 200,000 active events a month with ClickHouse included, and it can run alongside Segment without changing anything on your site. That is the version of this decision that costs nothing to make — send both a copy of the same events and read your own two bills. [See the pricing](/pricing) · [Run Segment and Jitsu side by side](/features/segment-compatibility) --- Source: https://jitsu.com/compare/jitsu-vs-rudderstack # Jitsu vs RudderStack: Licence and Price **Same architecture, same billing model, and Jitsu costs roughly five times less per event at the entry point, under a licence that does not restrict you.** This is the closest comparison in the set, which is exactly why the two differences that remain decide it. **Choose Jitsu if:** - You might ever put an event pipeline inside your own product, or run one for clients. RudderStack's licence forbids that. MIT does not. - Price per event matters. Their entry tier is $265 a month for 1 million events. Jitsu is $99 for 2 million. - You want to deploy in your own cloud without being pushed onto an enterprise contract to do it. - Your volume goes past 25 million events a month, which is where their self-serve pricing stops and a sales process starts. - You would rather validate events in code you own than in a configuration screen you rent. **Choose RudderStack if:** you need HIPAA, which they offer on Enterprise and Jitsu does not offer at all; or you need schema enforcement as a shipped product rather than as a documented function. Per-event billing is not a differentiator here. Both companies do it, and claiming otherwise loses a reader who already knows their pricing. ## What RudderStack is RudderStack is a warehouse-first event pipeline. Their central pitch is that they do not store your data on their own infrastructure, which is a strong argument in regulated industries, and their own sales team leads with it into healthcare and financial services specifically. They ship a governance toolkit covering a data catalog, tracking plans, schema enforcement and quarantining of invalid events. They support transformations in JavaScript and Python, and user profiles defined in YAML and computed inside your own warehouse. ## What Jitsu is Jitsu is an open-source event pipeline under the MIT licence. It collects events, transforms them in JavaScript in flight, and delivers them to your warehouse and your tools. It runs as a hosted service, as a dedicated deployment, or entirely on your own servers — on every tier rather than only at enterprise. The repository is at [github.com/jitsucom/jitsu](https://github.com/jitsucom/jitsu). Jitsu is the smaller company. It is also the cheaper one by a wide margin and the more permissively licensed one. ## Same billing model, different licence
The licence half first, because it is the part people discover late. RudderStack's server is published under the [Elastic License 2.0](https://github.com/rudderlabs/rudder-server/blob/master/LICENSE), which is source-available rather than an OSI-approved open source licence. It lets you read, modify and run the code. Its first limitation reads: > You may not provide the software to third parties as a hosted or managed > service, where the service provides users with access to any substantial set of > the features or functionality of the software. Jitsu is MIT, which places no such restriction. That difference is abstract until you are the one it applies to. If you are a software company that might one day embed an event pipeline inside your own product, or an agency running infrastructure on behalf of clients, it decides whether the free version is usable at all. There is a legitimate answer on their side: buy RudderStack Cloud and run a paid instance per client. That works, and it is also the point at which this stops being a licence comparison and becomes a price comparison. The price half is simpler. Their free tier is 250,000 events a month against Jitsu's 200,000, so they are more generous at the very bottom. Above that it inverts sharply: [their cheapest paid plan is $265 a month for 1 million events](https://www.rudderstack.com/pricing/) while [Jitsu is $99 a month for 2 million](/pricing). That is roughly five times the cost per event at the entry point. ## Side by side | | Jitsu | RudderStack | | -- | -- | -- | | Licence | MIT, OSI-approved, no restrictions | [Elastic License 2.0](https://github.com/rudderlabs/rudder-server/blob/master/LICENSE), source-available rather than OSI open source, and it bars providing the software to third parties as a hosted or managed service | | Billing basis | Per event | Per event | | Free tier | 200,000 events a month | 250,000 events a month | | Entry paid tier | $99 for 2 million events | $265 for 1 million events | | Self-serve ceiling | Published through Enterprise | [Stops at 25 million events](https://www.rudderstack.com/docs/dashboard-guides/billings-plans/) | | Self-hosting | Yes | Yes, within the licence terms | | Deployment in your own cloud | Yes, on all tiers | Enterprise plan only | | Data stored on the vendor's side | Optional, ClickHouse ships in the free tier | No, by design | | Schema enforcement | In a function you write, see below | Yes, tracking plans with quarantine | | Transformations | JavaScript functions | JavaScript and Python | | Destination catalog | Smaller. Check your own list against it | Larger | | HIPAA | Not offered | Enterprise plan only | | MCP server for AI assistants | Yes, on the [official registry](https://registry.modelcontextprotocol.io) | No server in that registry | ## What it costs Both companies publish per-event pricing, which makes this the one comparison in the set where the numbers sit side by side with nothing estimated. | Monthly volume | Jitsu | RudderStack | | -- | -- | -- | | 250,000 events | $99 | $0, the top of their free tier | | 1 million events | Covered inside the $99 plan | $265 | | 2 million events | $99 | No 2 million tier published. Their next one up is 3 million at $735 | | 5 million events | $219, that is $99 plus $40 for each additional million | $995 | | 10 million events | $419 | $1,675 | | 25 million events | $1,019 | $2,750, their self-serve ceiling | | Above 25 million | Published tiers continue | Contact sales | Their free tier is the one row where they win outright, and at 250,000 events a month it wins by a quarter. Above it the gap is widest through the middle of the ladder and narrows at the top: $995 against $219 at 5 million, closing to $2,750 against $1,019 at their 25 million ceiling. Every figure in both columns comes from a published page. ## Where RudderStack is genuinely ahead **They ship schema enforcement and Jitsu does not.** Tracking plans, quarantine, the whole governance toolkit. If bad data reaching your warehouse is a problem you have already been burned by, take that seriously. Jitsu's answer is below and it is a real answer rather than a deflection, but it is a different shape and you should judge it on its merits. **They store nothing on their own infrastructure, and that is a genuinely strong position.** Everything is processed and passed through to your warehouse. If your security review starts with where the data rests, that architecture answers the question before it is asked. Jitsu ships ClickHouse in the free tier, which most teams treat as a feature, but it does mean the honest answer to "do you hold our data" is "only if you choose to" rather than "never". **They offer HIPAA on Enterprise and Jitsu does not offer it at all.** If you are handling protected health information, that ends the conversation and you should buy theirs. **Their destination catalog is larger.** Check your actual list against Jitsu's before treating that as decisive, because most teams use a handful and the ones that matter are covered on both sides. **They support Python transformations as well as JavaScript.** Jitsu's are JavaScript only. For most teams writing a filter or an enrichment, that is not the constraint. **Their free tier is larger.** 250,000 events a month against 200,000. ## Schema: a screen you configure, or code you own
A [Jitsu function](/docs/functions) is JavaScript that runs on every event as it passes through. A short one will check that an event carries the fields and types you expect and route the failures to a separate destination instead of your warehouse. You get the same outcome their tracking plan gives you, with three differences: it lives in your repository, it is reviewed in a pull request like everything else you ship, and you can express rules a configuration screen cannot. That is not better for every team. If you want governance you configure rather than write, they built that and Jitsu did not. ## How you would move, or not move **If you are evaluating both.** Both accept Segment-format events, so you can point a copy of your stream at each and compare the output and the bill on your own data before deciding anything. **If you already run RudderStack Cloud.** The switch is a configuration change rather than a rewrite. Your existing tracking calls keep working, your destinations get recreated, and your transformations get rewritten as Jitsu functions, which is the only part that takes real time. **If you self-host RudderStack today.** Read the first limitation in their [licence](https://github.com/rudderlabs/rudder-server/blob/master/LICENSE) against what you actually do with it. If you run it for anyone other than yourself, that is the conversation to have before anything else, and it is the reason most people arrive on this page. Jitsu can [run on your own servers](/docs/self-hosting) on any tier and the [pricing is published](/pricing) for every self-serve volume. Other head-to-heads are on the [comparison hub](/compare). ## Questions people ask **Is RudderStack open source?** Their server is source-available under the Elastic License 2.0, which permits self-hosting but forbids offering it as a service to others. Jitsu is MIT licensed with no such restriction. **Do both bill per event?** Yes. This is the one comparison where the billing model is not the difference. **Can I self-host either one?** Both, though only one of the two licences lets you build a commercial service on top. **Does Jitsu have tracking plans?** Not as a product feature. You can achieve the same validation in a function that lives in your repository and is reviewed in a pull request. **Which is cheaper?** Jitsu, at every volume above the free tier. Their free tier is larger at 250,000 events a month against 200,000. **Can an AI assistant operate either pipeline?** Jitsu ships an MCP server published on the official Model Context Protocol registry, so an assistant can create a destination, wire a connection and send a test event on your behalf. RudderStack has no server in that registry. ## Get started with Jitsu Both accept Segment-format events, so the cheapest way to settle this is to point a copy of your stream at each and read the two bills. Jitsu's free tier is 200,000 active events a month with ClickHouse included, and the licence question only matters if you already know the answer to it. [See the pricing](/pricing) · [Run it on your own servers](/docs/self-hosting) --- Source: https://jitsu.com/compare/jitsu-vs-snowplow # Jitsu vs Snowplow: Truly Open Source vs Source-Available Behavioral Data **Snowplow asks you to define your schema, provision a pipeline in your own cloud and buy a warehouse before your first event exists. Jitsu asks you to open an account.** Buyers report [professional services fees of $15,000 to $50,000](https://www.vendr.com/marketplace/snowplow-analytics) for a Snowplow implementation, covering pipeline setup, schema design and enrichment configuration, with a separate warehouse bill on top because Snowplow does not include one. With Jitsu you open an account and send an event. There is no schema to author first, no cloud account to provision, no warehouse to buy and no statement of work, because the free tier includes the database. **Choose Jitsu if:** - You want data today rather than after a design exercise and an invoice. - You do not have a warehouse yet, because Jitsu includes one on the free tier. - Your event structure is still moving and you would rather refine it as you learn what matters. - You do not have a data engineering team free for a month. - You want a free tier that does not expire. Snowplow's trial runs 14 days; Jitsu's free tier is permanent at 200,000 active events a month. **Choose Snowplow if:** you are a large organisation where data quality is the problem you are solving, you already run a schema registry, and you have the engineering time and budget to do it properly. That discipline is real and it pays off at their scale. ## What Snowplow is Snowplow is a behavioural data pipeline built around a schema-first philosophy. Every event type is defined up front as a schema, events are validated against those schemas as they arrive, and anything failing validation is separated out rather than silently landing in the warehouse. The pipeline is designed to run inside your own cloud account. The result is that data arriving in your warehouse is structurally trustworthy in a way most pipelines cannot promise. The cost is that you cannot send an event until you have described it. ## What Jitsu is Jitsu is an open-source event pipeline under the MIT licence. You add the library, send events, and they arrive. There is no requirement to describe an event before sending it, and Jitsu creates the warehouse columns based on what actually arrives. ClickHouse ships inside the free tier, which means you do not need a warehouse already in order to see your data. The repository is at [github.com/jitsucom/jitsu](https://github.com/jitsucom/jitsu). ## What stands between you and your first event
With Jitsu it is one thing: opening an account. There is no schema to author, no cloud account to provision and no warehouse to stand up. The compounding part is what happens after. Snowplow delivers raw event data and most buyers then transform it into analytics-ready tables themselves. Jitsu lands structured data you can query immediately. Asking somebody to buy a warehouse before they know whether the pipeline works is asking them to spend money to answer a question. That is a decision people postpone rather than make, and a postponed decision is indistinguishable from a no. The trade runs the other way too. Teams arriving with a clear picture of what they want to measure get less out of Jitsu, because the step it removes is the one where you write it all down first. Snowplow is built for the team that has already had that meeting. Jitsu is built for the team that has not. ## Side by side | | Jitsu | Snowplow | | -- | -- | -- | | Steps before your first event | Open an account | Provision a pipeline in your cloud, define your schemas, buy a warehouse | | Implementation cost | None | Professional services commonly reported at $15,000 to $50,000 | | Database included | Yes, ClickHouse on the free tier | No, warehouse bought separately | | Free tier | 200,000 active events a month, permanent | 14-day free trial, then a quote | | Published pricing | Yes | No | | Start without a sales call | Yes | Yes, they offer a 14-day trial with no credit card | | Infrastructure to provision first | None on the hosted tier | Pipeline runs in your cloud account | | Schema required before sending | No | Yes, by design | | Licence | MIT throughout, OSI-approved | Community Edition is under the Snowplow Limited Use License, source-available rather than OSI open source | | Data ready to query on arrival | Yes | Raw events, modelled afterwards | | Event validation | In a function you write | Built in, and central to the product | | Bad events | Filtered by your function | Separated automatically | ## What it costs Snowplow publishes no pricing, so the honest comparison is between Jitsu's published rates and what their buyers report paying before the pipeline runs at all. | Monthly volume | Jitsu | Snowplow | | -- | -- | -- | | 200,000 active events | $0. Free tier, permanent, ClickHouse included | Free for 14 days, then implementation and a warehouse before the first billable event | | 2 million events | $99, ClickHouse included | Quote, plus reported implementation, plus your own warehouse bill | | 50 million events | $2,019, that is $99 for the first 2 million and $40 for each additional million | Quote, and the separate warehouse bill scales on its own | Two things are easy to miss. The Jitsu free tier includes the database, so at 200,000 active events a month the total is zero rather than a free pipeline with a paid warehouse behind it. And captured events are unlimited — an event filtered out in a function never becomes an active event, so it is never billed. ## Where Snowplow is genuinely ahead **Their data quality guarantee is real and Jitsu does not match it.** Events are validated against a schema on arrival and failures are separated automatically. If you have inherited a warehouse full of events with inconsistent field names and mismatched types, that is the product built for you. Jitsu's answer is a validation function you write yourself, which gets you most of the way for none of the setup cost. **Their event model is richer.** They have a well-developed approach to attaching context to events that Jitsu has no direct equivalent for. **They are the established choice for large data teams.** If your organisation has already decided governance comes before speed, this page is solving a problem you do not have. ## How you would move, or not move **If you are evaluating both.** Open a free Jitsu account and send the same events to both. Jitsu costs nothing at evaluation volume and includes the database, so there is nothing to provision on that side and nothing to sign. **If you already run Snowplow and it works.** This page is not asking you to replace it. The usual reason teams add Jitsu alongside is a second use case where the schema discipline is not worth the setup cost: a new product, a prototype, or a team that needs data this week. **If you are leaving.** The work is repointing your SDKs and rebuilding your enrichments as [Jitsu functions](/docs/functions). Your existing schemas stay useful as documentation of what your events look like. Jitsu does not require them before you can send anything. You can [run Jitsu on your own servers](/docs/self-hosting) on any tier, and the [published pricing](/pricing) covers every self-serve volume. Other head-to-heads are on the [comparison hub](/compare). ## Questions people ask **What do I have to do before my first event?** Open a Jitsu account. No schema, no cloud provisioning, no warehouse purchase. Snowplow asks you to define your schemas, provision a pipeline in your own cloud and buy a warehouse first, with professional services fees commonly reported on top. **Do I need a data warehouse first?** Not with Jitsu. ClickHouse is included on the free tier. Snowplow requires you to buy one separately. **Can I try either one for free?** Both. Jitsu's free tier is permanent at 200,000 active events a month. Snowplow runs a 14-day free trial with no credit card and no sales call, after which it is a quote. **Does Jitsu validate events?** Not as a built-in product. You write validation into a function, which lives in your repository and is reviewed like any other code. **Is Snowplow open source?** Their Community Edition is source-available under the Snowplow Limited Use License rather than an OSI-approved open source licence. Jitsu is MIT throughout. **Can an AI assistant set this up for me?** Jitsu ships an MCP server, published on the official Model Context Protocol registry, so an assistant can create a destination, wire a connection and send a test event on your behalf. Snowplow has no server in that registry. ## Get started with Jitsu Open an account and send an event. There is no schema to author first, no cloud account to provision and no warehouse to buy — the free tier includes ClickHouse and covers 200,000 active events a month, permanently. [See the pricing](/pricing) · [Run it on your own servers](/docs/self-hosting) --- Source: https://jitsu.com/compare/jitsu-vs-posthog # Jitsu vs PostHog **PostHog is a good product to send data to. It is not a good place to keep all of your data.** The moment events need to reach somewhere other than PostHog — a warehouse, an ad platform, an email tool — there is a routing problem PostHog was not built to solve. That is the job Jitsu does, and doing it in front of PostHog rather than behind it means one instrumentation, one place to filter, and one place to add the next destination. **Choose Jitsu if:** - Your events need to reach more than one destination, which is true of almost every company past its first year. - You want the collection layer MIT licensed with nothing held back behind a paid tier. PostHog is open core. - You want to self-host the pipe or run it in your own cloud account without losing functionality. - You are at real volume, where being billed by an analytics vendor per event gets expensive. - You want your analytics tool to be a choice you can change, rather than the thing your tracking is welded to. **Choose PostHog if:** PostHog is the only place your events need to go, and what you want is analytics rather than infrastructure. Then Jitsu adds a hop and solves nothing, and you should install their SDK. **Use both, and most teams should:** PostHog for product analytics, Jitsu for the pipe that feeds it alongside everything else. [Nextlytics](https://nextlytics.sh), built by Jitsu, ships PostHog as a supported backend, so this is a supported setup rather than a workaround. ## What PostHog is PostHog is an all-in-one product analytics platform. Web analytics, product analytics, session replay, A/B testing, feature flags, surveys and error tracking sit in one product, and it is open source with an analytics free tier covering a million events a month. They have also added a customer data platform and a data warehouse, which is why they appear in this set at all. Historically they were a destination Jitsu would feed. That is still mostly true, and it becomes less true each year. ## What Jitsu is Jitsu is the collection and delivery layer, and it is the whole company rather than one module of something larger. Open source under MIT with nothing held back for paying customers, which is a meaningfully different promise from open core. It takes events from your site, app or servers, transforms them in JavaScript in flight, and delivers them to as many destinations as you need, PostHog included. It runs on Go, is backed by ClickHouse, and handles billions of events a month for customers today. Jitsu does not do analytics. No funnels, no session replay, no experiments, and none are planned. That is the point: the pipe should not have opinions about which analytics tool you use, because you will change your mind about that and you should not have to re-instrument when you do. ## A destination is not a pipe
If every event you collect goes to PostHog and nowhere else, Jitsu adds a hop and solves nothing. Install their SDK and stop reading. That is honest, and it is also rare. The moment there is a second destination the shape changes completely. Now somebody decides whether the warehouse is fed by PostHog's export or separately, whether the ad platform gets its own integration, what happens when the two disagree, and who owns the reconciliation. Every one of those decisions is a small permanent tax, and it compounds with each tool you add. Almost nobody arrives at a pipeline because they wanted one. They arrive having outgrown not having one, and the pattern is consistent enough to describe before it happens to you. A team installs an analytics SDK. It works, and for a year there is no problem, which is why the top of this page says plainly that you should not move. Then finance wants the same events in the warehouse. Growth wants them in the ad platform. Somebody wires a second integration, then a third. Then two systems disagree about a number and an engineer spends a week working out which one is right. That week is the real cost of the decision, and it lands long after the decision was made. A collection layer in front of everything means one instrumentation, one place to transform and filter, and one place to add the next destination. It also means your analytics vendor becomes a choice rather than a foundation. If you decide in two years that you want a different tool, or your own warehouse models, or nothing at all, that is a configuration change rather than a re-instrumentation project. ## Side by side | | Jitsu | PostHog | | -- | -- | -- | | Built to route to many destinations | Yes, that is the product | No, primarily itself | | Licence | MIT throughout, nothing held back | Open core | | Self-hosting | Yes, full product | Yes, with some features not included | | Vendor stores your data | Optional | Yes, that is how the product works | | Warehouse as a first-class destination | Yes, streaming | Via their warehouse product | | Transform and filter in flight | Yes, JavaScript functions | Limited | | Swap your analytics tool later | Configuration change | Re-instrumentation | | Free tier | 200,000 active events a month | Analytics 1M events; data pipelines 10K events + 1M rows | | Product analytics | None | Funnels, retention, cohorts, paths | | Session replay, flags, experiments | None | Yes | | MCP server for AI assistants | Yes, on the [official registry](https://registry.modelcontextprotocol.io) | Yes, also on the registry | ## What it costs Both companies publish per-event pricing, and a table putting the two rates side by side would still mislead you, because the two columns do not buy the same thing. PostHog's price includes the analytics product. Jitsu's includes the pipe, a database, and delivery to as many destinations as you want. A cheaper rate on one side is not a cheaper stack. What is cleanly comparable is where each one stops being free. | | Jitsu | PostHog | | -- | -- | -- | | Free tier | 200,000 active events a month, ClickHouse included | **Analytics: 1M events.** Data pipelines: 10K events + 1M rows | | First paid tier | $99 for 2 million events | Per-product rate on a sliding scale, [published on their pricing page](https://posthog.com/pricing) | Their free allowances are per product, and it is worth being precise about which one. The headline 1M is their **analytics** allowance, five times Jitsu's 200,000 and a real reason to start with them. Their **data pipelines** allowance — the product that does the job Jitsu does — is 10,000 events plus a million rows. Both numbers are on their page and both matter, depending on what you are actually buying. Below a million events, if analytics is all you want, PostHog is free and Jitsu is not, and no argument on this page changes that. Above it, the question stops being the rate and becomes whether you want one bill for analytics or one bill for routing plus whatever analytics you choose. For your own numbers: Jitsu is $99 for 2 million events and $40 for each additional million, so 5 million is $219 and 50 million is $2,019. PostHog's scale varies by product line and is on their page, which is why it is not reproduced here rather than approximated. ## Where PostHog is genuinely ahead **Their analytics free tier is five times larger.** A million events a month against Jitsu's 200,000. If you are small and staying small and analytics is what you want, that is a real reason to start with them. **They give you answers and Jitsu gives you data.** A team of three who need to know which feature is being used will get further with PostHog in an afternoon than with any pipeline. Infrastructure only pays off once you have something to route. **Their developer reputation and documentation are excellent.** Jitsu's are not there yet. **Their mobile SDK coverage is broader.** If mobile is your primary surface, check Jitsu's SDK list against your platforms before deciding. ## How you would move, or not move **If PostHog is your only destination.** Do not move. Install their SDK. Jitsu would add a hop and solve nothing. **If you are adding a second destination.** This is the moment to put a pipe in front rather than to wire a second integration. Send events to Jitsu, and PostHog becomes one destination among several rather than the place everything has to pass through. **If you already have both and they disagree.** That is the reconciliation tax this page is about, and it is usually solved by making one system the source and the other a consumer of it. Jitsu is built to be the source. Jitsu can [run on your own servers](/docs/self-hosting) on any tier, the [pricing is published](/pricing) for every self-serve volume, and other head-to-heads are on the [comparison hub](/compare). ## Questions people ask **Is PostHog a competitor or a destination?** Mostly a destination, increasingly also a competitor since they added a customer data platform. **Can Jitsu send events to PostHog?** Yes, and the Nextlytics library lists PostHog as a supported backend. **Which free tier is more generous?** For analytics, PostHog's, by five times — a million events a month against Jitsu's 200,000. For the pipeline itself their allowance is 10,000 events plus a million rows, so it depends which product you are comparing. **Do I need both?** Only if you have destinations beyond PostHog. If you do not, you do not need Jitsu. **Is PostHog fully open source?** It is open source and open core. Jitsu is MIT throughout, with no feature held back for paying customers. **Can an AI assistant operate either one?** Both ship an MCP server and both are published on the official Model Context Protocol registry. That makes them the only two products in this comparison set where that is true, since RudderStack, Segment, Hightouch and Snowplow have no first-party server in that registry. ## Get started with Jitsu If PostHog is the only place your events need to go, install their SDK — this page is not for you. If you are adding a second destination, put the pipe in front first. Jitsu's free tier is 200,000 active events a month with ClickHouse included, and PostHog stays a supported destination. [See the pricing](/pricing) · [Run it on your own servers](/docs/self-hosting) --- Source: https://jitsu.com/compare/jitsu-vs-hightouch # Jitsu vs Hightouch **Your tracking code is the hardest thing in your stack to replace. That makes the question which layer you are willing to have somebody else own.** Hightouch is a large marketing platform that added event collection in 2024. Jitsu is the collection layer itself: open source under MIT, self-hostable, priced publicly from $99, buyable without a sales call. The question is not which product has more features, because theirs does. **Choose Jitsu if:** - You want a published price and no sales process to get started. - You want the ability to take the software and run it yourself if the commercial relationship stops working. - You want the collection layer to outlive whichever activation tool you pick this year. - Your event collection is the whole job rather than one feature of somebody's marketing suite. **Choose Hightouch if:** you need reverse ETL, meaning warehouse data pushed back out into your tools, which Jitsu does not do at all; or you want one vendor covering collection through to activation and are happy to buy that through a sales team. **Use both if:** Jitsu's collection layer feeds your warehouse and their reverse ETL pushes data back out. That is a coherent stack and plenty of companies should run it. ## What Hightouch is Hightouch began as a reverse ETL company, taking data sitting in your warehouse and pushing it back out into the tools your teams use. In 2024 they added [Hightouch Events](https://hightouch.com/docs/events/overview), which is event collection: browser, iOS, Android and Node SDKs, an HTTP API, Kafka and PubSub sources, warehouse destinations and event streaming. It also ships data contracts, which is their name for schema enforcement, and JavaScript event functions. Their SDKs are explicitly backward-compatible with both Segment's and RudderStack's, which tells you plainly who they are competing with. So they are now a direct competitor on collection, and simultaneously a much larger platform that does several things Jitsu does not do at all. Most readers arriving here still think of them as reverse ETL only. That is out of date. ## What Jitsu is Jitsu is the pipe on its own. Open source under MIT, self-hostable with no licence restriction, priced publicly starting at [$99 a month for 2 million events](/pricing), with no sales conversation required to buy it. The repository is at [github.com/jitsucom/jitsu](https://github.com/jitsucom/jitsu). It does not do reverse ETL. It does not build audiences. It moves events from where they happen to where you need them. ## What you are locked into
Every layer of a data stack is replaceable except one. You can swap a warehouse, swap an analytics tool, swap a reverse ETL vendor, and the work is measured in days or weeks. You cannot swap the collection layer easily, because it is the layer with your tracking code embedded across every page, screen and service you own. Customers change warehouses. They change analytics tools. They add and drop destinations constantly, and none of that touches their instrumentation. What almost nobody does is change collection. That is the layer to be careful about, and it is the layer where the licence matters more than the feature list. With Hightouch, collection is one module of a closed-source marketing platform whose centre of gravity is activation. If their pricing changes or their roadmap moves, your exit involves rebuilding instrumentation across your entire product. With Jitsu, collection is the whole company, and it is MIT licensed, so the worst case is that you take the code and keep running it yourself. That is not a feature they can add later, because their business model does not permit it. There is a second, smaller point. Jitsu publishes its prices. Hightouch does not publish prices for the events product at all. ## Side by side | | Jitsu | Hightouch | | -- | -- | -- | | Source available | Yes, MIT | No | | Self-hosting | Yes | No | | Run in your own infrastructure | Yes | No | | Published pricing | Yes, from $99 | No price published for any plan | | Try the events product without talking to sales | Yes | No, Hightouch Events sits behind a demo request | | Free tier | 200,000 active events a month, captured events unlimited | A free Basic Reverse ETL plan, but nothing free for events | | Collection is the main product | Yes | No, added in 2024 | | Database included | Yes, ClickHouse on the free tier | No | | Deployment options | Cloud, dedicated, or your own servers | Three clouds, eight regions — AWS, Azure and GCP | | Event functions | Yes, JavaScript | Yes, JavaScript | | Segment-compatible SDKs | Yes | Yes | | Schema enforcement | In a function you write, see below | Yes, data contracts | | Audience building | No | Yes | | Reverse ETL | No | Yes, and it is their origin | | MCP server for AI assistants | Yes, on the [official registry](https://registry.modelcontextprotocol.io) | No server in that registry | ## What it costs Hightouch does not publish pricing for the events product, so the only honest thing in the right-hand column is that finding out requires a call. Jitsu's is [on the pricing page](/pricing) and you can check every figure tonight. | Monthly volume | Jitsu | Hightouch | | -- | -- | -- | | 200,000 active events | $0, ClickHouse included | No free tier for the events product | | 2 million events | $99 | Contact sales | | 5 million events | $219, that is $99 plus $40 for each additional million | Contact sales | | 50 million events | $2,019 | Contact sales | The difference that matters here is not the number. It is that you can find Jitsu's without speaking to anybody. ## Where Hightouch is genuinely ahead **They do reverse ETL and Jitsu does not.** If the reason you are shopping is to get warehouse data back into your marketing tools, they solve all of that and Jitsu solves none of it. Buy theirs, and consider running Jitsu underneath it. **They ship data contracts and Jitsu does not.** Jitsu's answer is a [function](/docs/functions), JavaScript that runs on every event in flight. A short one will check that an event carries the fields and types you expect and route the failures to a separate destination instead of your warehouse. It lives in your repository and gets reviewed in a pull request rather than configured in somebody's console. Different shape, and for an engineering-led team often the preferable one, but it is not the same product.
**They are one vendor across the whole path.** If assembling a stack is the thing you are trying to avoid, that has real value. The trade is that the layer hardest to replace becomes the one you have least control over. **Their audience-building product has no counterpart here.** Jitsu moves events. It does not build segments. **Their platform is larger and better resourced.** That is worth saying plainly rather than leaving as an implication. ## How you would move, or not move **If you already use Hightouch for reverse ETL.** Nothing here asks you to stop. Their reverse ETL and Jitsu's collection layer are different jobs and they compose cleanly. **If you are evaluating their events product.** Both sets of SDKs are compatible with Segment's API, so you can send the same stream to both and compare output and cost before committing to either. **If you are leaving their events product.** Because the SDKs are Segment-compatible on both sides, the client-side work is a configuration change rather than a re-instrumentation. The real work is recreating destinations and rewriting event functions. Jitsu can [run on your own servers](/docs/self-hosting) on any tier and the [pricing is published](/pricing) for every self-serve volume. Other head-to-heads are on the [comparison hub](/compare). ## Questions people ask **Does Jitsu do reverse ETL?** No. If that is what you need, Hightouch is a better fit. **Is Hightouch open source?** No. Jitsu is MIT licensed. **Does Hightouch collect events?** Yes. They launched Hightouch Events in 2024 with browser, iOS, Android and Node SDKs, an HTTP API, Kafka and PubSub sources, data contracts and JavaScript event functions. Treating them as reverse ETL only is out of date. **Can I use both?** Yes, and for some teams that is the right answer: Jitsu's collection layer feeding a warehouse, with their reverse ETL pushing data back out. **Do their SDKs work with Segment's?** Yes, both are compatible with Segment's API. **Can I see Hightouch pricing without talking to sales?** Not for the events product. Jitsu's is published. ## Get started with Jitsu The price is published and the free tier is 200,000 active events a month, so you can evaluate the collection layer tonight without a sales conversation. If you end up wanting reverse ETL too, run both — they are different jobs. [See the pricing](/pricing) · [Run it on your own servers](/docs/self-hosting) --- Source: https://jitsu.com/compare/self-hosted-vs-managed-cdp # Self-Hosted vs Managed CDP: How to Choose (2026) The choice between self-hosting a customer data platform and using a managed service comes down to four variables: **cost at scale**, **data control**, **operational burden**, and **time-to-value**. This page works through each factor honestly. Managed CDPs are the right answer for many teams. Self-hosted CDPs are the right answer for others. The goal is to help you identify which side of that line your team sits on. --- ## What "Self-Hosted CDP" and "Managed CDP" Mean **A managed CDP** is software-as-a-service. The vendor operates the infrastructure, handles uptime, performs upgrades, and provides support. You connect sources and destinations through a web interface and SDK. Examples: [Segment](https://segment.com/pricing/) (managed-only), [RudderStack Cloud](https://www.rudderstack.com/pricing/) (managed tier), [Snowplow CDI Cloud](https://snowplow.io/pricing) (Snowplow-hosted). **A self-hosted CDP** is software you deploy and operate on your own infrastructure — a cloud provider (AWS, GCP, Azure), your data center, or any server environment. You are responsible for deployment, upgrades, scaling, and availability. Examples: [Jitsu self-hosted](/docs/self-hosting) (Kubernetes via Helm, MIT license), [RudderStack self-hosted](https://www.rudderstack.com/docs/get-started/rudderstack-open-source/) (Elastic License 2.0, source-available rather than OSI open source), Snowplow Production Self-Hosted (SLULA; commercial agreement required for HA). Some platforms offer both: Jitsu provides both MIT-licensed self-hosting and Jitsu Cloud as a managed option. This page will help you figure out which deployment model fits your team regardless of which tool you choose. --- ## Comparison: Self-Hosted vs Managed CDP | Factor | Self-Hosted | Managed (SaaS) | |---|---|---| | **Data ownership & residency** | Data stays on your infrastructure; full control over where it resides | Data transits vendor infrastructure; residency options vary (e.g., Segment EU/US regional workspaces) | | **Cost model** | Pay for infrastructure (compute, storage, egress); no per-event or per-user fees | Per-event, per-MTU, or tier-based; costs can grow non-linearly with traffic | | **Cost at scale** | Predictable as you scale; cloud infrastructure scales with usage | Can become expensive at high event volumes; MTU spikes and anonymous traffic inflate managed CDP bills | | **Ops & maintenance burden** | You own deployments, upgrades, monitoring, incident response, scaling | Vendor manages ops; you get SLAs; no servers to maintain | | **Time-to-value** | Requires initial setup (minutes to hours depending on tool); longer path to production for complex deployments | Faster onboarding; plug in SDK, start routing data; no infrastructure required | | **Scalability** | You provision capacity; cloud autoscaling helps but requires configuration | Vendor scales automatically; no capacity planning required | | **Privacy & compliance** | Strongest possible data privacy — data never leaves your control | Depends on vendor compliance posture; HIPAA/GDPR/CCPA support varies | | **Vendor lock-in** | Minimal if tool is open source (MIT or Apache 2.0); you own the data and can switch | Data format/schema in vendor's system; migration effort if you switch vendors | | **Customisation** | Full code access (if open source); modify pipeline logic, add connectors, write custom transforms | Limited to vendor's feature set, extension APIs, and supported integrations | | **Support & SLAs** | Community support (forums, GitHub); you handle incidents | Vendor SLAs, enterprise support tiers, dedicated customer success | | **License considerations** | Varies: MIT (Jitsu) imposes no restrictions; Elastic License 2.0 (RudderStack core) is source-available and bars offering it as a hosted service to third parties; SLULA (Snowplow core) restricts production HA and competing products | Vendor's terms of service; no OSS license concerns | --- ## Choose Self-Hosted If… **Your data cannot leave your infrastructure.** Healthcare, financial services, and government teams often have hard requirements that event data — which may include personal information — never transits a third-party SaaS. A self-hosted CDP on your own VPC satisfies this. A managed CDP, even with EU data residency, routes data through the vendor's infrastructure. **Your event volume makes managed pricing expensive.** Managed CDPs price on MTUs, monthly tracked users, or tiered event counts. These costs scale with traffic, and they scale non-linearly in some models. Segment's [MTU model](https://www.twilio.com/docs/segment/guides/usage-and-billing/mtus-and-throughput) counts anonymous visitors separately from logged-in users, and multiple browsers or cookie clearing can generate additional MTU counts for the same person. At high event volumes, the difference between paying per-million on managed pricing vs. paying only infrastructure on self-hosted is material. For reference: Jitsu Business on Cloud is $99/month for 2M active events, $40 per additional 1M. [RudderStack Growth](https://www.rudderstack.com/docs/dashboard-guides/billings-plans/) charges $1,675/month for 10M events. Self-hosting Jitsu at 10M events means only your infrastructure cost. **You want no vendor lock-in.** Open-source CDPs with permissive licenses give you full portability. Your data schema, your pipeline code, and your connectors belong to you. If you need to migrate tools, you take the code and configuration with you. **Your engineering team has capacity to operate infrastructure.** Self-hosting is not hard — Jitsu's [development Helm chart](/docs/self-hosting/quick-start) brings the full stack up on Minikube, and production runs on [any Kubernetes cluster](/docs/self-hosting/production-deployment) — but it does require someone to own the deployment, monitor it, and handle upgrades. **GDPR/CCPA compliance is easier to demonstrate to auditors through infrastructure control.** When data stays on your servers, data residency and DPA obligations are governed entirely by your cloud provider agreement and internal processes. There is no third-party sub-processor to add to your data map for the CDP itself. --- ## Choose Managed CDP If… **Your team has no bandwidth for infrastructure.** A startup with three engineers shipping product features should not spend a sprint setting up and operating a data pipeline. Managed CDPs have SLAs, automatic upgrades, and 24/7 vendor operations. The trade-off — data leaving your infrastructure and per-event pricing — is often worth it at small scale. **You need a wide pre-built integration catalog immediately.** Segment's [700+ destinations](https://segment.com/pricing/) mean most third-party tools have maintained connectors. Building and maintaining custom connectors for a long list of SaaS tools on a self-hosted pipeline requires ongoing engineering time. **Time-to-value is the top constraint.** A managed CDP with a polished onboarding flow gets you from zero to events flowing in an afternoon. Self-hosting adds infrastructure provisioning, configuration, and testing to that timeline. **You need vendor-managed compliance guarantees.** HIPAA with a vendor BAA, SOC 2 attestation, and data residency contracts are easier to purchase from a managed vendor than to implement from scratch. [Segment is a HIPAA-eligible platform](https://www.twilio.com/docs/segment/privacy/hipaa-eligible-segment) with BAA support. Building equivalent compliance controls on self-hosted infrastructure is achievable but requires internal investment. **You want predictable SLAs and enterprise support.** Managed CDPs provide uptime SLAs and enterprise support contracts. A self-hosted deployment's reliability depends on your team's infrastructure practices. --- ## A Note on Jitsu: Both Options, One Tool Jitsu offers both deployment models under the same product: - **Jitsu Cloud** (managed): [Free plan at 200k active events/month](/pricing), Business at $99/month for 2M active events, then $40 per additional 1M, Enterprise custom. Managed by Jitsu. No servers to run. - **Self-hosted Jitsu** ([MIT license](https://github.com/jitsucom/jitsu/blob/newjitsu/LICENSE)): Runs on any Kubernetes cluster. Jitsu ships a [development Helm chart](/docs/self-hosting/quick-start) that stands up the full architecture, and there are [community-maintained Helm charts](/docs/self-hosting/production-deployment#running-jitsu-on-a-kubernetes-cluster) as well. No usage limits. Infrastructure cost only. If you are not sure which model fits, you can start on Jitsu Cloud to validate the pipeline and migrate to self-hosted later when scale or compliance requirements make it worthwhile — or stay on Jitsu Cloud if managed fits your team. The Segment-compatible SDK means your instrumentation works identically on both. This matters for the comparison: Jitsu is not arguing that self-hosted is always better. It is arguing that you should have a genuine choice, and that the tool should not force you into one model. --- ## FAQ **Is a self-hosted CDP cheaper than a managed one?** At high event volumes, typically yes — substantially. Infrastructure cost grows with volume but does not have per-event pricing cliffs. Managed CDPs can have significant price jumps at tier boundaries or volume spikes. At small volumes (under ~500k events/month), a managed CDP free tier may be cheaper than the engineering time to operate infrastructure. The crossover point depends on your team's hourly cost and your event volume. **Is self-hosting a CDP technically difficult?** It depends on the tool. Jitsu runs on Kubernetes: its [self-hosting docs](/docs/self-hosting) start from a development Helm chart that brings the full stack up on Minikube, and a production deployment needs a cluster of your own and more configuration. Snowplow's self-hosted path starts at [~1 hour for a Terraform-proficient engineer](https://docs.snowplow.io/docs/get-started/self-hosted/). In general, if your team runs other Docker or Kubernetes workloads, self-hosting a modern event pipeline is tractable. The ongoing burden — monitoring, upgrades, on-call — is the bigger consideration. **Can I switch from a managed CDP to self-hosted later?** Yes, but the migration effort depends on data format portability. Managed CDPs that use standard schemas (analytics.js / Segment-compatible) make migration easier — your instrumentation stays the same, and you redirect the endpoint. Jitsu's [Segment-compatible endpoint](/features/segment-compatibility) means existing Segment SDK instrumentation routes to Jitsu without re-instrumentation. Proprietary event schemas or tightly vendor-coupled transformations increase migration effort. **Does self-hosting mean I lose GDPR compliance tools?** No, but you are responsible for implementing them. Self-hosted CDPs give you full control over data deletion, suppression, and residency — you implement those against your own infrastructure and database rather than clicking a vendor dashboard. For teams with dedicated data privacy engineering, this is often preferable. For teams without, managed vendors who offer GDPR tooling reduce implementation burden. **What if I outgrow the managed plan?** Most managed CDPs have enterprise tiers for large-scale use. If per-event or per-MTU pricing becomes a significant budget item, that is typically the signal to evaluate self-hosting. The Jitsu [Business plan](/pricing) at $40 per additional 1M active events is designed to remain predictable at scale before an enterprise conversation. --- ## Get Started If managed fits your team: [start on Jitsu Cloud](/pricing) — the Free plan handles 200k active events/month with no credit card required. If self-hosted fits your team: [read the Jitsu self-hosting docs](/docs/self-hosting) — the development Helm chart brings a full local instance up on Minikube. For the wider field — PostHog, Apache Unomi, Tracardi and where each one wins — see [Best Open-Source CDPs & Self-Hosted Segment Alternatives (2026)](/blog/open-source-cdp). ← [Back to all comparisons](/compare) --- Source: https://jitsu.com/blog/segment-pricing-mtus # Segment pricing: how MTUs work and when they get expensive If you are trying to work out what Segment will cost you next year, the numbers are on their pricing page, but the one you will actually pay is not. Segment publishes a base price and three per-user overage rates. What it does not publish is the only figure that matters, which is how many users you are about to be billed for. This post explains the billing unit Segment uses, what it costs at each published tier, and the three situations where the bill grows faster than the business does. We build an event pipeline ourselves and we bill on a different unit, so we have an interest here. Every number below links to where it came from. ## Segment bills per user, not per event Segment's billing unit is the monthly tracked user, usually shortened to MTU. An MTU is a unique user who generates at least one event in a calendar month. Two things follow from that definition, and both matter more than they sound. The first is that events inside an MTU are free. If one user fires five events this month or five thousand, you pay the same. Segment also deduplicates a user across web, mobile and server, so somebody who browses anonymously and then logs in counts once rather than twice. The second is that the count resets every calendar month. A user who is active in January and again in June is billed as an MTU in both months. ## What the tiers cost The free tier covers [1,000 MTUs and two sources](https://www.twilio.com/en-us/products/connections/pricing). The Team plan is [$120 a month for 10,000 MTUs](https://www.twilio.com/en-us/products/connections/pricing) with unlimited sources. Above 10,000 MTUs the Team plan meters, and [those rates are published too](https://www.twilio.com/en-us/products/connections/pricing): $12 for each additional 1,000 MTUs between 10,000 and 25,000, $11 per 1,000 from 25,000 to 100,000, and $10 per 1,000 above that. The Business tier is where the published numbers stop. Segment lists it as a custom quote, and [Vendr, which tracks what companies actually pay for software](https://www.vendr.com/marketplace/twilio-segment), describes it the same way: priced on MTU volume and feature requirements. So the arithmetic is available to you well past the entry plan. Most people never do it, because the input is a number they do not have. ## Where MTU pricing gets expensive **Your marketing site counts.** An MTU is a unique user who fires an event, and an anonymous visitor reading a blog post fires events. A company with 200,000 monthly readers and 2,000 customers is billed on the readers. **Growth in users costs more than growth in usage.** If your product becomes more valuable to each existing customer, you send more events per user and the bill does not move. If you acquire more users, the bill moves in step with them. That is the wrong way round for most consumer products, where user growth is the thing you are trying to cause. **The 10,000 MTU ceiling arrives early.** Ten thousand monthly active users is a small consumer product. Crossing it does not put you straight into a sales call, but it does start the meter: every further 1,000 users adds $12, then $11, then $10, on top of the $120 base. Growing from 10,000 to 50,000 users takes the bill from $120 to $575 with nothing else about the product changing. ## What per-event pricing does instead We bill on events delivered to a destination. Captured events are free and unlimited on every plan, so if you filter an event out in a function before it reaches your warehouse, you are not billed for it. Our [free tier is 200,000 delivered events a month, and the paid plan is $99 a month for 2 million events, then $40 for each additional million](https://jitsu.com/pricing). Take a consumer app with 50,000 monthly active users each generating 100 events, which is 5 million events a month. On our pricing that is $99 for the first 2 million plus $120 for the remaining 3 million. $219 a month, and you can check that arithmetic yourself before talking to anybody. On Segment that is 50,000 MTUs. Working down their published rates, it is $120 for the first 10,000, $12 per 1,000 for the next 15,000, and $11 per 1,000 for the last 25,000. That comes to $575 a month. So the same traffic is $219 on our pricing and $575 on theirs, and you can check both figures before you talk to anybody. ## When Segment is the cheaper choice MTU pricing is genuinely better when you have a small number of users each generating a large number of events. Take a B2B product with 500 seats where every seat fires 10,000 events a month. That is also 5 million events. On Segment it is 500 MTUs, which fits inside the free tier. On our pricing it is $219 a month. Segment wins outright and it is not close. The general rule is that MTU pricing rewards depth of instrumentation and punishes breadth of audience, and per-event pricing does the opposite. If your product is heavily instrumented for a small set of users, Segment's model is on your side and you should keep it. ## How to work out which side you are on Divide your monthly events by your monthly active users. If that number runs into the thousands, MTU pricing is working in your favor. If it sits in the tens or the low hundreds, you are paying for the size of your audience rather than for the data you collect, and per-event pricing will be cheaper. That holds once you are paying for something. Segment is free up to 1,000 tracked users and we are free up to 200,000 delivered events a month, so below both of those lines the ratio is choosing between two bills of nothing. Between them it can point the wrong way: 900 users sending 300 events each is 270,000 events a month, which is inside Segment's free tier and outside ours. Between 10,000 and 100,000 tracked users you do not have to rely on the rule at all. Segment publishes a rate for every band in that range and we publish ours, so both bills are arithmetic rather than estimates. If you do not know your event count, that is worth finding out before your next renewal whichever vendor you end up with. ## If you want to test the other model Our [JavaScript integration is API-compatible with Segment's](https://jitsu.com/features/segment-compatibility), so you can point existing tracking calls at us without rewriting your instrumentation. You can also run both at once by adding a webhook destination inside Segment that sends a copy of every event to us, which lets you compare the two bills on your own data before deciding anything. The full head-to-head, with both pricing pages worked at three volumes and the cases where Segment is the better choice, is at [Jitsu vs Segment](https://jitsu.com/compare/jitsu-vs-segment). And if you are comparing more than the two of us, we published [an honest ranking of open-source and self-hosted CDPs](https://jitsu.com/blog/open-source-cdp) earlier this year. We do not rank ourselves first in it. --- Source: https://jitsu.com/blog/jitsu-2-14 # Jitsu 2.14 is now public: Kubernetes-native for production self-hosting ## Jitsu 2.14 makes production self-hosting Kubernetes-native Functions, Profile Builder v2, and connector syncs now run as **managed Kubernetes workloads** — giving self-hosted teams a cleaner path to autoscaling, isolation, sharding, and cloud-portable operations. It's our first stable public release in nearly a year, and it's a big one — roughly **1,600 commits**. Most of that work went into making Jitsu more reliable, easier to operate, and ready for larger self-hosted deployments. The headline is the architecture: the pieces that used to run inside a single process are now first-class workloads you can scale independently. ## What "Kubernetes-native" means here In the old runtime, functions, profile builders, and connector syncs were background pieces sharing one process. In 2.14 each becomes its own managed workload, using a few new building blocks: - **Function Servers** — dedicated pods that run your user [Functions](/docs/functions) (the JavaScript that filters, reshapes, and routes each event) and **Profile Builder v2** (Jitsu's engine for rolling a user's events up into a single profile). Splitting them out is what makes autoscaling, workload isolation, sharding, and separate execution classes possible. - **The Jitsu operator** — a Kubernetes operator that creates and reconciles those workloads for each deployment, so you don't hand-manage the underlying resources. - **`syncctl`** — the controller that schedules connector syncs. It now reconciles scheduled syncs directly into the cluster as **Kubernetes CronJobs**, with each sync running as its own pod. That replaces the previous Google Cloud Scheduler dependency and makes self-hosting far more portable. The net result: scaling functions, syncs, and event processing no longer ties core behavior to one cloud provider or one process. ## Upgrading? Read this first If you self-host Jitsu, a few things changed that you need to plan for. **Read the [release notes](https://github.com/jitsucom/jitsu/releases/tag/2.14.0) and the [self-hosting docs](https://docs.jitsu.com/self-hosting) before you upgrade.** - **Kubernetes is now required for feature-complete production self-hosting** — specifically, for deployments that use functions, profile builders, and connector syncs. Smaller or exploratory setups still have lighter options (see below). - **Deploy the operator before upgrading rotor** — event delivery now depends on function-server routing, so the operator has to be in place first. - **Docker Compose is deprecated for full production use.** It still exists for exploration and development, but the recommended development setup is now the **Helm chart** — and 2.14 ships development Helm charts that run the full architecture locally. - **Google Cloud Scheduler support has been removed** (replaced by Kubernetes CronJobs). - **Some console defaults changed**: host-only auth cookies by default, a new **maintenance mode** replacing the old read-only flag, default API rate limiting, stricter config validation, and a lower default ingest payload size. ## What else is new Alongside the architecture work, 2.14 lands a long list of product and developer-facing features: - **MCP server support in Jitsu Console** — connect an AI agent directly to your workspace (see [Jitsu now speaks MCP](/blog/jitsu-mcp)). - **OpenAPI spec and a built-in API reference.** - **User API tokens** with names, types, and expiration dates. - **SOC 2-oriented audit logs.** - **Maintenance mode** for safe operational windows. - **Dead-letter queue and event reprocessing.** - **Better sync and connection notifications.** - **Redesigned signup flow.** - **Segment-style `sentAt` clock-skew correction.** - **ClickHouse TTL-based events-log retention.** ## More and better integrations We also added and improved a range of integrations: - **Destinations:** Resend, SendGrid, Statsig, DuckDB / MotherDuck, Xero. - **Warehouse auth:** Snowflake key-pair auth, Postgres Private Service Connect auth, and Redshift IAM role improvements. - **Syncs:** Firebase subcollection syncs. ## Get started 2.14 is a big step toward running Jitsu seriously in your own infrastructure — a cleaner foundation for scaling event processing without a hard dependency on any single cloud. - **Upgrading an existing deployment?** Start with the [self-hosting guide](https://docs.jitsu.com/self-hosting), which now walks through the Helm chart. - **Trying it for the first time?** The development Helm chart spins up the full architecture locally — no Kubernetes cluster of your own required. - **Want the full changelog?** Read the [2.14.0 release notes on GitHub](https://github.com/jitsucom/jitsu/releases/tag/2.14.0). - **Questions or hit a snag?** Open an issue on [github.com/jitsucom/jitsu](https://github.com/jitsucom/jitsu) — we read every one. --- Source: https://jitsu.com/blog/jitsu-mcp # Jitsu now speaks MCP: let your agent build and debug the pipeline ## TL;DR Jitsu now has an **MCP server**. Point your agent at `https://use.jitsu.com/mcp`, approve it once in the browser, and it can run your data pipeline for you. - **One URL, OAuth login.** No API keys to copy. Connect, click approve, done. - **It configures Jitsu.** Create destinations, wire streams to them, edit connections — the whole config surface, as tools. - **It watches Live Events.** The same real-time event stream you see in the UI is now an MCP tool, so the agent can see what's actually flowing. - **It fixes your Functions.** Jitsu transforms are JavaScript. The agent can read them, spot the bug, and rewrite them. Put those last two together and you get something we haven't seen elsewhere: an agent that doesn't just *set up* your pipeline, it *debugs* it. Works today in Claude Code, Claude Desktop, Cursor, and VS Code. [Setup docs are here](https://docs.jitsu.com/mcp). ## The next step after "programmable" A few weeks ago we [made Jitsu programmable](/blog/openapi-and-cli): a public Management API, a `jitsu-cli`, and a `llms.txt` so agents could find the docs. That was the foundation — a real API surface that something other than a human could drive. An MCP server is the natural next step. The API let an agent *call* Jitsu if you taught it how. MCP lets an agent *discover* Jitsu on its own. It connects, lists the tools, reads the schemas, and goes to work. No glue code, no remembering endpoints, no pasting keys. ## Why we built it Agents are good at one thing and bad at another. They are very good at calling well-described tools. They are bad at clicking through admin UIs. Jitsu's config is exactly the kind of thing that's tedious in a UI and easy as a tool call: create a destination, point a stream at it, add a filter, deploy a function. The Management API already modeled all of it. MCP just hands that model to the agent in the format agents expect — typed tools with descriptions and JSON schemas — and adds the one thing a raw API can't: a clean **OAuth** flow, so connecting is a click instead of a key-management chore. ## Connect in one step There's a single endpoint: ``` https://use.jitsu.com/mcp ``` Add it to your client, and on first use a browser tab opens asking you to approve the connection. Approve it and the agent gets a scoped, revocable token — managed for you, shown under your account, killable any time. No secrets in config files. In Claude Code that's one command: ```bash claude mcp add --transport http jitsu https://use.jitsu.com/mcp ``` Cursor, Claude Desktop, and VS Code take the same URL through their MCP settings. The [docs](https://docs.jitsu.com/mcp) have copy-paste config for each. ## What your agent can do The tools mirror the Management API, so once the agent learns one resource it knows them all: - `list_workspaces` — find the workspace to work in - `list_resources` / `get_resource` — inspect destinations, streams, services, connections, functions - `get_resource_schema` — get the exact JSON Schema for what it's about to create - `create_resource` / `update_resource` / `delete_resource` — make the change Every config object — destination, stream, service, connection, function — follows the same `list / get / create / update / delete` shape. The agent reads the schema, fills it in, and gets a structured error back if it's wrong, so it can correct itself without you. ## The part we're excited about: Live Events as a tool [Live Events](https://docs.jitsu.com/features/live-events) is one of the most-loved features in Jitsu. It's the real-time view of your pipeline: events arriving from your site, your functions' execution logs, and the status of every write to your warehouse. When something breaks, it's where you look. That stream is now an MCP tool. The agent can pull recent events for a stream, a connection, or a function, and filter to errors only — the same thing you'd do in the UI, except the agent does it. This closes the loop. An agent with config tools alone is working blind: it can create things but can't tell if they work. With Live Events, it can: 1. Create a destination and a connection. 2. Watch the events flow. 3. See the one that failed, with the error. 4. Fix it. 5. Watch the fix land. That's not a demo script. That's how you'd debug it yourself. ## Jitsu is programmable, and so are the fixes A quick refresher, because it's what makes step 4 possible. Jitsu isn't just pipes. Between your source and your destination you can run [Functions](/docs/functions) — plain JavaScript that filters, reshapes, enriches, or routes each event. Drop PII before it hits the warehouse, split one event into three, look up a value, forward only `purchase` events. It runs inline, on every event. Functions are config objects like any other, so they're available through the same tools. The agent can read a function's code with `get_resource`, find the bug that's throwing in your Live Events, and ship a corrected version with `update_resource`. The thing that's failing and the thing that fixes it are both in reach. ## A prompt that works today Drop this into Claude Code with the Jitsu MCP connected: > *"Add a Postgres destination called `analytics`, using the credentials in > `./pg.env`. Route my `web` stream to it, but only `purchase` and `signup` events. Send a > test event, check Live Events, and if anything errors, find the function in the path and > fix it."* It reads the schema, creates the destination and the connection, sends the event, tails Live Events, and — if your `enrich` function chokes on a missing field — opens it, patches it, and confirms the next event lands clean. The whole loop, unattended. ## What's covered today The full configuration surface is live over MCP: workspaces, destinations, streams, services, connections, and functions, plus the Live Events stream. That's the 90% of Jitsu you touch day to day. Sources, syncs, and reporting are next — they work over the API and we're rolling them into the MCP surface from there. ## Get started - Endpoint: `https://use.jitsu.com/mcp` - Setup for each client: [docs.jitsu.com/mcp](https://docs.jitsu.com/mcp) - Background: [Functions](/docs/functions) · [Live Events](https://docs.jitsu.com/features/live-events) · [Management API](https://docs.jitsu.com/api) Connect it, ask it to build something, and watch it work. Tell us what you ship — we read every Slack message and GitHub issue. --- Source: https://jitsu.com/blog/open-source-cdp # Best Open-Source CDPs & Self-Hosted Segment Alternatives (2026) An open-source CDP collects customer events from your sites, apps, servers, and tools, then makes that data usable in your warehouse, analytics stack, or activation tools. Teams usually look for a self-hosted CDP when Segment-style SaaS pricing starts to hurt, when compliance requires tighter control, or when they want raw event ownership instead of a vendor-shaped data model. Jitsu publishes this guide, so here is the disclosure: Jitsu is in the comparison, and it is not ranked first by default. The ranking below uses the same criteria for every product: license, self-hostability, warehouse fit, integration coverage, developer experience, maintenance, real-time support, and best-fit use case. The main takeaway: there is no single best open-source CDP. PostHog has the strongest open-source product/community footprint. Jitsu is the cleaner fit if you want a lean, warehouse-first event pipeline. RudderStack has the broadest integration catalog, but current `rudder-server` licensing is Elastic License 2.0 rather than OSI open source. Snowplow is mature, but new Community Edition production use is restricted by SLULA, so do not call it open source for production deployments. ## How we ranked these We ranked each tool on eight criteria: - License type: OSI open source, source-available, fair-code, or proprietary. - Self-hostability: whether you can run it yourself, and whether production use is allowed. - Warehouse-native architecture: how naturally the tool sends data to Snowflake, BigQuery, Redshift, Postgres, ClickHouse, or similar stores. - Integration breadth: sources, destinations, SDKs, and connector ecosystem. - Developer experience: setup path, docs, deployment effort, and maintenance burden. - Community and maintenance health: GitHub activity, stars, release signal, and project maturity. - Real-time vs batch: whether the product is built for live event delivery, scheduled syncs, or both. - Best-fit use case: where the product wins without pretending it wins everywhere. ## At-a-glance comparison | Rank | Tool | License status | Self-host? | GitHub stars | Warehouse fit | Integrations | Best fit | |---:|---|---|---|---:|---|---|---| | 1 | PostHog | MIT for core; `ee/` separately licensed | Yes, Docker Compose; PostHog says Cloud is best for most users | ~37.7k | Good for analytics, warehouse, batch exports, and realtime destinations | Broad product suite rather than a pure CDP catalog | Product teams that want analytics, replay, flags, experiments, and CDP-like pipelines in one stack | | 2 | [Jitsu](/pricing) | MIT | [Yes; open source self-host has no usage limits](/docs/self-hosting) | ~5.0k | Strong warehouse-first event pipeline | Smaller catalog than RudderStack; unlimited destinations in Jitsu pricing | Data teams that want a lean real-time Segment alternative into their warehouse | | 3 | RudderStack | Elastic License 2.0 for `rudder-server`; source-available, not OSI open source | Yes in practice, but license terms matter | ~4.5k | Strong warehouse and reverse ETL orientation | 16+ SDK sources, 15+ cloud event sources, 200+ cloud destinations, 10+ warehouse/data lake destinations | Teams that value integration breadth and can accept source-available licensing | | 4 | Apache Unomi | Apache 2.0 | Yes | ~372 | Profile-store/CDP foundation more than event-pipeline SaaS replacement | REST API, plugins, Groovy actions, OSGi plugins | Enterprises or agencies that want an Apache-licensed profile store and can handle Java/Karaf/Elasticsearch | | 5 | Tracardi | MIT with Commons Clause; fair-code/source-available, not OSI open source | Yes; Docker-oriented; commercial tiers for scale/SaaS | ~653 | More customer-profile/orchestration centric than warehouse-native | 31 documented extensions | Smaller teams that want low-code CDP workflows and can accept license restrictions | | 6 | Snowplow | Mixed; new pipeline components under SLULA/source-available terms | Community Edition self-host is for testing/evaluation, not production | ~7.0k | Very strong behavioral data pipeline | SDKs, enrichments, warehouse/database/lake/stream outputs | Mature behavioral data infrastructure if you are willing to buy the commercial production license | ## PostHog: best open-source product suite, not a pure CDP PostHog earns the top spot if you define the job broadly: collect product events, analyze behavior, replay sessions, run feature flags, run experiments, and send data onward. The core repository is MIT-licensed outside separately licensed enterprise areas, and PostHog’s self-hosting docs say the product is open-source and freely available to host yourself under an MIT-licensed Docker Compose deployment. Where PostHog wins: community size, product breadth, and buyer clarity. The repository has roughly 37.7k GitHub stars, which is much larger than the other tools in this list. PostHog Cloud pricing also has a concrete free tier, including the first 1 million product analytics events per month, 5k session replay recordings, 1 million requests, 1 million managed warehouse rows, 1 million batch export rows, 10k realtime destination trigger events, and other product-specific allowances. Where it falls short: PostHog is analytics-first. If you want a small, dedicated real-time event router into your warehouse, PostHog may be more platform than you need. Self-hosting is real, but PostHog is direct about the operational burden: you manage infrastructure, deployments, scaling, backups, and risk, and PostHog says Cloud is the best experience for most users. Best for: product-led engineering teams that want analytics plus adjacent CDP/data-pipeline capabilities in one open-source stack. ## Jitsu: best lean warehouse-first Segment alternative Jitsu is the strongest fit if the job is “collect events and stream them into our warehouse with minimal product sprawl.” Jitsu describes itself as 100% open source under the MIT license and [self-hostable](/docs/self-hosting). Its site positions the product around sending web, app, email, chatbot, and CRM event data into your warehouse, with named warehouse support for Snowflake, BigQuery, Redshift, Postgres, MySQL, and ClickHouse. Where Jitsu wins: it is simple, warehouse-first, and permissively licensed. Jitsu’s [pricing page](/pricing) says the open-source version is “Free. Forever,” MIT licensed, self-hostable on any cloud provider, cloud native, and has no usage limits. Jitsu also has a straightforward cloud plan: the free cloud plan includes 200k active events per month, and Business is listed at $99/month with 2 million active events per month plus $40 per additional 1 million active events. Where it falls short: Jitsu does not have RudderStack’s integration catalog or PostHog’s all-in-one product suite. If your decision hinges on hundreds of packaged destinations, identity resolution workflows, or built-in analytics UI, you will need to verify whether Jitsu’s [connectors](/integrations) and functions cover your exact paths. Best for: data teams that want a real-time, self-hostable, warehouse-native event pipeline and prefer MIT licensing over larger but more restrictive ecosystems. If you are coming from Segment, the [Segment compatibility guide](/features/segment-compatibility) covers the fastest migration path. ## RudderStack: broadest integration ecosystem, but not OSI open source RudderStack is still one of the most credible Segment alternatives, especially if you care about integrations. Its pricing page lists 16+ SDK sources, 15+ cloud event sources, 200+ cloud destinations, 10+ warehouse and data lake destinations, 8+ warehouse and data lake sources, and Reverse ETL. For many buyers, that ecosystem is the main reason to consider it. The honest license note matters. The current `rudder-server` repository reports “Other” on GitHub, and the raw license file is Elastic License 2.0. ELv2 is source-available and permits many internal uses, but it is not an OSI-approved open-source license. Older posts may still describe RudderStack’s server as AGPL or open source; verify against the current repository before publishing that claim. Where RudderStack wins: integration breadth, warehouse sync, and enterprise-grade data activation paths. The pricing page also lays out warehouse sync intervals by plan, with 3-hour syncs on Free, 30-minute syncs on Growth, and 5-minute syncs on Enterprise. Where it falls short: license clarity. If your requirement is truly OSI open source, RudderStack should not be counted the same way as MIT or Apache projects. It may still be the right choice, but the reason would be ecosystem coverage, not open-source purity. Best for: teams that need a large integration catalog and are comfortable with source-available licensing. ## Apache Unomi: most “true OSS” profile store, heavier developer experience Apache Unomi is the cleanest answer if your buyer criteria start with “Apache project, Apache 2.0 license, open standards.” The project describes itself as an open-source Customer Data Platform with unified profiles, segmentation, privacy controls, real-time profile updates, a REST API, plugin architecture, Groovy actions, and OSGi plugin support. Its site lists v3.0.0 as the latest release. Where Unomi wins: governance, license, and extensibility. Apache 2.0 is permissive and OSI-approved. The architecture is useful if you want a first-party customer profile service, privacy controls, segmentation, and custom integrations rather than a SaaS-style event-routing product. Where it falls short: developer experience and ecosystem shape. Unomi is Java/Karaf/Elasticsearch-based, and its GitHub footprint is much smaller than PostHog, Snowplow, Jitsu, or RudderStack. It is probably not the fastest path if your only goal is to replace Segment’s JavaScript snippet and warehouse destinations. Best for: organizations that want an Apache-licensed CDP foundation, have Java/platform engineering capacity, and care more about profile infrastructure than a polished cloud-style connector catalog. ## Tracardi: useful low-code CDP, but Commons Clause changes the open-source story Tracardi is a low-code/no-code CDP focused on customer profiles, event orchestration, and automation. Its GitHub README says it is available under “MIT with Common Clause,” and the license docs say the Commons Clause removes the right to sell the software, including paid hosting, consulting, or support services whose value derives substantially from the software. That makes Tracardi fair-code or source-available, not OSI open source. This may be fine for internal use, but it is not the same buyer profile as MIT or Apache 2.0. The pricing page also makes the split clear: the Open-Source Version is aimed at small companies and includes behavioral data collection, profile stitching, event/profile orchestration, and simple automation. Commercial Enterprise adds event validation, custom profile merging, parallel processing, extended automation, programmable data streams, queue collection, test/production separation, and customer activation. Where Tracardi wins: speed to low-code workflows. Its docs list 31 extensions, including ActiveCampaign, Airtable, Amplitude, HubSpot, Mailchimp, Matomo, Mautic, Mixpanel, PostgreSQL, RabbitMQ, Salesforce, SendGrid, and Zapier. Where it falls short: scale and license. The pricing page says the open-source version has limited collected events per second, single-tenant installation, limited automation, and no event validation. Best for: smaller teams that want self-hosted customer-profile workflows and can live with fair-code restrictions. ## Snowplow: mature behavioral data infrastructure, but not production-open-source anymore Snowplow is mature and technically strong. It has roughly 7k GitHub stars, a long history in behavioral data infrastructure, and self-hosted docs that describe web, mobile, and server-side SDKs, custom events and entities, enrichments, and outputs to a warehouse, database, lake, or real-time stream. The license status is the reason it ranks last in an open-source guide. Snowplow’s Limited Use License FAQ says SLULA 1.0 rolled out in January 2024 and 1.1 in December 2024. The self-hosted Community Edition docs say it is meant for testing and evaluating Snowplow and must not be deployed in production. The FAQ says commercial or production environments require a commercial license. Where Snowplow wins: event modeling, pipeline maturity, and data quality. If your team has data engineering capacity and budget, Snowplow can be a serious customer data infrastructure choice. Where it falls short: you should not evaluate current Community Edition as a free production open-source Segment alternative. It is source-available for limited use, and production use requires a commercial path. Best for: teams that want mature behavioral data infrastructure and are prepared for commercial licensing. ## Open source vs source-available vs proprietary The license distinction is not pedantic. It changes cost, control, and exit options. OSI open source means the license meets the Open Source Definition. MIT and Apache 2.0 are the clean examples in this guide. You can usually use, modify, host, and redistribute the software with minimal restrictions, subject to the license terms. Source-available means you can read the code, and sometimes run it internally, but the license restricts important uses. ELv2, Commons Clause, and SLULA-style licenses can be reasonable commercial choices, but they are not the same as MIT or Apache 2.0. Proprietary means the vendor controls the software and usually sells access as SaaS or a commercial license. Segment, Hightouch, Census, Tealium, and mParticle are still worth knowing, especially for enterprise buying cycles, but they do not solve the same problem as a self-hosted open-source CDP. ## How to choose If you want a real-time event pipeline into your warehouse, start with Jitsu. It has the cleanest fit for teams that want Segment-style collection without per-event SaaS dependency, and the MIT license keeps the [self-hosted path](/docs/self-hosting) simple. If you want product analytics plus data pipelines, start with PostHog. It is less pure as a CDP, but the product breadth and community are hard to ignore. If you need the widest connector catalog, evaluate [RudderStack](/compare/jitsu-vs-rudderstack). Just do it with eyes open on the current ELv2 license. If you need an Apache-licensed customer profile service, evaluate Apache Unomi. It is the best fit for custom CDP infrastructure, not the easiest plug-and-play event router. If you want low-code customer journeys and automation, evaluate Tracardi. It can be useful internally, but Commons Clause and open-source tier limits matter. If you want high-maturity behavioral data infrastructure, evaluate [Snowplow’s](/compare/jitsu-vs-snowplow) commercial path. Do not treat Community Edition as a free production open-source option. ## Proprietary alternatives worth knowing [Segment](/compare/jitsu-vs-segment) remains the default reference point for managed CDP/event collection. Hightouch and Census are more warehouse-activation and Reverse ETL oriented. Tealium and mParticle are enterprise CDP platforms with broader governance and identity features. These tools can be the right choice when you want a vendor-managed system, procurement support, and enterprise controls, but they are not replacements for source code ownership or a self-hosted deployment. ## FAQ ### Is there a free open-source alternative to Segment? Yes. Jitsu is MIT-licensed and self-hostable, and PostHog’s core is MIT-licensed outside separately licensed enterprise areas. Apache Unomi is Apache 2.0. Your best choice depends on whether you need event routing, product analytics, or customer profiles. ### Is Snowplow still open source? Not for new Community Edition production use in the way many buyers mean “open source.” Snowplow’s docs say SLULA applies to Community Edition, that Community Edition is for testing/evaluation, and that production or commercial use requires a commercial license. ### Can I self-host a CDP? Yes, but “can self-host” is not the same as “easy to run in production.” Jitsu, PostHog, Apache Unomi, Tracardi, RudderStack, and Snowplow all have self-hosting stories, but the license terms and operational burden differ sharply. ### What is the cheapest CDP? For software license cost, MIT/Apache self-hosted tools are the cheapest because the license fee can be zero. For total cost, include infrastructure, upgrades, monitoring, on-call work, and engineering time. A managed cloud plan can be cheaper than self-hosting if your team is small. ### Which open-source CDP is best for data warehouses? Jitsu is the cleanest warehouse-first choice in this list because its core positioning is real-time event collection into warehouses such as Snowflake, BigQuery, Redshift, Postgres, MySQL, and ClickHouse. RudderStack also has strong warehouse and reverse ETL coverage, but the current server license is source-available rather than OSI open source. ### Which CDP has the largest open-source community? PostHog has the largest GitHub footprint in this comparison, with roughly 37.7k stars as of this draft. Stars are not product fit, but they are a useful signal for community attention and project momentum. ## Closing take If you need an honest shortlist, start with three questions. Do you need an OSI-approved license? Do you need a pure event pipeline or a broader product analytics suite? Do you want to run production infrastructure yourself? For permissive self-hosted event collection, Jitsu is the most direct Segment alternative. For product analytics breadth, PostHog is hard to beat. For connector coverage, RudderStack deserves evaluation, but not under the label “open source” unless its license changes. For strict Apache governance, Unomi is the cleanest. For behavioral data infrastructure, Snowplow is serious, but commercial. ## Related comparisons - **[All Jitsu comparisons](/compare)** — the full comparison directory, including where each competitor wins. - **[Jitsu vs Segment](/compare/jitsu-vs-segment)** — open-source CDP or managed pipeline. - **[Jitsu vs RudderStack](/compare/jitsu-vs-rudderstack)** — fully open vs open-core. - **[Jitsu vs Snowplow](/compare/jitsu-vs-snowplow)** — truly open source vs source-available. - **[Self-hosted vs managed CDP](/compare/self-hosted-vs-managed-cdp)** — how to choose, without a predetermined answer. --- Source: https://jitsu.com/blog/openapi-and-cli # Jitsu, now programmable: Management API, CLI, and ready for AI agents ## TL;DR Three things shipped today: - A **public Management API** for every Jitsu instance, with a full OpenAPI 3.0 reference at [docs.jitsu.com/api](https://docs.jitsu.com/api). - **`jitsu-cli`** — a real command-line interface that does everything the UI does. - An API surface designed so that **AI agents can run it on your behalf** — and a `llms.txt` manifest at [docs.jitsu.com/llms.txt](https://docs.jitsu.com/llms.txt) so they can find the docs. If you've ever wanted to ask Claude or Cursor to "spin up a new destination, point our checkout events at it, and only forward `purchase` events" — that's a one-paragraph prompt now. ## Why this took us a while Jitsu has always had an internal API — the Jitsu UI is just a client of it. But "internal" and "public" are not the same thing. A public API is a contract: stable URLs, typed inputs, real error messages, versioned schemas, an honest reference doc that doesn't drift from the code. We rebuilt the inside of the API to make that contract real. Every endpoint is now declared with the same Zod schemas the server uses for validation. The OpenAPI reference is generated from those schemas at build time — there is no second source of truth, no hand-written docs to rot. When the code changes, the docs change. ## The CLI `jitsu-cli` is what we always wanted to give you. Install it, log in once, and you can stop clicking: ```bash npm i -g jitsu-cli jitsu-cli login jitsu-cli set-default-workspace my-workspace ``` Now you can do real work from the terminal: ```bash # inventory jitsu-cli config destinations list jitsu-cli config streams list # create a destination from a JSON file jitsu-cli config destinations create --from-file ./destinations/snowflake-prod.json # wire a stream to it jitsu-cli config connections create \ --from snowflake-prod \ --to events-stream # tear it down (with cascade if it's referenced) jitsu-cli config destinations delete legacy-bq --cascade ``` The CLI calls the same Management API under the hood, so anything you can script with `curl`, you can do with `jitsu-cli` — and vice versa. ## Infrastructure as code, finally Check your Jitsu config into git. Apply it from CI. Promote it between environments. The pattern that's been standard for the rest of your stack is now possible for your data pipeline. ```yaml # .github/workflows/jitsu-apply.yml - run: | jitsu-cli login -h https://use.jitsu.com -k "${{ secrets.JITSU_KEY }}" for f in destinations/*.json; do jitsu-cli config destinations upsert --from-file "$f" done ``` No more "who changed prod last Tuesday" — `git log` answers it. ## Built for agents Here's the part we're most excited about. Modern coding agents — Claude Code, Cursor, Codex, the home-grown ones your team is already writing — are very good at calling well-documented HTTP APIs. They are bad at clicking through admin panels. We designed this release with that in mind: - **One canonical reference.** Every endpoint lives at [docs.jitsu.com/api](https://docs.jitsu.com/api), with request/response schemas, examples, and the same Bearer-token auth model across the surface. An agent can read it once and be productive. - **`llms.txt`.** The docs site serves a [`llms.txt`](https://docs.jitsu.com/llms.txt) index and a full-text bundle. Drop them into your agent's context and it has the whole API surface in one request. - **Predictable shapes.** Configuration objects (destinations, streams, services, connections, syncs, functions) all follow the same `list / get / create / update / delete` pattern. Once an agent has the shape of one, it has the shape of all of them. - **Honest errors.** Validation errors come back as structured JSON with the field path. Agents can fix their own mistakes without you in the loop. A prompt that works today: > *"Create a new ClickHouse destination called `analytics-eu`. Use the credentials in > `~/.jitsu/clickhouse-eu.env`. Connect it to the `web-events` stream, but filter to only > `purchase` and `signup` events. Use the existing `pii-redact` function on the way in."* Drop that into Claude Code in a repo with `jitsu-cli` installed. It reads the docs, calls the right endpoints in the right order, and reports back. The same prompt would have taken twenty clicks across three pages in the UI. This isn't a future plan. It works now. We use it ourselves. ## What's covered today The first cut of the public surface covers the configuration objects most people touch every day: **workspaces, destinations, streams, services, connections, syncs, functions, and profile builders**. That's the 90% of what the UI does. Endpoints for sources, reports, schema, and SQL are still internal — they work, they're just not yet in the public spec. We'll roll them in next. ## Get started - Install: `npm i -g jitsu-cli` - Log in: `jitsu-cli login` - Reference: [docs.jitsu.com/api](https://docs.jitsu.com/api) - CLI docs: [docs.jitsu.com/jitsu-cli](https://docs.jitsu.com/jitsu-cli) Tell us what you build. We read every Slack message and GitHub issue. --- Source: https://jitsu.com/blog/introducing-notifications # Introducing Jitsu Notifications: Proactive Monitoring Made Simple ## Introduction We’re excited to introduce automated notifications for job failures and recoveries! Now, you’ll receive real-time Email or Slack alerts whenever a Connector Sync task or Data Warehouse Batch job fails or successfully recovers — ensuring you stay informed without constantly monitoring dashboards. ### How it works * Get notified when the first attempt to run a job is successful. * Get notified immediately when a job fails, so you can take action fast. * Get details about the failure in the notification, so you can quickly understand what went wrong. * Receive a recovery notification when the job is back on track. * Stay updated via email or Slack, based on your preferences. But that isn’t all! ### Keeping Notifications Under Control Jitsu ensures that you only receive relevant notifications, so you stay informed without unnecessary alerts: * By default, for ongoing issues, you will receive additional notifications only once every 24 hours. You can adjust this frequency in the [Notification Settings](#how-to-enable). * Jitsu also detects so-called flapping statuses—intermittent failures followed by successful runs. When a flapping status is detected, Jitsu stops sending notifications for every individual status change. This ensures you aren’t overwhelmed with alerts for each failure and recovery. ### Events Queue size reporting When a Data Warehouse Batch job doesn’t run successfully, you will receive a notification that includes the size of the events queue. This allows you to monitor the queue size and take action if needed. See the screenshot above. Queue size is also reported in **Data** -> **Live Events** -> **Batches & Data Warehouse Events** section of Jitsu UI. ### Partial Sync information Source Connector Syncs are usually configured with multiple selected streams. During a Sync Task run, some streams may fail while others succeed. In such cases, Jitsu marks the sync as **partially successful** and sends a special notification that includes details about the failed streams. The notification will include the following information:: ``` Last Status: PARTIAL Streams Failed: 2 of 13 Details: 2025-04-03T23:00:55.496Z [PARTIAL] 2 of 13 streams failed. Failed streams: products, companies. ``` ## How to enable Email notifications are enabled by default for all workspace members. Each user can manage their personal email notification preferences in the **Settings** -> **Notification Settings**. You can also configure a Slack Notifications webhook in the same settings panel to receive the same notifications in a Slack channel of your choice. Slack Notifications is the workspace-wide setting. We hope you enjoy the new notifications feature and find it helpful in keeping your data pipeline running smoothly. --- Source: https://jitsu.com/blog/server-side-tracking # Server side tracking 101 ## Introduction This article outlines the differences between client-side and server-side tracking, explaining why server-side tracking can be more beneficial. It also offers practical recommendations for switching to server-side tracking. Let's start with the basics. ## What is Client-Side Tracking? Client-side tracking has been the main way of tracking user behavior on the web for at least the past decade. The most popular tools, such as Google Analytics, Mixpanel, and Amplitude, are based on client-side tracking. It operates as follows: * A tracking JavaScript is loaded from the server, or integrated into the front-end app as a JavaScript library, or both. * The JavaScript is executed on the client, collecting basic information about the user, such as user ID, page title, etc. * Then, this script sends events to the server. Some events, such as page views, are automatic. Other events, such as conversions or certain button clicks, are sent from the front-end app. * The front-end app may interact with the tracking script in other ways, like providing a `user id` or `user email` to identify the user. ## What is Server-Side Tracking? Server-side tracking operates differently. It doesn't rely on JavaScript libraries and sends all events from the backend. Events that are known to the backend by design, such as page views, are sent automatically. Other events are triggered by the front end, but the first event goes to the server, and the server sends it to the tracking platform. ## Why is Server-Side Tracking Better? There was a time when JavaScript didn't exist on the web at all, and all tracking (if any) was done on the server. The server that renders the page inherently knows that the user has visited the page and can record this information for later analysis. Since JavaScript emerged, the world moved to client-side tracking for many reasons, but server-side tracking is still superior in several ways: * Server-side tracking doesn't interfere with user experience and page speed. Client-side tracking scripts are often heavy and slow down the page. * Server-side tracking is more precise. If the server returned the page (or data for the page in a SPA), the fact won't be lost. On the client side, the tracking script may fail to execute, or the user may close the page before the script has executed. * Server-side tracking is immune to ad blockers. Ad blockers can block tracking scripts, but they can't block server-side tracking. The only downside of server-side tracking is that it's harder to implement. ## When Server-Side Tracking is Not Possible There are a few cases when server-side tracking can't be used. Let's review them. ### Advertising Platforms Most advertising platforms rely on browser cookies to identify the user and cross-match user IDs with their internal data platforms. This means they need to be able to set cookies, which is only possible if their JavaScript is executed on the client. Most disadvantages of client-side tracking do not apply to advertising platforms by design. For example, if the user has an ad blocker, they won't see ads anyway, and it's pretty pointless to attempt to track their behavior. Similarly, tracking precision is less critical. Advertising platforms operate on a large scale, and they can tolerate losing information on a small portion of users. ### Heatmap and Session Trackers Heatmap and session trackers, like Hotjar, Logrocket, and Fullstory, are used to record user actions like mouse movements and clicks. These types of user actions never reach the server. ### Google Analytics Since the release of GA4, Google has significantly reduced the functionality of their [measurement protocol](https://developers.google.com/analytics/devguides/collection/protocol/ga4), so not all data can be sent from the server. If you want to use GA4, you'll need to rely on client-side tracking at least for page views. ## Jitsu's Recommendation: Use Client-Side Tracking for Google Tag Manager and GA We recommend switching to server-side tracking whenever possible. For client-side, use Google Tag Manager as middleware, connecting all advertising platforms and Google Analytics to it. This ensures that no important data is lost. You can also use Google Tag Manager for session recording and heatmap tools. ---