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.
Authentication
Section titled “Authentication”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.
1. Lead created
Section titled “1. Lead created”Every lead event is one call to /v1/conversion.
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.
2. Subsequent lead events
Section titled “2. Subsequent lead events”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”| Field | Why it matters |
|---|---|
vendor_source | Without 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_cents | Turns cost-per-lead into cost-per-person-acquired, which ranks channels differently and often dramatically |
brand_id | bll or ben. Falls back to the hostname, which is wrong for shared application hosts |
declared_source | What 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.
4. Idempotency
Section titled “4. Idempotency”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.
5. What a lead is, and is not
Section titled “5. What a lead is, and is not”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.
6. Smoke test
Section titled “6. Smoke test”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.