I opened BitMEX's live site, read the tracking implementation in the page bundle, mapped it against the JD, and wrote the blueprint I'd execute in the first 90 days: the path from a user's tap → Segment → Mixpanel / GA4 / AppsFlyer, managed in version control. Anything I couldn't verify from outside is marked as such.
This role isn't "add an analytics tool." It's owning the integrity of the data that Marketing, Growth and Finance make decisions on - at an exchange where a mislabelled or double-counted event can mean misallocated ad spend and a funnel nobody trusts. The JD's own words: be the "Source of Truth," own the "technical DNA of the analytics stack."
A live look at bitmex.com confirms the core is already in place: Twilio Segment as the CDP, Google Tag Manager, and Sentry, with Mixpanel, GA4 and AppsFlyer downstream. So the work isn't greenfield; it's governance, reliability and version control: a typed tracking plan, money events moved server-side, the config in Terraform, attribution wired for mobile, and monitoring that catches a broken pipeline before it reaches a dashboard. This deck covers the stack as it stands, the failure modes specific to a derivatives exchange, and the blueprint and code I'd ship.
BitMEX's tokenised commodity & equity perpetuals reportedly jumped 500%+, with TradFi perps hitting ~$30B+ weekly volume by Q1 2026 (silver, gold, crude). Each new product line is a new funnel and a new set of events to instrument - directly why a data-systems owner is needed now, not later.
Reports since Feb 2025 of BitMEX exploring a sale. An analytics stack that's clean, documented and in code is exactly what survives diligence and an ownership transition - a quiet but real argument for this role's mandate.
Public reporting (Messari, Investing.com/Chainwire, RootData); figures approximate - I'd confirm against internal numbers. Point isn't the exact %, it's that growth + new products = more events to get right.
A "Deposit" or "Order Filled" event feeds finance, incentive payouts and ad ROAS. "Roughly right" - fine for a blog pageview - is not acceptable here. These belong server-side, idempotent, reconciled against the ledger.
A regulated exchange can't fire every event to every tool in every region. The CDP has to enforce consent and geo-routing at the pipeline layer - a governance problem, not a tagging one.
Signup → KYC → deposit → first trade → retained trader is a long, high-stakes funnel. Every drop-off is worth real money, so the schema has to model it precisely and consistently across web and app.
The JD reads as one job but is really three distinct skill sets, and interviews will probe each. The combination is uncommon: most analytics people don't write Terraform; most engineers don't think in funnels.
Write the tracking code, client and server. Build webhook / REST integrations from the backend to third-party tools. Ship a typed instrumentation layer so events can't drift.
Own the event taxonomy and the "Source of Truth" schema; manage Segment / destinations as code in Terraform; audit pipelines so dashboards stay trustworthy.
Turn "which campaign drives funded accounts?" into a concrete tracking spec, and explain back to Marketing what the data can and can't say. The JD calls this out explicitly.
| JD responsibility | What it actually means day-to-day | Covered in |
|---|---|---|
| Design & maintain data flow to Mixpanel/GA4/AppsFlyer via Segment | Own the Segment Identify/Track/Page contract and the destination fan-out; keep client + server sources consistent. | §06 |
| Manage analytics infra with Terraform, not manual config | Sources, destinations, tracking-plan, warehouse connectors as version-controlled, peer-reviewed code. | §07 |
| Translate Marketing questions → technical tracking requirements | Run intake, write the spec, push back when a request would corrupt the schema. | §05 |
| Monitor & audit pipelines to prevent inaccurate dashboards | Volume-anomaly alerts, schema validation, a reconciliation runbook. | §09 |
| Maintain the org data-schema documentation ("Source of Truth") | A versioned tracking plan in git that is the contract between eng and analytics. | §05 |
| Configure mobile + web attribution - deep-linking & postbacks | AppsFlyer S2S events, deferred deep links, postbacks, SKAN, identity resolution. | §08 |
I fetched bitmex.com and read the page bundle. What I could verify from outside, and what's inferred from the JD - kept clearly separate.
Two write keys ≈ two sources (trading UI + general web). The questions that actually matter: is there a server-side (cloud-mode) source feeding money events; which destinations run device-mode (bundled SDK, ad-blockable) vs cloud-mode (sent from Segment's servers, survives blockers); and is the plan enforced by Protocols or implicit in code? The device/cloud split alone decides how much data the blockers eat.
The client-side SEGMENT_WRITE_KEY is visible in the bundle (a public write key - normal). Redacted here anyway.
The failure modes this role exists to prevent. None throw an error - they just produce a confident, wrong number. Each maps to a fix later in the deck.
Same "Deposit" fires client- and server-side with no shared messageId → revenue looks 2×, ad budget chases a ghost. Fix: idempotency + one owner per event.
Anonymous pre-signup activity never gets aliased to the userId → the funnel shows two people where there's one, acquisition source lost. Fix: deterministic identify/alias.
Order Filled, order_filled, OrderFilled all in prod → Mixpanel reports fracture. Fix: tracking plan + Protocols validation.
Privacy-heavy users drop client events; web-only signup counts read low, mobile vs web looks skewed. Fix: server-side for anything that matters.
Someone toggles a destination in the Segment UI; nobody knows when or why; staging ≠ prod. Fix: everything in Terraform.
Deferred deep link not wired → installs land on a generic screen and attribute to "organic"; paid channels look dead when they're working. Fix: AppsFlyer S2S + deep links.
What I'd do in the first two weeks - low-risk, high-signal, and each one builds trust before touching anything load-bearing.
Pull the full list of live events firing into Segment + their destinations; flag naming inconsistencies and orphan events nobody reads. Output = the first honest schema snapshot.
Signup → KYC → deposit → first trade: confirm each step is tracked, where it fires (client vs server), and whether the numbers reconcile with finance. Find the leaks.
Even before Protocols: a versioned spec doc as the single source of truth, so the next event added goes through review, not vibes. §05
If sources/destinations are UI-managed, terraform import them so the current reality is captured as code before any change. §07
One simple monitor: if a top-10 event's daily volume swings ±X%, page me. Cheapest possible insurance against a silent broken dashboard. §09
Trace one real user from anonymous → signup → funded across the pipeline; confirm alias/identify stitches history correctly. This is where the worst funnel bugs hide.
The deliverable the JD names directly. A tracking plan is a contract: Object-Action naming, typed properties, one owner, versioned in git, enforced by Segment Protocols so a malformed event is blocked at the edge - not discovered three weeks later in a broken Mixpanel funnel.
| Event | When | Key properties | Source | Destinations |
|---|---|---|---|---|
| Account Created | Signup completes | method, referral_code, utm_* | server | Mixpanel · GA4 · AppsFlyer · WH |
| KYC Submitted | Docs uploaded | tier, country, attempt_no | server | Mixpanel · WH |
| KYC Approved | Verification passes | tier, days_since_signup | server | Mixpanel · AppsFlyer · WH |
| Deposit Completed | Funds credited (ledger) | amount_usd, asset, method, is_first | server | Mixpanel · GA4 · AppsFlyer · WH |
| Order Placed | Order submitted | symbol, side, order_type, leverage | client | Mixpanel · WH |
| Order Filled | Trade executes | symbol, contracts, notional_usd, fee | server | Mixpanel · AppsFlyer · WH |
| Funnel Step Viewed | Onboarding screen seen | step_name, step_index | client | Mixpanel · GA4 |
New event? PR against the plan → review → Protocols enforces it in prod and routes violations to a blocked-events stream I monitor. Adding tracking becomes a reviewable change with an owner, a description and a diff - versioned like any other code.
The single most important design call in the whole stack, and the one that fixes most of the §04 failure modes.
UI interactions, screen views, funnel-step events. Fast to ship, rich context - but lossy: ad-blockers, privacy modes and dropped tabs eat a real fraction. Fine for directional product signals.
Deposits, fills, KYC, account state - emitted from the backend via Segment's HTTP/Node source. Reliable, tamper-resistant, can't be blocked. For an exchange, anything that touches money or identity lives here.
Caveat: alias resolves differently per destination - Mixpanel merges distinct IDs, GA4 keys on its own user_id, AppsFlyer on device IDs - so identity must be reconciled in the warehouse (or Segment Unify/Profiles), never assumed consistent downstream. Get it wrong and every funnel and attribution report is quietly off.
Pick one canonical source (server for money) and give the event a deterministic messageId from a domain key (e.g. deposit_id). Segment drops identical messageIds within a ~24h window - so for late retries or cross-source dupes outside that window, you dedupe again at the warehouse on the domain key, since double-counted revenue is the most expensive failure mode here. See the code →
The unusual JD requirement, and the strongest signal of what they want: most analytics teams click around in dashboards; this one wants the stack in version control. The reasoning: manual config in a UI produces an analytics stack nobody can review, reproduce, or roll back.
# segment/destinations.tf - a destination as reviewable code (illustrative) resource "segment_destination" "mixpanel_prod" { source = segment_source.web_ui.id name = "Mixpanel - Production" enabled = true metadata_id = data.segment_destination_metadata.mixpanel.id settings = jsonencode({ apiSecret = var.mixpanel_api_secret # from secrets manager, never in git token = var.mixpanel_token people = true setAllTraitsByDefault = false # explicit traits only - no PII leakage }) } # a change to this destination is now a pull request: # terraform plan → review the diff → apply on merge → audit trail for free
Illustrative of the segmentio/segment provider, which wraps Segment's Public API - so each destination's settings schema differs and API rate limits apply on bulk applies. Two gotchas to plan for up front: Terraform state stores destination secrets in plaintext → encrypted remote backend (S3+KMS / TF Cloud) with state locking, never local state; and any resource already created in the UI must be terraform import-ed before the first apply or it gets clobbered.
"Deep-linking and postbacks" in the JD = AppsFlyer. At a privacy-heavy crypto exchange this is the hardest part of the stack, and where deterministic tracking has mostly died - so the design has to be server-to-server first.
A deep link opens the app to specific content when it's installed. A deferred deep link survives the install: a new user clicks a campaign, installs, and still lands on the right screen after first open - the difference between a smooth funnel and a drop-off.
Server-to-server notifications back to ad networks confirming an install or in-app event, so the network can attribute and optimise. The reliable backbone when pixels are blocked.
Send Deposit Completed / Order Filled to AppsFlyer from the backend, not the device - accurate ROAS even when the client drops the event. Mirrors the server-side rule from §06.
Post-ATT iOS runs through SKAdNetwork / AdAttributionKit: a coarse/fine conversion value, Apple-randomised postback timers, and a crowd-anonymity threshold that nulls the source ID on low-volume campaigns. The real work is mapping deposit / first-trade into the limited SKAN conversion-value schema, then leaning on S2S + incrementality reads - deterministic per-user attribution is gone on iOS.
AppsFlyer plugs in as a Segment destination (device-mode SDK for installs, cloud-mode for server events), so its appsflyer_id has to be joined to Segment's anonymousId then userId for an install to attribute to the human who later funds an account. That join turns "1,000 installs" into "40 funded traders at $X CAC", the number Growth is judged on.
"Monitor and audit pipelines to prevent inaccurate dashboards." The interview version of this is almost always some flavour of the reconciliation question - here's the structured answer I'd give, which doubles as the runbook I'd actually build.
Per-event volume anomaly checks (z-score / day-over-day) plus Segment Delivery Overview for silent destination drops; Protocols violation stream; dbt source-freshness on warehouse tables.
Segment debugger + single-user trace; a nightly dbt test reconciling money events to the ledger (row counts and summed amount_usd must match within tolerance, else alert).
Typed events at compile time; Protocols at the edge; dbt not_null / unique / accepted_values tests in CI; every config change reviewed in Terraform.
A GDPR / CCPA deletion request has to reach everywhere the user's events went. Segment's user deletion & suppression API propagates a right-to-be-forgotten across connected destinations and the warehouse from a single call, and consent / geo state should gate routing before events leave Segment, not after. At a regulated exchange this is required, not optional.
Talk is cheap - here are the actual artifacts behind the blueprint. The theme throughout: make the schema impossible to violate by accident, and make money events reliable.
// events.ts - the tracking plan, as types. A typo or missing prop won't compile. type EventMap = { 'Deposit Completed': { amount_usd: number; asset: string; method: string; is_first: boolean } 'Order Filled': { symbol: string; contracts: number; notional_usd: number; fee: number } 'KYC Approved': { tier: 'basic' | 'advanced'; days_since_signup: number } } export function track<K extends keyof EventMap>(event: K, props: EventMap[K]) { // one funnel for every event → impossible to fire an off-plan name or shape analytics.track({ event, properties: props }) } track('Deposit Completed', { amount_usd: 500, asset: 'USDT', method: 'onchain', is_first: true }) // track('Deposit Completed', { amount: 500 }) ❌ won't compile - wrong shape, caught in CI
import { Analytics } from '@segment/analytics-node' const analytics = new Analytics({ writeKey: process.env.SEGMENT_SERVER_KEY }) export async function onDepositCredited(deposit) { analytics.track({ userId: deposit.userId, event: 'Deposit Completed', properties:{ amount_usd: deposit.usdValue, asset: deposit.asset, method: deposit.method, is_first: deposit.isFirst }, // deterministic id from the domain key → a retry collapses to ONE event, not two messageId: `deposit:${deposit.id}`, timestamp: deposit.creditedAt, }) }
function usePageTracking() { const { pathname } = useLocation() useEffect(() => { window.analytics?.page({ path: pathname }) // one place, every route, never forgotten }, [pathname]) }
Plus the integration piece the JD asks for: a small Node webhook receiver that takes events from internal services and forwards them into Segment with retries + the same idempotency rule - the "backend-to-third-party" glue, written once and reused.
A working build of this pipeline: step a trader through the funnel and watch typed events get validated, deduped, identity-stitched and fanned out to destinations in real time. Schema enforcement and idempotency run live, not just described.
Sequenced deliberately: earn trust by making the existing numbers correct first, then add governance, then extend to attribution. No load-bearing change before the stack is captured as code.
This role lives at the seam between Marketing and Engineering - and that seam is where I've spent my career. I've grown and instrumented audiences for crypto products (deBridge, KIP Protocol), done front-line technical support at a Bitcoin L2 (Build on Bitcoin) and an Ethereum privacy app (Aztec / zk.money), and run SEO & analytics with GA / Search Console end-to-end - so I read a funnel and a tracking spec with equal comfort. I'm the translator the JD describes, with the exchange-domain context already loaded.
Marketing↔Eng translation · analytics & attribution thinking · GA/GSC · crypto-exchange domain · JS/TS familiarity · the data-integrity instinct this role is built on.
Rather than list tools on a CV, I did the work: verified your live stack, wrote the tracking plan, the Terraform pattern and the typed event layer in this deck. That's how I ramp on any stack - by building on it.
Remote-Asia, independent, self-directed - the same way I've worked with global teams for years. I ship, measure, and tell you what the data can and can't support.
Fetched bitmex.com (Jun 2026) and inspected the returned page bundle for third-party integration markers. Found, verbatim in the source: SEGMENT_WRITE_KEY, SEGMENT_UI_WRITE_KEY, a Google Tag Manager container, and a Sentry ingest endpoint. Mixpanel / GA4 / AppsFlyer are not directly visible to an external web probe (they live downstream of Segment and inside the mobile app) - they're taken from the JD, and labelled as such. No private systems were accessed; everything here is from the public page and the public job description.
Role: Growth Engineer (Data Systems) - Greenhouse
Company: bitmex.com · BitMEX Blog
Stack docs: Segment Spec · Protocols · AppsFlyer S2S
Claims are split into confirmed (verified live from the public site) and inferred (from the JD) throughout. Code snippets are illustrative of approach, not production config. This is an unsolicited audit prepared as interview homework - happy to walk through any section live.
Prepared by Edward Tay · for the BitMEX Growth Engineer (Data Systems) role · Jun 2026