Create an API key, make an authenticated request, and receive signed webhooks without losing or duplicating events.
Who can do this: owners and managers. The Developer tab does not appear at all for anyone else.
Before you build against the API, check Supported integrations. If we already connect to your tool, that path is less work and less to maintain.
Create an API key
Open Settings, then the Developer tab. You can link straight to it with
?tab=developer.Copy your Organization ID from the top of the API Keys card. Every API route needs it, and this is the only place it is offered with a copy button.
Enter a label that says where the key will be used.
Pick a Lifetime: 30 days, 6 months, 1 year, Unlimited, or Custom date.
Select Create API key.
Copy the key from the dialog straight away.
The API Keys card. The Organization ID sits at the top, above the key list.
The key is shown once and never again. Only its identifier and status appear afterwards. If you lose it, the only remedy is to create another and revoke the old one.
A key looks like <keyId>.<secret>, where the first half is a UUID. Only the second half is stored hashed, which is why we cannot recover it for you.
Treat a key as an owner-level credential. Keys belong to an organization, not a person, and a request made with one acts as that organization's highest-privilege active member. There is no way to give a key a smaller role.
Make a request
The base URL is https://api.leadhero.io. Send the key either way:
x-api-key: <keyId>.<secret>
Authorization: Bearer <keyId>.<secret>
A first request looks like this:
curl -H "x-api-key: <keyId>.<secret>" \ https://api.leadhero.io/leads/<organization>
The generated reference, filtered to the routes a key can reach, is at https://api.leadhero.io/documentation/api-key.
A key only works on routes that carry the organization in the path, and only on an allowlist of those. Presented anywhere else it is ignored rather than refused, so the failure looks like bad credentials rather than a wrong route. If a request fails auth and you are sure the key is good, check the route before regenerating anything.
Creating an outbound call is blocked for API keys. This is an explicit denial, not a gap, so it will not be enabled on request.
Rate limit
120 requests per minute, counted per key, so separate keys get separate budgets. Going over returns 429 with a message saying when to retry.
Rotate a key
There is no rotate-in-place, and revoking takes effect immediately, so order matters:
Create the new key.
Deploy it.
Confirm traffic is arriving on the new key.
Revoke the old one.
Nothing reminds you when a key is about to expire. If you choose Unlimited, put the rotation in your own calendar. Custom date is useful for staging a rotation you already have a date for.
Receive webhooks
In the same Developer tab, create a webhook and give it a label and a URL.
Set Scope to: either a single workspace or All workspaces. A workspace-scoped webhook stays silent for everything outside it, which is worth checking first when events seem to be missing.
Select Configure Events and tick what you want.
Turn on the signing secret.
Events are chosen per webhook. Nothing is subscribed by default.
The signing secret is also shown only once. There is no regenerate button and no secret field on an existing webhook, so rotating one means creating a second webhook to the same URL, cutting over, then deleting the first.
Events
Event | Fires when |
| A call starts |
| A call is answered |
| A call ends |
| A lead is created |
| A lead changes |
| A lead is deleted |
| A lead's status changes |
| An enrichment job finishes |
lead.status.update is the only event with a filter. You can narrow it to specific statuses, so a webhook watching for booked stays quiet through everything else.
Verify the signature
Every signed delivery carries these headers:
Header | Contents |
|
|
| Milliseconds since the epoch |
| The event name |
| Which webhook sent it |
| Unique per delivery |
The digest is HMAC-SHA256 over the timestamp, a full stop, and the raw request body:
sha256=HMAC_SHA256(secret, timestamp + "." + rawBody)
Sign the raw bytes, not a re-serialised object. Parsing the JSON and stringifying it again will change the body and the signature will never match, which is the usual reason a first integration fails.
Delivery, retries, and duplicates
Any 2xx counts as success. Anything else fails and is retried, up to 5 attempts, backing off exponentially from 2 seconds, with each attempt timed out at 15 seconds. Deliveries are throttled to 10 per second across the whole system.
Return 2xx only once you have durably stored the event. Acknowledging first and processing after means a crash loses the event for good, because we already counted it as delivered.
Expect the same event more than once. Retries and at-least-once delivery both cause it. Deduplicate on X-LeadHero-Delivery-Id, which is unique per delivery and stable across retries of that delivery.
When to contact support
Get in touch if:
Authorization fails repeatedly on a route you believe is allowed.
Signature verification never matches, even against the raw body.
Events are still missing after all retries should have run.
The action you need is not in the API reference.
Include the request time, the endpoint, the response status, and the delivery ID. Never include the secret or the full authorization header.
Next steps
Supported integrations: check before building your own
Connect Email Bison and Connect Instantly: no-code alternatives
Organizations, workspaces, and members: roles and workspace scope
Contact Lead Hero support: what to include


