API & webhooks
Work with Leadvect from your own code, Zapier, Make or n8n: add leads to lists, create and move CRM deals, log calls, read replies, enrich businesses — and get a signed call the moment something happens.
Authentication
Create a key on Integrations (workspace owners and admins). Send it with every request:
curl https://leadvect.com/v1/me \
-H "Authorization: Bearer lv_YOUR_KEY"
Base URL https://leadvect.com/v1. Bodies are JSON (Content-Type: application/json). A key acts for its workspace, with that workspace’s plan and allowances. Keep keys on servers — never in a web page or app.
Lists
| Request | What it does |
|---|---|
| GET/lists | Your lists, with member counts. |
| POST/lists | Create a list. {"name": "Paris bakeries", "type": "segment"} → {"id"} |
| GET/lists/{id}/members | Members, newest first. ?limit= (≤ 500) &offset= |
| POST/lists/{id}/members | Add businesses (up to 10,000 per call; repeats are ignored). |
curl -X POST https://leadvect.com/v1/lists/LIST_ID/members \
-H "Authorization: Bearer lv_YOUR_KEY" -H "Content-Type: application/json" \
-d '{"leads":[{"name":"Du Pain et des Idées","domain":"dupainetdesidees.com","city":"Paris","country":"FR","email":"contact@dupainetdesidees.com"}]}'
Lead fields: name, domain, email, phone, city, country (2 letters), category, address, website, social.
Deals (CRM)
| Request | What it does |
|---|---|
| GET/deals | ?stage= ?q= (name, email, company, domain) ?updated_since= (ISO date) ?limit= (≤ 1000) |
| POST/deals | Create — or return the existing deal with the same email (or phone, when there’s no email). 201 when new. |
| GET/deals/{id} | The deal and its timeline notes. |
| PATCH/deals/{id} | Update any field below (POST works too). |
| DELETE/deals/{id} | Delete the deal and its notes. |
| POST/deals/{id}/notes | {"body": "Sent the price list"} |
| POST/deals/{id}/activity | Log a call or WhatsApp message: {"kind":"call","outcome":"call_back","note":"…","nextStep":"Call back","nextDue":"2026-11-03"} |
Deal fields: company, contactName, contactEmail, phone, domain, city, country, stage, value, currency, nextStep, nextDue (YYYY-MM-DD), lostReason.
Stages: new · contacted · replied · interested · meeting · proposal · won · lost. Call outcomes: connected · no_answer · voicemail · call_back · interested · not_interested · wrong_number (a logged activity only moves a deal forward).
Replies
| Request | What it does |
|---|---|
| GET/replies | Replies to your outreach, newest first, with the AI label. ?since= (ISO) ?label= ?limit= (≤ 200) |
Labels: interested · question · meeting · not_now · not_interested · wrong_person · unsubscribe · out_of_office · other.
Enrichment
| Request | What it does |
|---|---|
| POST/enrich | {"domain":"septime.fr","name":"Septime","country":"FR"} → contacts (emails verified), tech, signals, lead score. 1 enrichment from your plan. |
| POST/owner | {"name":"Septime","domain":"septime.fr","country":"FR"} → owners and decision makers (website, company registries). 1 enrichment. |
Webhooks
Add an address on Integrations, or from code (Zapier-style REST hooks):
| Request | What it does |
|---|---|
| GET/webhooks | Your webhooks. |
| POST/webhooks | {"url":"https://…","events":["reply.received"]} → the webhook and its signing secret (shown once). |
| DELETE/webhooks/{id} | Remove it. |
Each event is a POST of JSON to your https address. Answer with any 2xx within 10 seconds. Anything else is retried after 1 min, 5 min, 30 min, 2 h, 6 h and 24 h (7 tries in all); redirects are not followed. Answer 410 Gone to switch the webhook off. A webhook that fails 25 times in a row is paused (turn it back on from Integrations).
Events
{
"id": "evt_6f1c…",
"type": "deal.stage_changed",
"created_at": "2026-10-28T14:03:11.000Z",
"workspace_id": "…",
"data": {
"deal": { "id": "…", "company": "Septime", "contact_email": "direction@septime.fr", "stage": "meeting", "value": 1200, "currency": "EUR", "url": "https://leadvect.com/crm?deal=…", … },
"from": "interested",
"to": "meeting"
}
}
| Type | data |
|---|---|
| reply.received | email, name, company, subject, text, label, label_by (rules | ai), deal_id, received_at |
| deal.created | deal |
| deal.stage_changed | deal, from, to |
| activity.logged | kind (call | whatsapp), outcome, note, next_step, next_due, deal |
| meeting.booked | status (booked | rescheduled), email, name, start, end, title, source, deal |
| meeting.cancelled | Same fields, status = cancelled. |
| contact.unsubscribed | email, source (link | reply | email), campaign_id |
| test.ping | Sent by the Test button. |
The same event can arrive twice (a retry after a timeout): use id to ignore repeats. Deals imported from a CSV don’t send deal.created.
Meetings booked
Your workspace has a private address (Integrations → Meetings) that booking tools call when someone books, reschedules or cancels: POST https://leadvect.com/in/meetings/<your address>. It takes Cal.com and Calendly webhooks as they are, or plain JSON (or form fields) from Zapier, Make or n8n:
{"email": "nadia@bistro.example", "start": "2026-11-04T10:30:00Z", "title": "Intro call", "status": "booked", "ref": "s_…"}
status: booked (default) · rescheduled · cancelled. ref is optional: the utm_content that {{meeting_link}} puts on your booking page link, so a booking made with another address still reaches the right deal. The deal moves to meeting (never backwards), the meeting becomes its next step, and meeting.booked / meeting.cancelled fire.
Checking signatures
Every call carries Leadvect-Signature: t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of "<t>.<raw body>" with your webhook’s signing secret. Also sent: Leadvect-Event and Leadvect-Delivery.
// Node.js
import crypto from 'node:crypto';
function fromLeadvect(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; // 5 minutes
return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 || ''));
}
Errors & limits
- Errors are JSON:
{"error": "…"}with400(bad input),401(missing or revoked key),402(no active plan, or the plan’s allowance is used up),404,429(rate limit — wait forRetry-After). - 120 requests a minute per key.
- Leadvect’s rules apply through the API too: suppressed and unsubscribed contacts are never emailed, and sending always needs a campaign you approved.