Receive and verify signed webhooks
Add an HTTPS endpoint, select contact or campaign events, save the one-time signing secret, verify raw-body signatures and handle retries or disabled delivery.
Last updated: · Read as Markdown
What webhooks do
A webhook sends a signed HTTPS POST from LeadKnight to your server when a selected CRM contact or campaign status changes. Use it to update another system without repeatedly polling the Developer API. It is an outbound notification, not an incoming command, and it does not need a personal API key in its receiver URL.
Webhooks are available wherever the Developer API is enabled. They are managed in Settings using your signed-in account, not through an API key. Each endpoint belongs to the person who created it in one workspace and carries only data that person is currently allowed to see.
Before you start
- Prepare a public HTTPS receiver on port 443 or 8443 with a valid TLS certificate. The URL may contain no credentials or fragment and may be at most 2,048 characters long.
- Ensure every IP address the hostname resolves to is public. A public hostname with even one private/internal result is not accepted; public-address validation also happens before delivery.
- Configure the receiver to retain the exact raw request body before JSON parsing and to accept POST requests without redirecting.
- Prepare private server-side secret storage, a synchronized clock, durable delivery-ID deduplication and a queue for slower processing.
- Sign in as the intended endpoint owner and select the correct workspace. Current role, seat and contact visibility still apply. You can have at most five live webhooks per person per workspace.
If Webhooks is missing, it may not be enabled for your workspace yet. While the Developer API is disabled, existing endpoints stay visible for deletion; a card with no endpoints is hidden. Do not publish a receiver secret in source control, request URLs, logs or screenshots.
Add the endpoint
- Open Settings and find Webhooks alongside Developer API. Your webhooks shows the endpoints belonging to your account.
- Select Add webhook and verify the displayed Workspace.
- Enter the final receiver address in Webhook URL. Use the actual HTTPS POST URL, not a login page or an address that redirects to another path.
- Under Events, select at least one of New contact, Contact updated and Campaign status changed. You can select more than one for the same endpoint.
- Select Save. On success, the Signing secret dialog appears. Use Copy and save the
whsec_secret in your server's secret manager. It is shown only once. - If copying fails, copy the selected field manually or select Copy again. Select I've saved the secret only after it is stored. Closing or leaving the page loses access to the displayed secret.
- Find the new URL under Your webhooks. Check its workspace, event badges, state and Last delivery.
- Configure the receiver's verifier using that endpoint's secret, then select Send test event. A successful receiver returns a display such as Delivered (200); other successful 2xx statuses are also accepted. The test body is
webhook.test, not a new contact or actual campaign change.
If creation is uncertain or you lose the secret, use Refresh list, delete any newly created endpoint whose secret you did not save, and add it again. Do not automatically repeat creation: the first request might already have succeeded. Secrets cannot be displayed again; replacing a secret requires deleting and recreating the endpoint.
Choose events and understand the payload
Every body contains type, ISO datetime timestamp and data. The attempt header webhook-timestamp is a separate Unix-seconds value used for signature verification. Use the API reference or the top-level webhooks section of the OpenAPI document for full field definitions.
| Settings event | Body type |
When and what is sent |
|---|---|---|
| New contact | contact.created |
A CRM contact is created, including an audience member promoted to a contact. data.contact uses the safe Developer API contact projection. |
| Contact updated | contact.updated |
Changes to name, email, phone, owner, stage, loss reason, deal value/currency, tags or custom values. data.contact is current state when the delivery is prepared. |
| Campaign status changed | campaign.status_changed |
A derived campaign status change; data.campaign is the API campaign summary and data.previousStatus is the previous status. Checks run about once a minute, so a shorter-lived state can be missed. |
| Send test event | webhook.test |
A manual receiver check with data.test set to true. It does not prove that a real contact is visible to this endpoint's owner. |
Closely spaced contact changes can produce a single notification. This is not an immutable log of every edit. Contact data is limited by the endpoint owner's current visibility; private categories that are no longer accessible are not sent. Deleted and merged-away contacts produce no contact payload. Membership, role/seat and category access are rechecked before delivery, including retries.
Campaign status is not a delivery receipt from a social network. Do not mark a recipient contacted or a post delivered solely because a status-change event arrived.
Verify every request before using its data
Deliveries follow Standard Webhooks. Use the endpoint's signing secret, not a Developer API key.
| Header | Meaning |
|---|---|
webhook-id |
Unique delivery ID. Retries retain the same ID and body. |
webhook-timestamp |
Unix seconds for this particular attempt. |
webhook-signature |
Versioned signature, v1, followed by a base64 HMAC-SHA256 value. |
- Read the raw request body exactly as received. Do not parse and reserialize JSON before verifying: whitespace, escaping or character changes alter the signature.
- Require a delivery ID, a valid numeric attempt timestamp and a versioned signature. Reject a timestamp more than five minutes away from your server's clock; do not confuse Unix seconds with milliseconds.
- Remove the
whsec_prefix from the stored secret and base64-decode the remaining text to obtain the HMAC key. Do not use the prefixed secret text directly as the key. - Construct the signed input as
<webhook-id>.<webhook-timestamp>.<raw body>, with periods between the three parts, using the exact header values and original body. - Calculate HMAC-SHA256 with the decoded key and base64-encode the result. Match the
v1,signature value using a constant-time comparison. If the signature header contains space-separated candidates, accept only a valid matching supported candidate. - Reject unverifiable or stale requests before applying business changes. Never bypass verification just to make a test display as delivered.
- After verification, deduplicate
webhook-iddurably for 30 days. An already-accepted delivery should receive a successful acknowledgement without repeating side effects. - Parse and validate the event, safely persist or queue it, and return any 2xx within ten seconds. Perform longer processing after acknowledgement, with your own durable recovery so acknowledged work is not lost.
Each retry has a fresh attempt timestamp/signature but the same delivery ID and body. A body timestamp can therefore be older than its request timestamp; verify the attempt header, not the event's age, for the five-minute check.
Delivery rules and recovery
- Delivery is at least once. A response can be lost after your receiver processed a request, so duplicate handling is required even when your receiver normally succeeds.
- There is no ordering guarantee across contacts. Use payload state rather than assuming global arrival order is the edit order.
- Return 2xx within ten seconds. Redirects are not followed; 3xx, other unsuccessful statuses, connection/TLS failures and timeouts count as failures.
- Failed delivery retries wait 1 minute, 5 minutes, 15 minutes, 1 hour, 3 hours, 6 hours and 12 hours after the successive failures. That is eight attempts including the original, spanning about 22.5 hours plus request processing.
- While enabled with a failed latest attempt, the endpoint shows Failing. After the last failed attempt, it becomes Disabled and its owner is emailed.
- Fix the receiver before selecting Enable. This resets failure state but does not replay abandoned deliveries or changes missed while off. Reconcile current records through the Developer API when needed.
- Erasing a contact removes pending deliveries. A request already sent to a receiver cannot be recalled.
Disable, delete or replace an endpoint
- Find the URL under Your webhooks. Use Disable to stop delivery without deleting the configuration; its state changes to Disabled.
- Use Enable only after the receiver and permissions are ready. Disabling and enabling do not create a history-replay service.
- To remove the URL permanently, select Delete. Read Delete webhook? and confirm Delete; Cancel leaves the endpoint in place. Deletion cannot be undone.
- To change the receiver URL, event selection through the available creation flow, or replace a lost signing secret, add a correctly configured replacement and remove the old endpoint. The current Settings card has no edit form for an existing URL or event set. Observe the five-endpoint limit and handle potential overlap without applying changes twice.
- If any management result is uncertain, use Refresh list to inspect current state before repeating the action.
If something goes wrong
| Symptom | Likely cause | What to do |
|---|---|---|
| Invalid URL message | HTTP, disallowed port, credentials, fragment or excessive length. | Use a final public HTTPS URL on 443 or 8443, no credentials/fragment, at most 2,048 characters. |
| Public-address refusal | DNS returns a private, reserved or otherwise non-public address. | Correct every DNS result; a localhost or internal receiver cannot be used. |
| No event selected | The form requires at least one event. | Select the appropriate Events before Save. |
| Five-endpoint limit | This person's live endpoints in the workspace reached the cap. | Delete an obsolete endpoint before adding another. |
| Delivered (200) but no real updates | A test verifies transport, not the selected events or visibility of a record. | Check event badges, actual changes, workspace and owner access. |
| Signature mismatch | Wrong endpoint secret or body changed by middleware. | Keep the original bytes, decode the secret after whsec_ and use the exact three signed parts. |
| Timestamp rejected | Clock drift or seconds interpreted as milliseconds. | Synchronize the receiver clock and check webhook-timestamp in seconds. |
| Timeout | The receiver did not complete within ten seconds. | Verify, durably queue and acknowledge promptly; move long work out of the request. |
| TLS, connection or redirect error | Unreachable server, invalid certificate or forwarding URL. | Fix connectivity/TLS and use the final non-redirecting endpoint, then send a test. |
| Disabled after repeated failures | All delivery attempts failed. | Repair the receiver, test it and select Enable; reconcile missed state separately. |
| Secret lost or creation uncertain | The one-time secret is no longer available. | Refresh, delete that endpoint and add it again; do not guess or recover a secret from logs. |