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:

  1. Content-Type: application/json
  2. X-Konektor-Signature: t=<unix_timestamp>,v1=<hex_hmac_sha256>
  3. X-Konektor-Event: <event_name>
  4. User-Agent: Konektor-Webhook/1.0

Emitted Events

Lead flow currently emits:

  1. lead.created
  2. lead.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:

sourceWhere the lead came from
apiPublic API
agent_apiAgent API
inbound_webhookIncoming webhooks
The lead source value, for example manualLeads created in the dashboard
whatsappWhatsApp QR session
whatsapp_cloudOfficial WhatsApp Business
partnerWhatsApp partners and Repliz direct messages
trackingTracking pixel
rotatorRotator click and custom domain click
formRotator lead form
ruleLead created by a WhatsApp rule
orderLead 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:

  1. inbox.message.received
  2. inbox.message.sent
  3. inbox.message.status
  4. inbox.conversation.owner_changed
  5. inbox.conversation.handover
  6. inbox.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 shownMeaning
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.

PlanAPI keysWebhooksIncoming webhooks
Trial101010
Starter101010
Pro202020
Enterprise505050
Custom202020

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:

  1. Store an internal event key (for example leadId + updatedAt + event).
  2. Ignore duplicate events that already completed.
  3. Return 2xx for valid duplicates to stop further retries.

Timeout and Receiver Behavior

Konektor sends webhook requests with a short timeout (5s). A reliable receiver should:

  1. Verify signature.
  2. Enqueue heavy processing internally.
  3. Respond 200 OK quickly.

Security Notes

  1. Store webhook secrets in server-side secret storage.
  2. To change a secret, create a new webhook, switch your receiver to the new secret, then delete the old webhook.
  3. Reject missing or invalid signature headers.
  4. 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.