Push events to your tools — webhooks
Webhooks push LeadHunter events to any system that can receive an HTTPS POST — your CRM, Zapier, n8n, Make, a Slack relay, your own backend. Deep-link custom fields let you jump into your other tools; webhooks are the other direction: LeadHunter telling your tools what just happened.
What gets pushed
Section titled “What gets pushed”| Event | Fires when |
|---|---|
account.created | A new account lands in the company (manual, import, lookup, inbound) |
account.status_changed | An account actually moves in the pipeline — prospect → contacted, → customer, → do_not_contact, … |
message.inbound | An inbound reply is logged on a conversation |
Every payload carries the account’s core fields including
imported_id — so if the account came from your CRM, the event
arrives already labelled with your record id and matching it on the
receiving side is a lookup, not a fuzzy search. Custom fields ride
along too.
Set one up
Section titled “Set one up”- Open Settings → Webhooks (per company).
- Add the destination URL (must be
https://), pick the events, save. - Copy the secret immediately — it’s shown once, at creation. If you lose it, rotate it (rotation invalidates the old one).
- Hit Test — LeadHunter sends a synthetic
webhook.testevent through the real delivery pipeline so you can verify your receiver end-to-end before real traffic flows.
Verifying the signature
Section titled “Verifying the signature”Every delivery is signed so your receiver can prove it came from
LeadHunter and wasn’t tampered with. The X-LeadHunter-Signature
header carries sha256=<hex> — the HMAC-SHA256 of the raw request
body with your webhook’s secret as the key. Recompute it on your
side and compare:
import hashlib, hmac
expected = "sha256=" + hmac.new( secret.encode(), raw_body, hashlib.sha256).hexdigest()ok = hmac.compare_digest(expected, request.headers["X-LeadHunter-Signature"])Two more headers help with bookkeeping: X-LeadHunter-Event (the
event type) and X-LeadHunter-Delivery (the delivery id).
Delivery semantics — what your receiver should expect
Section titled “Delivery semantics — what your receiver should expect”- At-least-once. Failures are retried with backoff (a 500, a
timeout, a 429). The payload’s top-level
idis stable across retries and redeliveries of the same event — dedupe on it if your handler isn’t naturally idempotent. - Respond fast with a 2xx. Anything else counts as a failure. Do the heavy work async on your side.
- Permanent rejections aren’t retried. A 404/401/410 marks the delivery failed immediately — retrying wouldn’t heal it.
- Dead endpoints switch themselves off. After 20 consecutive failures the webhook is auto-disabled (you’ll see “auto-disabled after repeated failures” on the settings page). Fix the receiver, hit Test, then re-enable — the failure streak resets.
Replaying
Section titled “Replaying”Each webhook’s Recent deliveries panel shows every attempt with its status, response code, and error. Any delivery can be redelivered with one click — same payload, fresh attempt — which is the tool for “our endpoint was down for an hour” recoveries. Delivery history is kept for 90 days; anything older is pruned automatically.
Pushing data back in — upsert by imported id
Section titled “Pushing data back in — upsert by imported id”Webhooks are LeadHunter → your system. The return path is one endpoint:
POST /api/accounts/upsert-by-imported-id/{ "project": "my-company", "imported_id": "crm-4711", "status": "customer", "custom_fields": {"plan": "pro"} }LeadHunter finds the account whose imported_id matches (the same
correlation key the webhook payloads carry) and patches only the
fields you send — custom_fields merge per key, and a null
deletes a key. No match → the account is created. Status changes go
through the normal pipeline, so the audit trail and your other
webhooks fire exactly as if an operator made the change — and pushing
the same status twice is a no-op, so periodic full-state re-syncs
don’t pollute the history. Authenticate with your API token like any
other endpoint.
Together that’s the full loop: a webhook tells your CRM an account
replied; your CRM closes the deal and pushes status: customer back.
What’s next
Section titled “What’s next”Richer two-way sync (bulk upserts, field-level sync rules) builds on the upsert endpoint above; webhooks + upsert already close the basic loop.
Read next
Section titled “Read next”- Custom fields → Deep links into external systems — the inbound-click half of integrations.
- Import accounts — where
imported_idcomes from.