Skip to main content
Attribution closes the loop: you tell Mailr about deals and leads happening in your CRM, and Mailr matches each address back to the campaign that mailed it (geo-first, org-scoped, within a 180-day window). Two endpoints, both scoped webhooks:inbound, both rate-limited to 60 requests / minute / org.
The org is always taken from the API key, never from the request body — an org_id in the body is ignored. Both endpoints tolerate HubSpot/Zapier field shapes: most fields can be sent flat or nested under a properties object.

Report a won deal

POST /api/v1/conversions attributes revenue. Mailr resolves the deal’s address to a mailed campaign and upserts an idempotent conversion; the address is also recorded as an existing customer regardless of whether revenue attributes. Unmatched deals are discarded.
200 OK — matched
200 OK — unmatched
string
required
The deal’s service address. Also accepted as properties.address / deal_address / install_address.
string
Your deal id. Drives idempotency. Also properties.hs_object_id / objectId.
string | number
Deal value. A number or numeric string.
string (ISO-8601)
Close date. Drives last-touch attribution within the 180-day window.
string
Optional Mailr campaign id for an exact, address-independent attribution.
string
Optional deal/contact details, stored with the conversion.

Sync a new lead (indirect)

POST /api/v1/tracking/lead records a lead your CRM captured elsewhere as an indirect lead against the mailed campaign (source = crm_sync). Unmatched leads are discarded. Idempotency uses crm_record_id first, then a non-empty email or phone. Records with none of those identity fields stay distinct instead of sharing an empty email key. When a CRM record originated from Mailr, echo its mailr_lead_id; Mailr returns { "matched": false, "reason": "loop_guard" } without re-ingesting it. These leads are never delivered back outbound. If the address already has a direct landing-page lead, the sync collapses onto it and returns duplicate: true with attribution_type: "direct" — the existing lead is not downgraded.
200 OK — matched
200 OK — unmatched

Trigger a specific automation directly

POST /api/v1/webhooks/trigger?id=<automationId> fires one named automation — the older mechanism the Zapier app and HubSpot workflows use. It’s still supported, but for new integrations prefer POST /api/v1/events: it binds by event type instead of hardcoding an automation id, and reports per-automation outcomes. Field extraction is highly tolerant of CRM/Zapier mappings; it’s idempotent on deal_id (a repeat short-circuits before geocoding), and a deal_won automation also attributes revenue as a side effect.
202 Accepted
Discover automation ids with GET /api/v1/automation/list{ "automations": [{ id, name, enabled }] }.
A foreign automation id returns the same 404 as a missing one — a key can’t probe another org’s automation ids.

Registering existing customers

POST /api/v1/customers/webhook suppresses addresses from future mailings. Send a single { address, notes? } or a batch { addresses: [...] }; it upserts on the normalized address per org and returns { added, total }. A batch is capped at 1000 addresses — more is a 422. The endpoint is rate-limited to 60 requests / minute / org in its own bucket (separate from the two attribution endpoints above); over-limit responses are 429 with Retry-After: 60.