Embedding forms
Point a website form at a capture hook URL so submissions become leads automatically.
By the end of this page your website form will post straight into SmileLine — no backend code on your side.
Before you start
You need a capture hook and its endpoint URL. Create one under Settings → Capture hooks and copy the URL from the list — it looks like https://api.smileline.io/capture/YOUR_TOKEN. See Capture hooks.
The endpoint accepts application/json, application/x-www-form-urlencoded and multipart/form-data. URL query parameters are merged underneath the body, so pixel-style integrations can ride the query string alone. File uploads are ignored.
Point your form at the hook
A classic form post. The browser navigates to the endpoint's raw JSON response, so prefer the fetch variant to keep visitors on your page:
<form action="https://api.smileline.io/capture/YOUR_TOKEN" method="POST">
<input name="first_name" placeholder="First name" />
<input name="last_name" placeholder="Last name" />
<input name="email" type="email" placeholder="Email" required />
<input name="phone" type="tel" placeholder="Mobile" />
<input name="treatment" type="hidden" value="invisalign" />
<textarea name="message" placeholder="How can we help?"></textarea>
<button type="submit">Request a consultation</button>
</form>Submit in the background and show your own thank-you message. The endpoint allows cross-origin requests, so this works from any website:
const form = document.querySelector("#enquiry-form");
form.addEventListener("submit", async (event) => {
event.preventDefault();
const response = await fetch(
"https://api.smileline.io/capture/YOUR_TOKEN",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
first_name: form.first_name.value,
last_name: form.last_name.value,
email: form.email.value,
phone: form.phone.value,
treatment: "invisalign",
message: form.message.value,
}),
},
);
if (response.ok) showThankYou();
});Test from the command line before wiring up the website:
curl -X POST "https://api.smileline.io/capture/YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"first_name":"Amelia","email":"amelia@example.co.uk","phone":"+447700900123"}'A successful capture answers 201 with {"ok":true} — and the lead appears on the Today page in real time.
Which fields are understood
Field names are set per hook in its Field mapping — the left column below shows the defaults a new hook starts with. Rename them in the hook to match whatever your form already sends.
| Default field name | Fills |
|---|---|
first_name | Patient first name |
last_name | Patient last name |
email | Patient email |
phone | Patient mobile number |
treatment | Matched against a treatment's slug (see Settings → Treatments); unknown values fall back to the hook's Default treatment |
location | Matched against a location id, then a case-insensitive location name; unknown values fall back to the hook's Location — one form can serve every site |
message | Saved as a note on the patient timeline |
Rules worth knowing:
- A valid email or phone is required. A submission with neither (after mapping) is rejected with
400. - Nothing is thrown away. Fields that aren't mapped are kept verbatim on the lead's capture record and shown under Additional information on the lead panel.
- Bad values degrade, they don't fail. An unparseable email or phone is kept in the raw capture record while the other contact detail carries the lead.
- Duplicates attach, they don't multiply. If the email or phone matches an existing patient, the enquiry is attached to them instead of creating a duplicate.
Attribution is automatic
Do not map marketing parameters — they're extracted automatically into the lead's attribution record: utm_* parameters, ad-platform click ids (gclid, fbclid, msclkid, ttclid and many more), landing_url, referrer and user_agent. Just make sure your form forwards the page's query parameters (or posts them alongside the fields). See Attribution.
Rich integrations (e.g. Zapier or a custom tracking script) can also post a structured _sl envelope with visitor context and an event_id idempotency key — re-posting the same event_id never creates a duplicate. The full envelope shape is documented in the API reference under the Capture group at /reference.
Responses
| Status | Meaning |
|---|---|
201 | Captured — body is {"ok":true} |
400 | Invalid body, or no valid email/phone after mapping |
404 | Unknown, paused or archived token |
429 | Rate limited — retry after the number of seconds in the Retry-After header |
A 404 from a URL that used to work usually means the hook was paused,
archived, or its token was regenerated. Check Settings → Capture hooks.