Webhooks
Konektor webhooks are sent asynchronously when lead changes occur. Delivery uses queue-based dispatch, so you should assume at-least-once delivery semantics.
Webhook Headers
Each webhook request includes:
Content-Type: application/jsonX-Konektor-Signature: t=<unix_timestamp>,v1=<hex_hmac_sha256>X-Konektor-Event: <event_name>User-Agent: Konektor-Webhook/1.0
Emitted Events
Lead flow currently emits:
lead.createdlead.updated
The HTTP body contains the event payload directly, without an extra wrapper.
lead.created now fires for every new lead from every source, not only from the API and the dashboard. The source field shows where the lead came from:
source | Where the lead came from |
|---|---|
api | Public API |
agent_api | Agent API |
inbound_webhook | Incoming webhooks |
The lead source value, for example manual | Leads created in the dashboard |
whatsapp | WhatsApp QR session |
whatsapp_cloud | Official WhatsApp Business |
partner | WhatsApp partners and Repliz direct messages |
tracking | Tracking pixel |
rotator | Rotator click and custom domain click |
form | Rotator lead form |
rule | Lead created by a WhatsApp rule |
order | Lead created from a store order (Scalev, Mayar, or Lynk.id) |
A visitor who only views a page is not a lead yet. lead.created fires when that visitor becomes a lead. Your receiver must drop duplicates by lead.id.
lead.created Example
{
"lead": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"uniqueCode": "API_1A2B3C4D",
"externalRef": "crm-001",
"firstName": "John",
"email": "john@example.com",
"phone": "6281234567890",
"status": "new",
"priority": "medium",
"city": null,
"country": null
},
"source": "api"
}
lead.updated Example
{
"lead": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"uniqueCode": "API_1A2B3C4D",
"status": "qualified"
},
"leadId": "550e8400-e29b-41d4-a716-446655440000",
"previousStatus": "new",
"newStatus": "qualified",
"changes": ["status"],
"source": "api"
}
Inbox Events
Beside the lead events above, the Inbox sends six events through the same webhook:
inbox.message.receivedinbox.message.sentinbox.message.statusinbox.conversation.owner_changedinbox.conversation.handoverinbox.conversation.updated
The inbox.conversation.handover event fires when the Agentic assistant hands a conversation to your team. Its payload carries identifiers and a reason code only, never message text or a phone number:
{
"event": "inbox.conversation.handover",
"payload": {
"conversation_id": "7c9ecb52-...",
"reason": "customer_request",
"assigned_to_user_id": "usr_182",
"occurred_at": "2026-10-11T10:05:00.000Z"
}
}
reason is one of keyword, max_replies, customer_request, sensitive_topic, upset, no_answer, or other. assigned_to_user_id is null when no team member received the conversation.
The inbox.conversation.updated event fires when a conversation changes: a new status, labels, or assignee, or a snooze that ends. A webhook receives it only when you select it on that webhook. Its payload carries identifiers, the status, and label IDs only, never message text, a phone number, or a name:
{
"event": "inbox.conversation.updated",
"payload": {
"conversation_id": "7c9ecb52-...",
"status": "resolved",
"assigned_to_user_id": "usr_182",
"label_ids": ["lbl_3", "lbl_7"],
"occurred_at": "2026-10-11T10:07:00.000Z"
}
}
An Inbox event wraps its payload, unlike the lead events above, whose body is the payload alone:
{
"event": "inbox.message.received",
"payload": { "...": "event data" }
}
The headers, signature verification, and idempotency rules on this page apply to Inbox events too. Read Agent Inbox API for the full payload of each event.
Edit, Pause, and Check a Webhook
You do not need to delete a webhook to change it. In Settings → API & Webhooks, open the menu of a webhook and select Edit webhook. Then change the address, the events, or the conditions, and select Save. You can select several events for one webhook. The secret stays the same.
Use the switch on a webhook to pause it or turn it on again. A paused webhook receives no events until you turn it on.
Delivery status
Each webhook shows the time of its last attempt. The status shows Failed when the last attempt did not succeed. The next successful delivery clears Failed. The status then shows Active again.
| Reason shown | Meaning |
|---|---|
| The address answered with an error. | Your receiver answered with a status outside 2xx and 3xx. |
| The address took too long to answer. | Your receiver did not answer in 5 seconds. |
| Konektor could not reach the address. | The connection failed. Check the domain, the certificate, and your firewall. |
| This address cannot receive webhooks. | The address is internal or private. Use a public https address. |
Retry delays
When a lead event fails, Konektor tries again up to 5 times. The wait grows after each failure: 15 seconds, 30 seconds, 1 minute, 2 minutes, and 4 minutes. After the last try, Konektor stops sending that event. Inbox events retry on a separate schedule.
You can also manage webhooks with the Agent API and from Claude with the Konektor connector.
Limits per Plan
Each workspace has a limit on API keys, webhooks, and incoming webhooks. The plan sets the limit. Each count is separate.
| Plan | API keys | Webhooks | Incoming webhooks |
|---|---|---|---|
| Trial | 10 | 10 | 10 |
| Starter | 10 | 10 | 10 |
| Pro | 20 | 20 | 20 |
| Enterprise | 50 | 50 | 50 |
| Custom | 20 | 20 | 20 |
An API key that is revoked or expired does not count. Incoming webhooks also need a plan that includes the API.
At the limit, Konektor does not create a new item. Delete a webhook you no longer use, or revoke an API key you no longer use. You can also upgrade your plan.
Webhook Address and Secret
A new webhook needs a public https address that has a domain name. Konektor refuses internal and private addresses, such as localhost, addresses in a private network, and addresses that end in .local or .internal.
Each webhook has a signing secret. Konektor stores the secret in encrypted form. The webhook list shows only the last 4 characters. Select Show secret on a webhook to see the full secret and copy it.
Signature Verification (Timing-Safe)
Always verify using the raw request body. Do not re-stringify parsed JSON before hashing.
import crypto from 'node:crypto'
function verifyKonektorSignature(rawBody, signatureHeader, secret) {
const parts = signatureHeader.split(',')
const timestamp = parts.find(p => p.startsWith('t='))?.slice(2)
const signature = parts.find(p => p.startsWith('v1='))?.slice(3)
if (!timestamp || !signature) return false
const now = Math.floor(Date.now() / 1000)
if (Math.abs(now - Number(timestamp)) > 300) return false
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex')
const providedBuf = Buffer.from(signature, 'hex')
const expectedBuf = Buffer.from(expected, 'hex')
if (providedBuf.length !== expectedBuf.length) return false
return crypto.timingSafeEqual(providedBuf, expectedBuf)
}
Idempotency Requirements
Because queue retries can resend events, your receiver must be idempotent:
- Store an internal event key (for example
leadId + updatedAt + event). - Ignore duplicate events that already completed.
- Return
2xxfor valid duplicates to stop further retries.
Timeout and Receiver Behavior
Konektor sends webhook requests with a short timeout (5s). A reliable receiver should:
- Verify signature.
- Enqueue heavy processing internally.
- Respond
200 OKquickly.
Security Notes
- Store webhook secrets in server-side secret storage.
- To change a secret, create a new webhook, switch your receiver to the new secret, then delete the old webhook.
- Reject missing or invalid signature headers.
- Apply infrastructure-level IP allowlists if required by your environment.
Next steps
Need More Help?
Our team is ready to help you maximize ad tracking and business attribution.
Wulan answers your questions and sends tutorial videos. Session scheduling is available through Wulan if you still need it.
© 2026 Konektor. All rights reserved.
