Settings
Team, pipeline, tags, branding, integrations, enrichment, and the audit log.
.env.local for development and in the hosting provider for production, then restart. Provider credentials are never stored in the database.API keys
Other software uses a key to send leads to this workspace. Each tool should get its own key so it can be revoked on its own.
No keys yet.
Webhooks
When something happens in the CRM, a signed JSON POST goes to each URL below. Deliveries time out after 5 seconds and an endpoint pauses itself after 10 failures in a row.
No webhooks yet.
Import and export
CSV files up to 2 MB and 2,000 rows. Columns are matched by header name, in any order.
Required: address, city, state, zip. Optional: first_name, last_name or name, phone, email, asking_price, source, urgency, beds, baths, sqft, year_built, property_type, notes, external_id. A row that matches an open lead at the same address adds a note to that lead. SMS consent is always set to unknown.
Required: first_name, name, or company. Optional: last_name, phone, email, website, states (two letter codes separated by commas or spaces, for example MD, PA), source, notes. A row is skipped when its email or phone already belongs to a buyer. A row with no phone and no email is skipped when a buyer with the same company and first name exists. A buyers export can be imported as it is.
Cells that begin with =, +, -, or @ are prefixed with a single quote so a spreadsheet does not run them as formulas.
Connect other software
Anything that can send or receive an HTTP request can connect: Zapier, Make, a website form, a dialer, a list provider, or a script.
1. Send leads in
Create an API key above, then POST JSON to https://wholesale-demo-six.vercel.app/api/v1/leads. Send one lead, or { "leads": [...] } with up to 200. Field names may be camelCase or snake_case. JSON is expected; form encoded and multipart form posts work for a single lead. Required: address, city, state, zip. Notes may be up to 5,000 characters. A lead at an address that already has an open lead is not created twice; a note is added instead and the response marks it as a duplicate. The limit is 120 requests per minute per key.
curl -X POST https://wholesale-demo-six.vercel.app/api/v1/leads \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"address":"123 Main St","city":"Baltimore","state":"MD","zip":"21201","name":"Jane Doe","phone":"410-555-0100","asking_price":150000,"source":"Website form"}'Check that a key works:
curl https://wholesale-demo-six.vercel.app/api/v1/ping -H "Authorization: Bearer YOUR_API_KEY"
Read leads back, newest first, for polling triggers:
curl "https://wholesale-demo-six.vercel.app/api/v1/leads?updatedSince=2026-01-01T00:00:00Z&limit=100" -H "X-Api-Key: YOUR_API_KEY"
2. Send events out
Add a webhook above. Each delivery is a JSON POST with the shape { id, event, createdAt, orgId, data } and the headers X-DealCalc-Event, X-DealCalc-Delivery, X-DealCalc-Timestamp, and X-DealCalc-Signature. The body id names the event and is the same at every endpoint that receives it, so use it to ignore an event you have already handled. X-DealCalc-Delivery is different for every delivery attempt and matches one row in the delivery list above. Reply with any 2xx status within 5 seconds. Failed deliveries are not retried, so poll the leads endpoint if you need a backstop.
3. Verify the signature
The signature is sha256= plus the hex HMAC-SHA256 of {timestamp}.{raw body} using the endpoint signing secret. Reject requests older than 5 minutes. Node example:
const crypto = require("node:crypto");
// rawBody must be the exact bytes received, before any JSON parsing.
function verifyDealCalcWebhook(headers, rawBody, secret) {
const timestamp = headers["x-dealcalc-timestamp"];
const given = String(headers["x-dealcalc-signature"] || "").replace("sha256=", "");
const expected = crypto.createHmac("sha256", secret).update(timestamp + "." + rawBody).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300; // 5 minutes
const a = Buffer.from(expected);
const b = Buffer.from(given);
return fresh && a.length === b.length && crypto.timingSafeEqual(a, b);
}4. Zapier and Make
- Into the CRM (Zapier): add the action Webhooks by Zapier, event POST. URL
https://wholesale-demo-six.vercel.app/api/v1/leads, payload type json, map your fields under Data, and add the headerAuthorizationwith the valueBearer YOUR_API_KEY. - Out of the CRM (Zapier): start a Zap with the trigger Webhooks by Zapier, event Catch Hook. Copy the hook URL Zapier gives you, paste it above as a new webhook, pick the events, then press Send test so Zapier sees a sample.
- Make: use the HTTP module, Make a request, for inbound leads with the same URL and header. For outbound, create a Custom webhook in Make and paste its URL above as a webhook.
- Polling instead of webhooks: Webhooks by Zapier, event Retrieve Poll, URL
https://wholesale-demo-six.vercel.app/api/v1/leads, keyleads, with the X-Api-Key header. Zapier removes duplicates by the id field. - Spreadsheets: use the CSV import and export above. No key is needed.
The full reference, including every event payload, is in docs/INTEGRATIONS.md under Connecting other software.