Skip to main content

The branch-decision contract: from CDP to flows

A customer browses your store, adds an item to their cart, and checks out. The CDP records three events — Product Viewed, Add to Cart, Order Completed — on the same append-only stream. A flow reads the Order Completed event, evaluates whether the order total exceeds $100, and branches: yes sends a loyalty discount, no sends a standard confirmation. The audit log records both the fact and the branch the flow took. That chain — a CDP fact driving a flow branch, with an audit verdict recording the outcome — is the branch-decision contract. This page defines the envelope that carries that hand-off, walks through one end-to-end example, and documents the three guard-rails that prevent the two halves from reading different versions of the same truth.

What a branch-decision event is

A branch-decision event is not a separate event type or a new API resource. It is the intersection of three existing primitives:
  1. The CDP fact — a behavioral event (track call), a segment membership change, a score crossing a threshold, or a tracking-plan signal. The CDP event model defines the envelope it arrives in and how it binds to a contact.
  2. The flow condition node — the point in a flow execution where a condition node evaluates a predicate (segment.repeat_buyers = true, order.total > 100, intent = 'refund') and picks a handle (yes / no, an intent name, a sentiment bucket). The execution enters the routed arm.
  3. The audit verdict — the row the audit log writes when the branch is taken, recording which predicate was evaluated, which handle was chosen, and at what time. The audit ledger model owns the schema.
These three pieces together form the branch-decision contract: the CDP side captures what happened, the flow side decides what to do next, and the audit side records which path was taken and why.

What the two subgroups actually cover

The Concepts tree divides the question cleanly. Whichever subgroup you landed on tells you which half you own:
  • CDP & Intelligence covers the capture side — CDP events, identity resolution, segments, scoring, and the rendered analytics surface. Its output is a fact a flow can read.
  • AI & Automation covers the action side — flows, runs (executions), and follow-up steps. Its output is a branch and a recorded step trace — not a new CDP fact.
The hand-off between them is a read, not a write. A flow condition node reads the CDP event or segment state at execution time; it never mutates the CDP record. The audit log records what the flow decided; it never reinterprets the CDP fact.

The two flagship pages, one paragraph each

  • CDP & Intelligence’s flagship is the Analytics pipeline: raw events feed into rollup groupings, which feed into statistics, which feed into the charts a customer reads — all on the same append-only event stream the CDP event model defines.
  • AI & Automation’s flagship is the Flow execution model: a flow definition becomes a run through the trigger catalog and node semantics, advancing node by node through handles until a webhook hand-off or a terminal state closes it.
A reader who landed here from either flagship now has the contract that holds them together.

The build-your-first-branch contract

When a flow condition node evaluates a CDP fact and picks a branch, the execution writes one step in the execution trace. The canonical shape of that step is:
The fields:
  • predicate — the expression the condition node evaluated, in the same syntax you authored in the flow builder.
  • resolved_value — the result of resolving the predicate against the execution context at that moment. This is the value the CDP fact produced when the branch read it.
  • selected_handle — the outgoing edge the flow took. For a binary condition, yes or no; for an AI classifier, the intent name; for an A/B split, the variant label.
  • context_snapshot — the subset of execution context the predicate read, captured so the audit log can reconstruct what the branch saw without replaying the whole run.
  • timestamp — the moment the branch was evaluated. This is the wall-clock time on the flow executor, not the CDP event’s originalTimestamp.
This step appears inside the execution’s steps[] array and is addressable by step_id. The audit log reads the same row; nothing in the chain invents a separate branch-event type.

Worked example: a loyalty-discount branch

A repeat-buyer segment drives a flow that sends a loyalty discount to high-value orders. Here is the end-to-end chain:

1. The CDP leg — the fact is recorded

A track call lands on the CDP event model ingest surface:
The event is appended to the tenant’s event stream. A real-time segment computation runs and tags the contact as repeat_buyers = true (unmodified since Q2 2026; the segment gate is tenant-owned, per the compliance tenant-owned model).

2. The flow leg — the branch is taken

A published flow with a webhook trigger and a condition node fires:
The executor walks the flow graph. At the condition node (condition_loyalty), it resolves:
The yes arm routes to a sendEmail action that dispatches a 15% loyalty discount template. The per-resume execution budget (300 s, per the flow-execution model) applies from the moment the condition node is reached.

3. The audit leg — the verdict is written

The audit log records a branch-verdict row:
The audit row is queryable by execution_id and by condition_node, so you can ask two questions: “what did this run decide?” and “what did this condition node decide across all runs?” The verdict field carries the same terminal status the execution carries — if the execution timeouts, the audit verdict is timeout, not completed.

Three guard-rails

Three constraints prevent the CDP fact and the flow branch from reading different versions of the truth. Each one answers a specific “why did my branch pick the wrong handle?” question.

1. The no-ACK window (~30 minutes for SMPP)

A branch that reads a sent-leg delivery receipt inside the carrier’s no-DLR grace window is reading a pending fact, not a final fact. For SMPP channels, the grace window is 30 minutes — a sent status that has not yet received a delivered or failed DLR is in-flight, not terminal. If your flow condition reads message.status = 'sent' and branches within that window, the audit row records the verdict as pending_dlr, not completed. The timer contract is defined in the DLR model: two planes. To avoid the most common audit muddle, configure your condition node to gate on the delivery receipt parent (dlr_anchor) rather than the carrier status field, and set a minimum age (at least 30 minutes) on the receipt before branching.

2. Ownership — only one side decides

A branch on the same fact must have exactly one owner. When a flow’s condition node and a CDP audience rule both claim to decide a routing outcome, the two decisions race — and the audit log records whichever one fired last, not whichever one was correct. The contract resolves this: the flow owns the branch decision. A CDP segment or score supplies the input (a true/false, a numeric value, a trait), and the flow’s condition node consumes it. A CDP rule should never write a branch outcome that a flow could also write — if both systems need the same decision, route the CDP rule into a flow trigger, so the flow remains the single owner of the selected_handle.

3. The execution timeout (300 seconds)

Every flow run has a per-resume execution budget of 300 seconds (defined in the flow-execution model). A run that exceeds this budget finalises as timeout — a hard failure verdict, not completed, and not an in-flight run that “caught up.” A branch that follows a timed-out run without treating the verdict as a terminal failure is the second way an audit row can read a wrong conclusion. When your flow condition reads the outcome of a prior run, check the prior run’s status before consuming its selected_handle: timeout means the branch never actually completed, and the handle is not a valid input.

The return path: what the audit log reads back

After the branch is taken and the flow completes (or times out), two read surfaces surface the same verdict:
  • The execution trace (GET /api/v1/flows/:flow_id/executions/:id) includes the condition step with its selected_handle and context_snapshot. This is the flow executor’s own record — the action-side view.
  • The audit log (GET /api/v1/audit?execution_id=exec_branch01) records the same branch verdict, cross-referenced by execution ID. This is the system-of-record view — the one group-by-kind charts and aggregate reads consume.
The two surfaces agree because they read from the same source: the execution’s steps[] array. There is no cross-subgroup exchange that can diverge — the flow writes the branch step, and the audit log reads the same step. A group-by-condition-node chart that queries the audit log and a run-level trace that queries the execution API return the same selected_handle for the same execution_id.