Skip to content
  1. Home
  2. Help center
  3. Connections
  4. Lead intake webhook — API reference

Lead intake webhook — API reference

Last updated:

When a form platform has no ready integration — a custom-built site, an internal system, a back-office script — you can post leads to KO directly. This is the Webhook: Lead Intake connection.

Only the contact is required. Everything else improves what the reports can tell you.

The request

URL: https://webhook.kodata.pro/{hash32} — issued in the connection settings.

Method: POST

Content type: application/json or multipart/form-data

Any field may be omitted except the contact. Phone numbers are accepted in any shape:

FormExample
Formatted+358 40 123 4567
Digits358401234567
With extension (; separator)358401234567;5555
SIP addressu100@example.com

Phone and email can each hold several values — pass an array.

Minimal request

{
  "contact": {
    "phone": "+358 40 123 4567"
  }
}

This creates a lead with no source. It will appear in reports as unattributed, which is the right answer when the source is genuinely unknown — but if the visitor came through your site, pass the touch identifier below and it will not have to be.

Minimal request with attribution

{
  "contact": {
    "phone": "+358 40 123 4567"
  },
  "touch": {
    "touch_id": 123
  }
}

touch_id comes from the _ko_touch_id cookie, which the KO tracking script sets on your pages. Reading that cookie and passing it here is the whole of attribution: it is what connects this lead to the campaign that produced the visit.

Full request

{
  "contact": {
    "name": "Firstname Lastname",
    "phone": "+358 40 123 4567",
    "email": "person@example.com"
  },
  "lead": {
    "amount": 1000,
    "currency": "EUR",
    "profile": {
      "comment": "Call me tomorrow",
      "i-agree": "on"
    }
  },
  "touch": {
    "touch_id": "123456",
    "communication": "site",
    "action": "lead",
    "site_url": "https://example.com/path?utm_source=newsletter",
    "ref_url": "https://referrer.example/path",
    "user_device": "desktop",
    "user_browser": "firefox",
    "user_ip": "127.0.0.1"
  }
}

Contact fields also accept messenger handles: whatsapp, telegram, viber, facebook, instagram, skype.

lead.profile carries the form’s own fields. Either an object of name: value pairs, or an array of {name, label, value} objects when you want the human-readable label preserved:

"profile": [
  { "name": "comment", "label": "Comment", "value": "Call me tomorrow" }
]

touch field values:

FieldValues
communicationthe channel: site, chat, chatbot, messenger, widget, quiz, leadform, call, email, sms, manager, payment, app, webinar, fishing, billboard, radio, book, unknown
actionwhat happened: for a lead, lead; of the rest, view, impression, click, paid, subscribe, unsubscribe apply to a webhook
user_devicedesktop, tablet, mobile, tv, unknown
user_browserstored as sent — use the common lowercase name: chrome, firefox, safari, edge, opera

A value outside these lists is not rejected, but it is not understood either: channel and action feed source detection, and an invented value files the lead under an unknown channel.

touch additionally accepts the UTM fields (utm_source, utm_medium, utm_campaign, utm_content, utm_term) and client identifiers (client_id_ko, client_id_ga, client_id_fb) when you have them.

The response

JSON, HTTP 200.

{
  "contact_state": "ok",
  "contact_id": 111,
  "lead_id": 222,
  "crm_service": "HubSpot",
  "crm_contact_id": 333,
  "crm_lead_id": 444
}

The submission succeeded when contact_id is not 0. Check that field specifically: contact_state and crm_service are only returned when the lead reached a CRM connected to the project — without a CRM connection the response is just the identifiers.

contact_state: "double" means the contact already exists in KO or in your CRM and was not created again — the contact_id returned is the existing one. This is a normal outcome, not an error: treat it as success.

Errors

An error comes back with the same 200, with two fields instead of the identifiers:

{
  "error_code": 1,
  "error_message": "putContact: Contact not created. Check request."
}

error_code: 1 — the request was not accepted (empty or unreadable contact); error_code: 2 — putContact: Ignored by filter, the lead was dropped by an intake rule configured on the connection.

Test mode

Add a test parameter to get a simulated response without creating anything. The value determines the numbers you get back, so you can distinguish one test from another: test=1 returns contact_id: 1 with contact_state: "double", test=2 returns contact_id: 2 with contact_state: "ok", and so on.

{
  "test": 2,
  "contact": { "name": "Firstname Lastname", "phone": "+358 40 123 4567" }
}
{
  "contact_state": "ok",
  "contact_id": 2,
  "lead_id": 2,
  "crm_contact_id": 20,
  "crm_lead_id": 20,
  "crm_service": "Test"
}

Reading lead status back

Disabled by default — enable it in the connection settings under Permissions, with the Allow reading lead status checkbox; the deal amount is a second checkbox of its own, Show the deal amount.

If you want status changes pushed rather than polled, write to technical support: postbacks (lead created, lead changed, deal won, deal closed) are set up separately and arrive at your endpoint on their own.

Same URL, GET or POST. You can look up leads by lead_id or by crm_lead_id, at most 100 per request. Only leads submitted through this same webhook are visible, and deleted leads are absent from the response.

{
  "method": "getLeads",
  "filter": {
    "lead_id": [1, 2]
  }
}
{
  "leads": [
    { "lead_id": 1, "crm_lead_id": 10, "state": "process", "amount": 0, "currency": "" },
    { "lead_id": 2, "crm_lead_id": 20, "state": "success", "amount": 1000, "currency": "EUR" }
  ]
}

state is one of process, success, fail. Which fields come back depends on the permissions granted in the connection settings — by default only the status is exposed, and the deal amount is not.