Part 8 — Webhook on every status change · both sites
One CRM, one webhook for both sites. On every lead status change — one POST to our URL.
- What
- POST with JSON on every status change, including creation — leads from both sites
- Where
- Your CRM's status-change hook
- Done when
- You send — we see the events on our side. Nothing for you to test
Endpoint
POST https://prod-observix-integrations.orangeplant-bcc99803.eastus.azurecontainerapps.io/api/crm/hooks/hk_9U2-kZVbLTdSx7yfMPuowVsd_aJuKXhTpMNzUtHVB-Y
Content-Type: application/jsonPayload
{
// ── Required ─────────────────────────────────────────────────────────────
"entity_id": "L-48213", // your lead / deal id — stable for its whole life
"status": "Estimate Sent", // the status name exactly as it is in your CRM
"occurred_at": "2026-09-22T14:19:00Z", // when the status changed in the CRM, UTC — not the send time
// ── Recommended — revenue in reports, and matching a customer across devices ──
"value": 8400.00, // estimate / contract amount, if known
"currency": "USD",
"email": "jane@example.com",
"phone": "+19165550123",
"external_id": "C-1092", // your customer / contact id
"properties": {
"observix_visitor_id": "01a0c8ac-2a12-7e9c-b0a2-059ecef85246", // from Part 2 — required
// + any lead fields you have: site, service, address, source, owner, notes…
"site": "abdteam.com", // which website the lead came from
"service": "James Hardie siding",
"city": "Sacramento",
// + any customer fields you have
"first_name": "Jane",
"last_name": "Doe",
// + the marks HandL already stores: gclid, fbclid, utm_* — send what you have
"gclid": "Cj0KCQ…",
"utm_source": "google"
}
}Steps
- 01
From the CRM's status-change hook, after the change is saved
3 s timeout, errors to the log. Analytics must never break a lead update.
reference implementation · for your review // wherever a lead's status changes in your CRM — after the change is saved function sendToObservix(lead, newStatus) { const payload = { entity_id: String(lead.id), status: newStatus, occurred_at: lead.statusChangedAt, // the CRM's own change time — identical on every retry value: lead.amount ?? undefined, currency: "USD", email: lead.email, phone: lead.phone, external_id: lead.contactId ? String(lead.contactId) : undefined, properties: { observix_visitor_id: lead.observix_visitor_id, site: lead.site, ...lead.customFields }, // + whatever you have }; fetch("https://prod-observix-integrations.orangeplant-bcc99803.eastus.azurecontainerapps.io/api/crm/hooks/hk_9U2-kZVbLTdSx7yfMPuowVsd_aJuKXhTpMNzUtHVB-Y", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), signal: AbortSignal.timeout(3000), // never hold up the CRM }).catch((e) => console.warn("observix:", e.message)); // log and move on } - 02
One entity_id per lead, unique across both sites
Both sites land in the same CRM, so the lead id must not collide between them. Add a
siteproperty so we can split reports per website. - 03
One call per change, no dedup on your side
Deduplication is on us: the same
entity_id,statusandoccurred_atcount once. That only works whenoccurred_atis the time the status changed (in Airtable, a “Last modified time” field on the status), never the time of sending.
Verify
- Create a test lead and change its status twice in the CRM
- Nothing in your error log; the lead updated as usual
- Tell us the lead id — we confirm from our side that we see three events