Event pipelines
An event pipeline is a deterministic flow that receives an inbound event and does fixed work with it: a lead-form submission, a third-party webhook, or an in-app action arrives at the pipeline’s endpoint, gets normalized, and runs through an ordered list of actions — write the row to a dataset, send an email, call a connected integration, broadcast a live update, or return a custom response. Pipelines are how a page captures leads, and how Erdo enriches and routes them without an agent in the loop on every event. Each pipeline has:- a slug and name — a stable, human-readable handle (e.g.
acme-leads-a1b2c3d4), - a state —
active,disabled, orproposed, - a source kind —
browser_form,third_party_webhook,custom_http,internal, …, - an inbound endpoint URL — where events are POSTed,
- an auth mode — how inbound events are verified (HMAC, shared secret, provider signature, signed-in viewer, or public),
- the actions it runs and the datasets it writes to, and
- an execution log — every event it received, with status (
accepted/rejected/failed), a payload preview, the response, and any error.
Where data lands
A pipeline’sdataset.write actions persist rows to a dataset — per-row, SQL-queryable
storage. To see captured leads, query the dataset the pipeline writes to (shown as
write_target_datasets), not the pipeline itself. The pipeline’s execution log tells you
whether events are arriving and being accepted; the dataset holds the rows.
Emails a pipeline sends
A pipeline that emails on an event — a lead alert to your sales inbox, a confirmation back to the person who submitted the form — has Erdo compose the message. The pipeline supplies the content (a heading, the captured fields as label/value rows, a button through to the page the lead came from) and Erdo renders it in the standard Erdo styling, with a plain-text version alongside for clients that block HTML. Addresses and links in the fields become clickable, so a lead alert can be replied to from the notification itself.Pipeline purpose
Every pipeline declares apurpose — its role — so you can tell capture flows apart from
analytics infrastructure without guessing from names:
record_capture— a pipeline whose job is persisting submitted records to a dataset (it has at least onedataset.writeaction). This is how you find which datasets receive a flow’s records: filter pipelines bypurpose = record_captureand read theirwrite_target_datasets(each entry is a fully-qualifiedorg-slug.dataset-slug). You can set this explicitly when creating or updating a pipeline; acustompipeline that writes to a dataset is also promoted torecord_captureautomatically at save time.page_events— the managed first-party page-analytics pipeline described below. Reserved for Erdo’s own provisioning; you can’t set it.custom— everything else: a webhook or enrichment flow that doesn’t persist records.
record_capture pipelines and
read their write_target_datasets, then query that dataset.
Every dataset.write row is stamped at ingestion with columns your transform doesn’t set:
variant, artifact_id, page_url (from the page envelope), plus a set of request signals that
let you keep your metrics clean and segment them by who actually showed up:
is_test— a boolean, always present.truewhen the submission was tagged as test traffic (see Marking test traffic below), otherwisefalse. Filter it out to count real leads and conversions.device_type—"mobile","tablet","desktop", or empty, classified from the request’sUser-Agentat ingestion. Segment funnels by it to compare mobile vs desktop (a page can take most of your spend yet convert zero mobile visitors — this column surfaces that without parsing User-Agents by hand).browser— the coarse browser family ("chrome","safari","firefox","edge","samsung","opera","other", or empty), also from theUser-Agent.in_app— the in-app webview container the visit came from ("instagram","facebook","tiktok", and similar), or empty for a normal standalone browser. In-app webviews routinely break modals and payment flows, so this separates “converts badly on mobile” from “converts badly inside Instagram’s browser”.language— the visitor’s preferred language, the primary tag of theAccept-Languageheader (e.g."pt-BR"), or empty. Check that a Brazilian, Colombian, or French campaign is actually reaching PT/ES/FR speakers.country,region,city— the visitor’s coarse location: ISO-2 country (e.g."US"), ISO-3166-2 region code (e.g."NY"), and city name, from the serving edge. Empty when the request reaches the pipeline through an ingress that carries no edge geo. Segment by them to verify geo-targeted campaigns (e.g. “New Yorkers relocating to Miami”) landed where you paid for.
Marking test traffic
QA checks and automated evaluations submit to the same pipelines your real visitors do, so their rows would otherwise inflate lead and funnel counts. Tag a submission as test traffic and it lands withis_test = true, keeping it out of your real-traffic metrics. Two equivalent markers:
- Send the request header
X-Erdo-Test: 1(1ortrue), or - Include a top-level
"is_test": truefield in the JSON body.
is_test = true rows, so their funnel counts exclude test traffic automatically;
add the same is_test not_equals true default filter to your own lead datasets to get the same
behaviour on reads.
Auto-provisioned page-events pipelines
Alongside the pipelines you or your agents create, every published page gets one page-events pipeline automatically (itspurpose is page_events — see
Pipeline purpose above). It receives the page’s first-party analytics events — pageviews, scroll
milestones, section views, CTA clicks, form starts, leads, video plays — and writes them to your
organization’s shared Page events dataset, stamped with the page, experiment variant, session,
campaign attribution, is_test, device_type, browser, in_app, language, and geo
(country/region/city). Real-user LCP, INP, and CLS measurements arrive as web_vital rows
with vital_name, vital_value, and vital_rating. The server also distils recorded sessions into
scroll_bail, rage_click, and form_abandon rows with redaction-safe typed dimensions; these
names cannot be submitted through the public browser endpoint. Events outside that fixed vocabulary
are rejected, which you
can see in the pipeline’s execution log. Disabling a page-events pipeline turns first-party
analytics off for that page; Erdo won’t re-create it. See
Pages → First-party page events for what’s captured and how to
query it.
The generated page_events action and its shared Page events dataset target are reserved:
updates may change ordinary metadata or disable the pipeline, but cannot remove, duplicate, or
redirect that write. Trusted replay receipts fail closed unless exactly one keyed write succeeds
against the canonical dataset, so a response-only, zero-write, wrong-target, or multiple-write
configuration cannot be recorded as settled.
Managing your pipelines
You can list your pipelines, read one’s configuration, review its run history, create new pipelines, update their editable fields, and rotate their verification secrets over MCP, the REST API, and the CLI. Everything is org-scoped — a caller only ever sees their own organization’s pipelines — and pipelines are referenced by slug (a UUID is also accepted).CLI
erdo login, erdo org).
MCP tools
Identity and RBAC ride the request context, so a caller can only reach their own org’s pipelines.
REST
Base URLhttps://api.erdo.ai. Authenticate with Authorization: Bearer <token> and select the
org with X-Organization-ID.
To confirm a pipeline is capturing events end-to-end, check its executions here, then query the
dataset it writes to.
Creating a pipeline
POST /v1/event-pipelines (or erdo_create_event_pipeline) takes:
name(required) — the slug is derived from it,state—active,disabled, orproposed(the default); passactiveto start capturing immediately,source_kind—browser_form,third_party_webhook,custom_http(default), …,auth_modeandauth_config— how inbound events are verified.hmac_sha256(the default),shared_secret_header, andprovider_signaturecarry a verification secret: omit it and one is generated for you.authenticated_vieweraccepts signed-in page viewers — setauth_config.audienceto"public_and_viewers"for a public lead form,request_schema— a JSON Schema the inbound payload must satisfy,transform_js—function transform(event) { … }normalizing the payload before the actions run,pipeline— the ordered actions array, e.g.[{"type": "dataset.write", "dataset_slug": "riverview-leads"}]. Reference datasets by slug, never UUID — a plain slug targets your own organization’s datasets, and a well-formed slug that doesn’t exist yet is auto-created as the pipeline’s write target (reported back increated_datasets), so provisioning a new campaign’s lead capture is one call,purpose—customorrecord_capture(see Pipeline purpose);page_eventsis reserved,owner_artifact_id— the page this pipeline belongs to, for page-fed pipelines.
PUT /v1/event-pipelines/:slug.
Save-time warnings
The create and update responses carry awarnings array — advisory findings the save does not
refuse but you should act on. Empty means no findings.
The one warning today catches a split leads dataset: a pipeline that captures leads by email
upsert while other active lead-capture pipelines in your organization write to different
datasets. That shape usually comes from two build sessions each minting their own leads dataset
(a slug collision suffix like -6894 is the tell), and it silently strands leads — every page
looks healthy, but dashboards and email dedupe see only the fraction landing in their dataset.
When you see it, point every lead-capture pipeline for one customer at a single leads dataset
(an update repointing the dataset.write step), or disable the stale pipelines.
Secrets are shown once
For the secret-bearing auth modes, the create and rotate responses carry the plaintext verification secret in asecret field — exactly once. List and get never show it again
(the stored auth_config is redacted on every read), so store it when you receive it. Rotating
(POST /v1/event-pipelines/:slug/rotate-secret) mints a fresh secret and invalidates the old one
immediately — update the sender’s signing configuration right away. Pipelines whose auth mode
carries no secret (authenticated_viewer, public_artifact, none) reject rotation.
