דלג לתוכן הראשי

Lead webhooks

Send new leads into a CloseUp campaign with one HTTP POST, from your own code or from Zapier, Make or n8n.

התיעוד למפתחים כתוב באנגלית.

Every campaign in CloseUp has its own private webhook URL. Any system that can send an HTTP POST can create leads in that campaign: your backend, a landing page builder, or an automation tool such as Zapier, Make or n8n. The lead lands in the campaign, is routed to a rep by the campaign's rules, and is deduplicated against existing leads.

Lead webhooks are inbound only: other tools push leads into CloseUp. CloseUp does not send outbound webhooks, and there is no Zapier trigger app. To change a lead's status or book appointments from your own system, see the public API.

Get your campaign webhook URL

  1. Open Lead Sources
    In CloseUp, go to Lead Sources and open Zapier, Make & webhooks (under Developers & automation).
  2. Find the campaign
    Under Campaign addresses, every active and paused campaign is listed with its own URL. If the list is empty, create a campaign first in Campaigns.
  3. Copy the URL
    Use the copy button next to the campaign. The full URL is your credential. You do not need any other key or header.
Treat the campaign webhook URL like a password. Anyone who has it can create leads in your campaign. Keep it out of browser code and public repositories. If it leaks, click New address next to the campaign: the old URL stops working immediately, and you must paste the new one into every tool that still uses the old one.

The campaign's page in Campaigns also shows its webhook URL, the full field specification, and ready-made code samples in cURL, HTTP, JavaScript, Node.js, Python, PHP and a Postman collection. The samples include any custom fields the campaign declares.

In the examples below, YOUR_CAMPAIGN_WEBHOOK_URL stands for the URL you copied. Use it exactly as copied.

Send a lead

http
POST YOUR_CAMPAIGN_WEBHOOK_URL
Content-Type: application/json

{
  "name": "Dana Levi",
  "phone": "0501234567",
  "email": "dana@example.com",
  "utm_source": "facebook",
  "utm_campaign": "summer-sale",
  "utm_content": "video-ad-1",
  "message": "I'd like a quote for 3 rooms"
}

The body can be JSON (application/json), a URL-encoded form (application/x-www-form-urlencoded) or multipart/form-data. A request with no content type is parsed as JSON first, then as URL-encoded.

Only one thing is required: a phone or an email. The name is optional. Without one, the lead is named after its phone or email.

cURL
curl -X POST "YOUR_CAMPAIGN_WEBHOOK_URL" \
  -H "Content-Type: application/json" \
  -d '{"name":"Dana Levi","phone":"0501234567","email":"dana@example.com","utm_source":"facebook"}'
Node.js (18+)
// Keep the URL in server-side configuration, never in browser code.
const res = await fetch(process.env.CLOSEUP_CAMPAIGN_WEBHOOK_URL, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    name: "Dana Levi",
    phone: "0501234567",
    email: "dana@example.com",
    utm_source: "google",
    utm_campaign: "brand-search",
    gclid: "EAIaIQobChMI...",
  }),
});

const data = await res.json();
console.log(res.status, data); // 201 { success: true, leadId } or 200 { success: true, deduped: true, leadId }
Python (requests)
import os

import requests

resp = requests.post(
    os.environ["CLOSEUP_CAMPAIGN_WEBHOOK_URL"],
    json={
        "name": "Dana Levi",
        "phone": "0501234567",
        "email": "dana@example.com",
        "utm_source": "newsletter",
    },
    timeout=15,
)
print(resp.status_code, resp.json())

Accepted fields

CloseUp reads each lead field from the first matching key in the payload. Keys are matched exactly as listed. Values are trimmed, and empty values are ignored.

Lead fieldAccepted keysNotes
Namename, NAME, full_name, fullNameOptional
Phonephone, PHONE, phone_numberPhone or email is required. Israeli numbers are normalized for matching
Emailemail, EMAILPhone or email is required
Sourceutm_source, source, SOURCEFalls back to the campaign's default source
Campaign nameutm_campaign, campaign, CAMPAIGNStored as the campaign label. The lead still belongs to the campaign that owns the URL
Contentutm_content, content, CONTENTAd or creative
External lead IDlead_id, leadId, leaderid, LEAD IDYour own ID for the lead
Sales representativesales_representative, SalesRepresentative, salesRepStored with the lead's intake details
MessagemessageThe customer's free-text message, shown on the lead's timeline

Attribution fields are stored with the lead and used for ad reporting and offline conversions: utm_medium, utm_term, and click IDs such as gclid, gbraid, wbraid, fbclid, fbc, fbp, ttclid, li_fat_id and msclkid. Send them as top-level keys.

If your tool nests fields inside a customData object (Make and GoHighLevel do this), CloseUp reads them from there too. A top-level key wins over the same key inside customData.

Custom field names and custom fields

  • Field mapping. If your tool uses other key names (for example mobile or full-name), add them to the campaign's field mapping in Campaigns. Your names are tried first, and the built-in keys above still work.
  • Custom fields. A campaign can declare extra fields (budget, product, preferred time). Send them as keys in the payload and they are saved on the lead. A missing required custom field does not reject the lead. The lead is created and the gap is logged.

Responses

HTTPBodyMeaning
201{ "success": true, "leadId": "..." }A new lead was created
200{ "success": true, "deduped": true, "leadId": "..." }The contact already exists. The submission was added to the existing lead
400{ "error": "Invalid body" }The body could not be parsed
400{ "error": "At least one of phone or email is required" }No phone and no email in the payload
401{ "error": "Unauthorized" }The URL is wrong, or it was replaced with New address
409{ "error": "..." }The contact details conflict with another lead and need manual review
500{ "error": "..." }Server error. Safe to retry

Treat 200 and 201 as success. Retry 500 responses with a backoff. Do not retry 400 or 401 without fixing the request.

Duplicate handling

Before creating a lead, CloseUp looks for an existing lead in your company with the same phone (normalized, so 050-123-4567 matches 0501234567) or the same email. This check covers all campaigns in the company, not just the one that owns the URL.

  • No match: a new lead is created, assigned by the campaign's routing rules, and managers are notified. The response is 201.
  • Match: no second lead is created. The submission is recorded on the existing lead as a new inbound activity, and the lead is marked as returning. New custom field values are merged into the lead without erasing earlier ones. A name captured earlier is never replaced by a blank one. The response is 200 with deduped: true and the existing leadId.

Because duplicates are merged, it is safe to resend the same lead after a timeout. The second request returns 200 with the same leadId.

Recipe: Zapier

Use any Zapier trigger that produces a new lead (a form tool, a spreadsheet row, an ad platform) and send it to CloseUp with a webhook action.

  1. Add the trigger
    Pick the app and event that produces the lead, for example a new form entry.
  2. Add the action
    Choose Webhooks by Zapier and the POST event.
  3. Set the URL
    Paste your campaign webhook URL into URL. Set Payload Type to json.
  4. Map the fields
    Under Data, add rows named name, phone, email and, if you have them, utm_source, utm_campaign and message. Map each one to the matching field from the trigger.
  5. Test
    Run the test step. A 201 response means the lead is in the campaign. Check it in CloseUp under Lead Sources → Recent deliveries.

Recipe: Make

  1. Add the trigger module
    Start the scenario with the module that produces the lead.
  2. Add an HTTP module
    Add HTTP → Make a request. Set URL to your campaign webhook URL and Method to POST.
  3. Build the body
    Set Body type to Raw and Content type to JSON (application/json). Enter a JSON body with name, phone, email and any other fields, mapping values from the trigger module.
  4. Run once
    Run the scenario once and check the response status. 201 means a new lead, 200 means it matched an existing lead.
Example Make request body
{
  "name": "{{1.full_name}}",
  "phone": "{{1.phone}}",
  "email": "{{1.email}}",
  "utm_source": "make",
  "message": "{{1.notes}}"
}

Recipe: n8n

  1. Add the trigger node
    Start the workflow with the node that produces the lead.
  2. Add an HTTP Request node
    Set Method to POST and URL to your campaign webhook URL. Store the URL in an n8n credential or variable, not in a shared workflow export.
  3. Send the body
    Turn on Send Body, set Body Content Type to JSON, and add the parameters name, phone, email and any others, using expressions from the trigger node.
  4. Execute
    Execute the node and confirm a 201 or 200 response.
Example n8n JSON body
{
  "name": "={{ $json.name }}",
  "phone": "={{ $json.phone }}",
  "email": "={{ $json.email }}",
  "utm_source": "n8n"
}
Every delivery, successful or not, appears in CloseUp under Lead Sources → Recent deliveries, with the reason when a lead was refused. Start there when a test lead does not show up.