Skip to content

Lead events

Event: a lead is created, or its status changes. Endpoint: POST /v1/conversion with event Status: live in dev, verified end to end

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 emitter is whatever is the system of record for leads (today, probably the CRM), or a middleware in front of it. Stitch treats every emitter as a peer against the same contract — none is upstream of another, and Stitch is the join.


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.


Every lead event is one call to /v1/conversion.

Terminal window
curl -X POST https://stitch-collector-dev.magic-blocks.workers.dev/v1/conversion \
-H "content-type: application/json" \
-H "authorization: Bearer <your key>" \
-d '{
"idempotency_key": "lead-44821-created",
"identity": { "relcu_contact_id": "88213" },
"lead": {
"external_id": "44821",
"brand_id": "bll",
"attrs": { "status": "new", "vendor_source": "partner-x", "cost_cents": 4500, "submission_id": "k8s9d7f6g5h4j3k2" }
},
"event": "created",
"occurred_at": "2026-03-01T12:00:00Z"
}'
{ "personId": "01fa113c-…", "leadId": "…", "loanId": null, "duplicate": false }

occurred_at on lead creation matters more than any other timestamp you send. It becomes the lead’s attribution anchor, frozen once set, and every loan produced by that lead inherits it. Attribution windows run backwards from this moment. Send the real creation time, not the time the message was sent.

identity must carry at least one recognised key (relcu_contact_id, relcu_lead_id, vesta_borrower_id, vesta_loan_number, magicblocks_contact_id, or the browser’s anon_id). If the contact has not been bound to a browser yet, send the anon_id here too, or through identity.md — either binds the browsing history.

submission_id is the page’s hidden field (see web-sdk.md §3a). Stitch uses it as the lead conversion’s event id, so the pixel’s event and the server’s are one event to the ad platform.

The created event is what fires the lead conversion to the enabled ad platforms (see outbound.md); later events do not.

Terminal window
curl -X POST https://stitch-collector-dev.magic-blocks.workers.dev/v1/conversion \
-H "content-type: application/json" \
-H "authorization: Bearer <your key>" \
-d '{
"idempotency_key": "lead-44821-contacted",
"identity": { "relcu_contact_id": "88213" },
"lead": { "external_id": "44821" },
"event": "contacted",
"occurred_at": "2026-03-01T12:04:00Z"
}'

Valid events: created, contacted, connected, qualified, disqualified, nurture_started, app_started, recycled, dead.

An unrecognised event returns 400 naming the valid ones — deliberately loud, because a silently dropped event is a gap nobody notices for weeks. You do not have to send every event; send the ones the system of record actually has.


3. Fields worth sending even though they are optional

Section titled “3. Fields worth sending even though they are optional”
FieldWhy it matters
vendor_sourceWithout it, the duplicate-lead report cannot run. If a vendor sends the same person three times, the stated cost per lead is a fiction — this is usually where real money is found
cost_centsTurns cost-per-lead into cost-per-person-acquired, which ranks channels differently and often dramatically
brand_idbll or ben. Falls back to the hostname, which is wrong for shared application hosts
declared_sourceWhat the person said when asked how they heard about us; see declared-source.md

attrs is free-form: unrecognised keys are stored too, so a new attribute needs no change here.


idempotency_key must be unique per event, and stable across retries — and unique across every event and every emitter, lead or loan, because Stitch keeps one table of them. Prefix it with the emitter’s name: {emitter}-lead-{lead_id}-{event}, e.g. crm-lead-44821-created.

Re-sending a key returns 200 with "duplicate": true and changes nothing. Retry as aggressively as you like.


A lead is whatever the system of record says it is. A /thank-you page hit is evidence someone probably submitted a form; Stitch counts leads from this event and uses page events only for funnel shape. The two are never summed and never substituted, which is what stops the classic failure where the site reports 400 conversions, the CRM’s own count is 340, and nobody can explain the gap.


Terminal window
STAMP=$(date +%s)
curl -sS -X POST https://stitch-collector-dev.magic-blocks.workers.dev/v1/conversion \
-H "content-type: application/json" \
-H "authorization: Bearer <your key>" \
-d "{\"idempotency_key\":\"smoke-$STAMP-lead\",\"identity\":{\"relcu_contact_id\":\"smoke-oct-$STAMP\"},\"lead\":{\"external_id\":\"smoke-l-$STAMP\",\"brand_id\":\"bll\"},\"event\":\"created\",\"occurred_at\":$(date +%s)000}"

Expect 202 and a leadId. Re-send it and expect 200 with "duplicate": true.