Skip to content

Sessions outside the browser we track

Event: a session happens somewhere the web SDK is not — an authenticated portal, an AI agent’s conversation — and, inside it, goals are reached. Endpoints: POST /v1/session; goals through POST /v1/collect Status: /v1/session live in dev

Who sends this. Stitch does not know and does not need to. It sees a key, which names the system the owner issued it to, and a payload. Today the likeliest emitters are the borrower portal’s backend for portal sessions and the MagicBlocks agent platform for ai_agent sessions; a middleware in front of either sends exactly the same thing.


Authorization: Bearer <your key>

One key per emitting system, named by the owner after whatever actually sends; 401 {"error":"unauthorized"} without it, and nothing written. The system a key belongs to is recorded on every row it creates as source_system. /v1/collect takes no key; it is gated by the page’s origin.


Terminal window
curl -X POST https://stitch-collector-dev.magic-blocks.workers.dev/v1/session \
-H "content-type: application/json" \
-H "authorization: Bearer <your key>" \
-d '{
"session_type": "ai_agent",
"external_session_ref": "sess-77120",
"octopus_id": "88213",
"anon_id": "3f1a8c2e-9b4d-4a71-8e2f-0c5d6a7b8e91",
"started_at": 1772539200000,
"ended_at": 1772539980000,
"ai_channel": "web_chat",
"ai_direction": "inbound",
"ai_agent_id": "agent-dscr-qualifier",
"ai_agent_version": "v3.2"
}'
{ "session_id": "8c1d…" }

session_type is portal or ai_agent; the ai_* fields belong to the second. A portal session is the same shape without them:

{ "session_type": "portal", "external_session_ref": "portal-sess-99213", "octopus_id": "88213",
"anon_id": "3f1a8c2e-9b4d-4a71-8e2f-0c5d6a7b8e91", "started_at": 1772539200000, "ended_at": 1772539860000 }

external_session_ref is the idempotency key: re-sending returns the id of the session that already exists rather than creating a second one, so you can emit on both session start and session end with the same ref. Unlike /v1/collect, a malformed payload here is a loud 400 — a silent success would hide a broken integration for weeks.

Carry the anon_id when a session starts on a web page

Section titled “Carry the anon_id when a session starts on a web page”

When a session begins on one of our pages — a chat widget, a sign-in on the app host — pass the page’s anon_id into the session. A chat that ends in a lead then carries the visitor’s full referral history with it. Without it, the session is an island. For an authenticated session, also send identity.md’s /v1/identify: that is what binds the browsing history to the person for good.


Goals are ordinary events carrying a session_id and goal_id. There is no special machinery — a goal is just a named thing that happened inside a session.

Terminal window
curl -X POST https://stitch-collector-dev.magic-blocks.workers.dev/v1/collect \
-H "content-type: application/json" \
-d '[{
"event_id": "goal-99123",
"ts": 1772539900000,
"name": "goal_completed",
"url": "https://makeabeeline.com/",
"referrer": null,
"props": { "session_id": "sess-77120", "goal_id": "qualified_dscr", "agent_version": "v3.2" }
}]'

They convert; they do not acquire. Portal and AI sessions are hard-classed touch_class: "nurture" and can never take acquisition credit, whatever the payload says ("touch_class": "discovery" is ignored rather than rejected). They are by definition post-acquisition: the agent harvests and converts demand a channel already produced. If these sessions could take credit, the agent would progressively cannibalise credit from every real channel, and the better it performed the more it would distort the picture — the same logic that excludes brand search.

Portal sessions earn their place twice. They are authenticated, so every one is a free tier-1 identity confirmation (recorded at certainty A). And portal activity is a well-known pull-through predictor — people with three or more portal sessions tend to fund at a different rate, which makes it an early ops signal rather than only a marketing one.

4. How an agent’s contribution is measured instead: lift, not credit

Section titled “4. How an agent’s contribution is measured instead: lift, not credit”
MeasureWhat it needs from the emitter
Holdout liftA configurable % of leads routed to human-only or delayed-agent handling. Even 5% gives a usable read within a quarter
AI-touched vs not, by acquisition channelNothing extra — falls out of the session records
Response latency curveAccurate started_at, to the second
Identification rate% of anonymous sessions promoted to known persons via an agent session. An attribution-coverage lever disguised as a product metric — and the one where the agent most clearly earns its place
Goal completion by agent versionai_agent_version and any experiment variant in props

Open question for whoever owns the agent: what holdout percentage is acceptable without hurting the business? It is the only honest measure of incremental contribution, and 5% is enough.


Terminal window
STAMP=$(date +%s)
curl -sS -X POST https://stitch-collector-dev.magic-blocks.workers.dev/v1/session \
-H "content-type: application/json" \
-H "authorization: Bearer <your key>" \
-d "{\"session_type\":\"portal\",\"external_session_ref\":\"smoke-portal-$STAMP\",\"octopus_id\":\"smoke-$STAMP\",\"started_at\":$(date +%s)000}"

Expect 202 and a session_id. Re-send the same external_session_ref and expect the same session_id back. And an agent session:

Terminal window
curl -sS -X POST https://stitch-collector-dev.magic-blocks.workers.dev/v1/session \
-H "content-type: application/json" \
-H "authorization: Bearer <your key>" \
-d "{\"session_type\":\"ai_agent\",\"external_session_ref\":\"smoke-ai-$STAMP\",\"started_at\":$(date +%s)000,\"ai_channel\":\"web_chat\",\"ai_direction\":\"inbound\"}"