LanguageENES
Integrations

Send a lead from your own code

Post a lead you collected somewhere else into the same list, with the same CRM routes and webhooks.

Updated 2026-09-11View .md

What it does

POST /api/v1/leads puts a lead into the workspace's leads list from anywhere: a form on your own site, a checkout, a call somebody typed up afterwards. It lands in the same place a widget's lead lands, so the same CRM routes fire, the same outgoing webhooks fire, and it shows up in the same list and the same export.

What it needs before you start

  • An API key with the `leads:write` scope. It cannot read the leads list; that is leads:read.
  • A workspace on a plan that includes the API.

Authentication

Authorization: Bearer wai_live_…, over HTTPS. The key is shown once when you create it.

The request

bash
curl -X POST https://app.welcomeai.dev/api/v1/leads \
  -H "Authorization: Bearer wai_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "ada@example.com",
    "name": "Ada Lovelace",
    "phone": "+34 600 111 222",
    "page_url": "https://acme.com/pricing",
    "fields": { "company": "Analytical Engines", "budget": "5000" },
    "attribution": { "utm_source": "newsletter", "utm_campaign": "spring", "gclid": "Cj0KC…" }
  }'
Field
email, phone
At least one is required. A lead nobody can be reached at is a row, not a lead, and every CRM destination refuses one anyway.
name
Split into first and last exactly as a submitted form splits it. Send first_name / last_name instead if you already have them apart.
fields
Your form's own answers. Any keys you like; they appear on the lead and can be mapped onto a CRM field.
page_url
Must be an http or https URL.
attribution
UTM parameters, ad click ids, referrer, landing_page. Anything that is not one of the keys we know is dropped rather than refused.
widget_id, form_config_id
Optional. Ignored if they do not belong to your workspace.

The response

json
{ "ok": true, "lead_id": "ld_9Kq2…", "recurring": false, "submissions": 1 }

`201` for somebody new, `200` for somebody who came back. One email address is one lead per workspace: posting the same person again updates the lead you already have rather than making a second, submissions counts how many times they have been in touch, and the leads list marks them Returning.

The merge fills gaps and never blanks anything. If they gave a phone last week and only an email this time, the phone stays. A newer answer to the same question wins; an older answer to a question you did not send again survives. The lead's status and notes are never touched -- somebody may have moved it to Contacted and written on it, and a returning visitor is not a reason to undo that.

Common errors

`400 A lead needs an email or a phone number.` Send one.

`400 page_url: must be an http or https URL.` The field is rendered as a link in the leads list, so only those two schemes are accepted.

`403 This key does not have the "leads:write" scope.` Scopes are chosen when a key is created and cannot be added afterwards. Create a new key.

`429` The plan's daily API budget. It is a rolling 24-hour window per key.

What people use it for

A contact form that already exists and that nobody wants to rebuild. A phone enquiry logged by hand so it reaches the CRM with the rest. A checkout that should create a lead as well as an order. Bringing an older list in once, so the CRM routes run over it.

How to check it is working

  1. Post one and read the lead_id back.
  2. Leads shows it, with API as the trigger.
  3. If the workspace has a CRM route, the dispatch log has a row for it within a few seconds.
  4. Post the same address again: the response is 200 with "recurring": true, and the list still has one lead with a Returning chip.