Overview
The workspace Settings page includes anIntegrate your site section that shows how to add a lead-capture form to any page you control. Leads signed up through your site land in your organization's workspace. You need theManage roles permission (owners have it).
1. Add your domain
A hostname is the web address where your form page lives (for examplemyproduct.com). Leads submitted from a verified hostname are routed to your organization.
- Open Settings and find the Hostnames section, or the Integrate your site section's step 1.
- Click Add hostname — it appears with an Unverified status and a verification token.
- At your DNS provider, add a TXT record for that hostname with the token as its value.
- Click Verify; if the record has not propagated yet, wait and click it again.
Once verified, leads from that address route to your organization. SeeBranding & hostnames for details on removing a hostname.
2. Add a lead form
Paste this form into your page. On submit it postsname, email, and phone toPOST /api/public/lead and shows the server message.
<form id="lead-form">
<input name="name" type="text" placeholder="Full name" required>
<input name="email" type="email" placeholder="Email address" required>
<input name="phone" type="tel" placeholder="Phone number" required>
<button type="submit">Sign up</button>
<p id="msg"></p>
</form>
<script>
document.getElementById("lead-form").addEventListener("submit", async (e) => {
e.preventDefault();
const f = e.currentTarget;
const msg = document.getElementById("msg");
try {
const res = await fetch("/api/public/lead", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: f.name.value.trim(),
email: f.email.value.trim(),
phone: f.phone.value.trim(),
}),
});
const data = await res.json();
msg.textContent = data.message;
msg.style.color = data.success ? "#16a34a" : "#dc2626";
if (data.success) f.reset();
} catch {
msg.textContent = "Something went wrong";
msg.style.color = "#dc2626";
}
});
</script>Hosting the page outside this deployment? Use your workspace origin instead of the relative path, e.g. https://<your-workspace-domain>/api/public/lead. The endpoint accepts cross-origin requests.
3. Parameters
- name (required) — full name, trimmed, non-empty.
- email (required) — valid email address, lowercased before storing.
- phone (required) — phone number; the WhatsApp auto-reply is sent to it when the lead routes to an organization.
- ref (optional) — referral code; credits the lead to that member.
Responses are JSON. 200 success:true, message:"..." means the lead was captured. 202 returns the same body when the API origin is briefly unavailable. The submission is queued and delivered automatically, so treat 202 as success.400 success:false, message means invalid input, and429 means a rate limit was hit. A duplicate email within your organization is treated as success.
Limits and bot protection
The endpoint bounds what a form can do. Names are capped at 100 characters, phone numbers at 32, and referral codes at 64; over-length values are rejected with 400. Requests are rate limited per visitor IP (100 per minute) and per organization (60 per minute), so a burst is shed with 429 instead of reaching the database.
Every capture that routes to an organization can also send one WhatsApp message, so an organization has a daily cap on those first replies (50 per rolling day). Over the cap the lead is still stored and follow-ups still schedule; only the automatic first message is skipped.
If your deployment enables Cloudflare Turnstile, the branding response addsturnstileSiteKey. Render the widget with that key and send the resulting token as turnstileToken in the request body. When your deployment has Turnstile configured, a capture without a valid token is rejected. With no key, no widget and no token are needed.
How leads are routed
- A form served on a verified hostname of your organization is attributed to it (the request must come from that hostname).
- A request with a valid
refis attributed to that referral code's organization and member. - Requests with no verified hostname and no valid
refenter the platform queue for operator review.
4. Replies (workspace API)
The workspace sends and receives WhatsApp on your organization's connected number. Replies posted from the lead detail page usePOST /api/workspace/leads/:id/reply with a JSON body containingchannel: "whatsapp" and body. The message and its send attempt are stored before the provider is contacted, so an interrupted send is recovered by the maintenance sweep instead of being lost. The endpoint also accepts an optional Idempotency-Key header: repeating the same request with the same key returns the original result instead of sending a second copy.
Reply sends are rate limited per member (20 per minute) and per organization (100 per minute). Over a limit the request answers429 before any message row is written or any AI credit is reserved, so a stuck client cannot spend the organization's balance.
Delivery status updates on the lead's thread as the provider reports it:accepted when the provider takes the message,delivered when it reaches the recipient, andread where the channel reports it. A rejection or bounce isfailed with the provider's reason, and a send whose outcome cannot be confirmed is unconfirmed. The workspace never resends an unconfirmed message automatically; a member can retry it from the thread, and the retry warns that a duplicate is possible.
Inbound WhatsApp and email messages for a lead are stored on the same thread, so the conversation history is complete regardless of channel.
To follow a thread without reloading the whole lead, pollGET /api/workspace/leads/:id/messages?afterId=<id>&limit=<n>. It returns messages newer than the cursor with their attachments and atruncated flag. The workspace app polls it every 10 seconds while idle and every 2.5 seconds while the reply box is focused, and a poll with nothing new returns an empty list.