How to preserve event order when syncing Bubble changes to BigQuery

Ordered event tiles pass through a sequence checkpoint into an analytical warehouse while duplicate and missing events are held aside.

The short answer

Do not depend on completion order when Bubble schedules a workflow on a list. Bubble's official documentation says those workflows run in parallel and can overlap even when an interval is configured. If create, update, and delete events for the same record must be applied in order, assign a monotonically increasing sequence to each record's changes and make the receiver reject duplicates, hold gaps, and apply only the next expected sequence.

This answers a forum scenario where an external change list contained create → update → delete, but parallel processing could finish as delete → create → update, leaving data that should no longer exist. The discussion recommended recursion when strict sequence is required. That is valid inside Bubble, but an external warehouse sync should also defend itself against duplicate and late delivery.

Why an interval is not ordering

Bubble's Scheduling API workflows guide states that scheduling on a list creates one workflow per item and does not wait for one to finish before moving to the next. An interval changes scheduled start times; it does not guarantee that a slow earlier request finishes before a fast later request.

Use one of two designs:

  1. Sequential production: a recursive Bubble workflow processes one item, records success, and schedules the next. This is appropriate when the next event depends on the previous result.

  2. Parallel delivery with ordered consumption: events may arrive in any order, but the integration service applies them according to a trusted per-record sequence.

The second design normally provides better throughput because changes for different records can proceed concurrently while each record retains its own ordering constraint.

Define a durable event envelope

Each event needs at least:

{
  "event_id": "evt_01J...",
  "record_id": "bubble_thing_123",
  "tenant_id": "tenant_456",
  "sequence": 17,
  "operation": "update",
  "occurred_at": "2026-10-02T12:00:00Z",
  "payload": { "status": "approved" }
}
{
  "event_id": "evt_01J...",
  "record_id": "bubble_thing_123",
  "tenant_id": "tenant_456",
  "sequence": 17,
  "operation": "update",
  "occurred_at": "2026-10-02T12:00:00Z",
  "payload": { "status": "approved" }
}
{
  "event_id": "evt_01J...",
  "record_id": "bubble_thing_123",
  "tenant_id": "tenant_456",
  "sequence": 17,
  "operation": "update",
  "occurred_at": "2026-10-02T12:00:00Z",
  "payload": { "status": "approved" }
}

event_id deduplicates the delivery. record_id identifies the entity. tenant_id prevents cross-tenant joins. sequence determines order for that record. occurred_at is useful evidence but should not be the sole ordering key: timestamps can collide, arrive late, or be generated by clocks with different offsets.

Generate the sequence in one trusted write path. If Bubble cannot atomically increment a counter with the business update, use a change-log record whose creation order and immutable ID are governed by the integration workflow. Never let a browser choose the authoritative sequence.

Apply only the next sequence

The tested reference function is in forum-operations-reference.mjs. It follows three rules:

  • sequence <= last_sequence: acknowledge as a stale or duplicate delivery;

  • sequence == last_sequence + 1: apply and advance the checkpoint;

  • sequence > last_sequence + 1: hold the event and request or wait for the missing sequence.

The automated test sends sequence 1 twice, then sequence 3 before sequence 2. The duplicate is ignored, the gap is blocked, and the final delete is applied only after the update arrives. This produces the intended empty final state.

Use a transaction around the checkpoint read, target update, and checkpoint advance. Two workers must not both observe last_sequence = 16 and apply different sequence-17 events.

Land events before updating reporting tables

A robust BigQuery design uses two layers:

  1. an append-only staging table containing every accepted event;

  2. a current-state table updated from validated events.

The staging table preserves evidence for replay and reconciliation. The current-state table is optimized for reporting. Google documents that a BigQuery MERGE statement can combine inserts, updates, and deletes atomically within one statement.

A simplified merge can choose the highest accepted sequence per record and update only when it is newer than the target:

MERGE analytics.customer_state AS target
USING validated_latest_event AS source
ON target.tenant_id = source.tenant_id
AND target.record_id = source.record_id
WHEN MATCHED AND source.sequence > target.last_sequence AND source.operation = 'delete'
  THEN DELETE
WHEN MATCHED AND source.sequence > target.last_sequence
  THEN UPDATE SET status = source.status, last_sequence = source.sequence
WHEN NOT MATCHED AND source.operation != 'delete'
  THEN INSERT (tenant_id, record_id, status, last_sequence)
       VALUES (source.tenant_id, source.record_id, source.status, source.sequence);
MERGE analytics.customer_state AS target
USING validated_latest_event AS source
ON target.tenant_id = source.tenant_id
AND target.record_id = source.record_id
WHEN MATCHED AND source.sequence > target.last_sequence AND source.operation = 'delete'
  THEN DELETE
WHEN MATCHED AND source.sequence > target.last_sequence
  THEN UPDATE SET status = source.status, last_sequence = source.sequence
WHEN NOT MATCHED AND source.operation != 'delete'
  THEN INSERT (tenant_id, record_id, status, last_sequence)
       VALUES (source.tenant_id, source.record_id, source.status, source.sequence);
MERGE analytics.customer_state AS target
USING validated_latest_event AS source
ON target.tenant_id = source.tenant_id
AND target.record_id = source.record_id
WHEN MATCHED AND source.sequence > target.last_sequence AND source.operation = 'delete'
  THEN DELETE
WHEN MATCHED AND source.sequence > target.last_sequence
  THEN UPDATE SET status = source.status, last_sequence = source.sequence
WHEN NOT MATCHED AND source.operation != 'delete'
  THEN INSERT (tenant_id, record_id, status, last_sequence)
       VALUES (source.tenant_id, source.record_id, source.status, source.sequence);

This query alone does not detect a missing intermediate event. Perform gap validation before placing an event in validated_latest_event, or use a business rule that safely allows last-write-wins snapshots. State the semantic choice explicitly.

Events versus snapshots

Not every sync requires strict event replay. If each payload is a complete snapshot and deletion is represented by a durable tombstone, applying the newest sequence can be sufficient. If changes are patches—“add 5,” “remove tag,” “delete”—skipping an event changes the result and gaps must block processing.

Choose one contract:

  • Snapshot contract: every event contains complete current state; newest valid sequence wins.

  • Event contract: every mutation matters; consume contiguous sequences.

Do not mix the two silently. A partial Bubble payload treated as a snapshot can erase fields; a snapshot treated as a patch can leave old fields behind.

Backfills and live changes

Assign a backfill boundary. Record the source watermark, export the historical snapshot, then replay events after that watermark. Without a boundary, a record changed during the export may appear twice or be overwritten by an older backfill row.

Bubble's Data API is paginated; the Data API request guide documents cursor and limit parameters and a default page size of 100. Store the cursor and page checksum for restartability. Do not assume that completing page N means the dataset stayed unchanged during the complete traversal.

Verification checklist

  • Send create, update, and delete in order; the final row is absent.

  • Deliver the same event twice; the second delivery changes nothing.

  • Deliver sequence 3 before 2; sequence 3 waits or fails with a bounded gap result.

  • Run two tenants with the same record ID; their checkpoints remain separate.

  • Crash after staging but before merge; replay does not duplicate the state change.

  • Start a backfill while live changes continue; the watermark prevents stale overwrite.

  • Query for sequence gaps and unresolved held events.

  • Verify delete tombstones or deletion events survive long enough for every consumer.

The pure ordering logic passed local tests on 2026-10-02. Publication as a step-by-step Bubble tutorial still requires a disposable app and BigQuery dataset test.

For schema and relationship design, read Moving relational Bubble data into BigQuery.

Primary CTA: Describe the Bubble records and change types your reporting pipeline must preserve.

Sources checked: Bubble Scheduling API workflows, Bulk operation methods compared, Data API requests, and BigQuery DML syntax, checked 2026-10-02.

An illustrative event-ordering flow stages each change, compares its record sequence with a checkpoint, then applies, ignores, or holds the event.
An illustrative event-ordering flow stages each change, compares its record sequence with a checkpoint, then applies, ignores, or holds the event.

Help shape the cloud layer for Bubble apps

Help shape the cloud layer for Bubble apps

Help shape the cloud layer for Bubble apps

Bubble2X is building a Bubble-to-Google-Cloud integration for controlled customer-file workflows.

Newsletter

Get tips, product updates, and insights about building with Bubble2X.

Subscribe to Bubble2X updates. See our Privacy Notice.

© 2026 Bubble2X. Product in development.

Bubble2X is building a Bubble-to-Google-Cloud integration for controlled customer-file workflows.

Newsletter

Get tips, product updates, and insights about building with Bubble2X.

Subscribe to Bubble2X updates. See our Privacy Notice.

© 2026 Bubble2X. Product in development.

Bubble2X is building a Bubble-to-Google-Cloud integration for controlled customer-file workflows.

Newsletter

Get tips, product updates, and insights about building with Bubble2X.

Subscribe to Bubble2X updates. See our Privacy Notice.

© 2026 Bubble2X. Product in development.