Skip to content

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.

EventFires when
account.createdA new account lands in the company (manual, import, lookup, inbound)
account.status_changedAn account actually moves in the pipeline — prospect → contacted, → customer, → do_not_contact, …
message.inboundAn 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.

  1. Open Settings → Webhooks (per company).
  2. Add the destination URL (must be https://), pick the events, save.
  3. Copy the secret immediately — it’s shown once, at creation. If you lose it, rotate it (rotation invalidates the old one).
  4. Hit Test — LeadHunter sends a synthetic webhook.test event through the real delivery pipeline so you can verify your receiver end-to-end before real traffic flows.

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 id is 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.

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 sendcustom_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.

Richer two-way sync (bulk upserts, field-level sync rules) builds on the upsert endpoint above; webhooks + upsert already close the basic loop.