DocsMigrations

Migrations

Migrate from RudderStack

4 min readReviewed July 2026

RudderStack is commonly an event router rather than the only durable owner of event history. The safest migration usually has two parts:

  1. route new selected events to GraphJSON
  2. backfill required history from the warehouse, lake, or source system where RudderStack delivered it

Do not assume the RudderStack control plane contains a complete, replayable history for every source and destination.

Inventory the existing pipeline#

For every source, record:

source name and ID:
event types:
SDK or cloud source:
current destinations:
warehouse or archive destination:
transformations:
tracking plan:
consent behavior:
delivery monitoring:
owner:

Identify which destination currently holds the historical event-level data. That system is normally the backfill source.

Choose the live routing pattern#

Recommended:

RudderStack source
  → destination-specific filter and transformation
  → HTTPS adapter you operate
  → GraphJSON bulk logging API

RudderStack documents Webhook sources and destinations and Transformations for custom routing and payload changes.

Use an adapter because:

  • GraphJSON expects its API key inside a specific request body
  • the key should not become an event property
  • the adapter can validate, batch, retry, and quarantine
  • the adapter can verify that calls originate from the intended pipeline

Do not publish the GraphJSON key in a transformation or client-side source.

Map the event specification#

Typical RudderStack events use concepts such as:

RudderStack GraphJSON
messageId stable event_id
event on a track call bounded event
originalTimestamp or source occurrence time request timestamp
userId user_id
anonymousId anonymous_id
properties allowlisted event fields
context selected platform, source, and campaign context
type source call type

Use the current RudderStack event specification for the exact source contract.

Do not copy the full context, traits, integrations configuration, or provider payload into GraphJSON.

Handle call types deliberately#

Track#

Map approved business actions to stable past-tense event names:

Order Completed → order_completed
Report Exported → report_exported

Identify#

An identify call describes traits, not necessarily a business event. Use it only when it represents a meaningful identity transition, such as:

anonymous_identity_linked
user_profile_state_published

Do not create an event for every trait refresh.

Group#

Map account or workspace membership changes when they are real transitions. Do not attach current group traits retroactively to all old events.

Page and screen#

Keep approved, normalized paths or screen names. Remove arbitrary query parameters and high-cardinality routes.

Normalize in the adapter#

function normalizeRudderTrack(input) {
  if (input.type !== "track" || !input.event || !input.messageId) {
    throw new Error("Unsupported RudderStack event");
  }

  const occurredAt = new Date(
    input.originalTimestamp || input.timestamp
  );

  if (Number.isNaN(occurredAt.getTime())) {
    throw new Error("Invalid occurrence time");
  }

  const allowed = {
    event: toStableEventName(input.event),
    event_id: `rudder:${input.messageId}`,
    source: "rudderstack",
    user_id: input.userId || null,
    anonymous_id: input.anonymousId || null,
    account_id: input.properties?.account_id || null,
    plan: input.properties?.plan || null,
    source_name: input.context?.source || null,
    schema_version: 1
  };

  return {
    timestamp: Math.floor(occurredAt.getTime() / 1000),
    body: allowed
  };
}

Replace toStableEventName with an explicit mapping table. Automatically lowercasing arbitrary names does not preserve meaning.

Filter by destination#

Only route:

  • approved event names
  • approved sources
  • production environment
  • consent-eligible traffic
  • properties on the GraphJSON allowlist

The transformation should remove forbidden fields before the webhook. The adapter should enforce the allowlist again.

Version the transformation and adapter mapping together.

Backfill from the durable destination#

If RudderStack delivered to a warehouse or lake:

  1. query the source event table for the required interval
  2. preserve messageId and source occurrence time
  3. apply the same live mapping version
  4. stage normalized NDJSON
  5. validate and import in batches
  6. reconcile by source, event, day, and identity

If no durable destination exists, ask RudderStack what export or replay options apply to the specific deployment. Do not promise historical continuity until the recoverable source is confirmed.

Preserve source transformations#

The warehouse may contain events after a sequence of:

  • source SDK normalization
  • consent filtering
  • tracking-plan enforcement
  • user transformation
  • destination transformation

Choose whether GraphJSON receives:

  • the canonical post-source event
  • the existing warehouse representation
  • a destination-specific normalized event

Document the exact boundary. Reimplementing only one old transformation can change event names, identity, redaction, or types.

Manage dual delivery#

Use a cutover matrix:

Period Existing destination GraphJSON
historical backfill source import
validation live live shadow
cutover live or retained fallback live
after cutover intentional decision live

During shadow delivery compare:

  • track calls selected
  • adapter accepted and quarantined
  • GraphJSON accepted
  • occurrence-time delay
  • counts by event and source
  • user and account coverage
  • required property completeness

Avoid source-side breakage#

Adding GraphJSON must not:

  • block the RudderStack source
  • alter payloads sent to existing destinations unexpectedly
  • expose the GraphJSON key to the client
  • change consent behavior
  • turn destination failures into application failures

Use destination-specific transformation and independent failure handling.

Migration checklist#

  • Every source, transformation, destination, and owner is inventoried.
  • The durable historical source is identified.
  • Live events reach a secure adapter rather than exposing the GraphJSON key.
  • Track, identify, group, page, and screen calls have explicit policies.
  • Event names use a mapping table.
  • messageId and occurrence time are preserved.
  • Context, traits, and provider payloads are allowlisted.
  • Consent and environment filtering happen before delivery.
  • Live and historical paths use the same mapping version.
  • Shadow delivery reconciles source, adapter, and GraphJSON counts.
  • Existing destinations remain unchanged unless explicitly migrated.
  • Cutover and rollback boundaries are recorded.
Need a hand?

Tell us what you’re building and we’ll point you in the right direction.

Contact support