Send leads to Moonshot EHR

Post each form submission from your site or CRM to the clinic. The lead lands on the clinic's Leads board with its source, campaign, notes and the service the visitor asked about. With the clinic's secret API key you can also read whether the lead has booked.

The request

POST https://api.moonshotehr.com/api/leads/webhook
X-Tenant-Slug: <clinic>
X-Webhook-Key: <key the clinic gives you>
Content-Type: application/json

The response says whether the note landed:

{ "ok": true, "lead_id": "…", "existing_patient": false, "note_saved": true, "note_status": "saved" }

existing_patient is true when the lead matched someone already in the clinic's records and false for a new person. Sending the same lead again within 24 hours does not create a second lead; it answers 200 with "deduplicated": true.

Key names

Keys are matched after lower-casing and turning every run of spaces or punctuation into one underscore, so the labels a CRM sends work as-is: UTM Source, utm-source and utm_source are the same key; Appointment Type is appointment_type; E-mail is email. Fields nested under customData, custom_fields, contact, data, fields or form are read as if they were top level (a top-level key wins on a clash).

What the form asksSend as (any of)Where it shows
Emailemail, email_addresscontact
Phonephone, phone_number, mobilecontact
Namename, full_name, or first_name + last_namecontact
Sourcesource, contact_source, lead_sourcesource badge
Event / webinar sign-uptags (text or list), contact_tags, utm_campaign, form_name, campaign_name, event_name, workflow_namesource badge Event / Webinar (see below)
Campaignutm_source, utm_medium, utm_campaign, utm_content, utm_term, gclid, fbclid, msclkid, landing_page, last_page, referrerAttribution
Appointment type / serviceappointment_type, service_interest, service, interestboard card + From the form
Patient status (new / existing)patient_status, patient_type, new_or_existingFrom the form
Referred by (a person)referred_by, referral_source, who_referred_you, how_did_you_hear_about_usFrom the form
Message / health historysee Notes belowtimeline, "From the form"
Anything elseany other keyFrom the form, labelled by the key

Anything else: every other text field you choose to send is kept on the lead and shown in the clinic's lead drawer under its key name (best_time_to_call shows as "Best time to call"). One line each, up to 500 characters, at most 25 such fields per lead; CRM plumbing (contact_id, location_id, workflow_id, tags, dates) is skipped. Adding a question to the form needs no change on the Moonshot side.

The response carries a receipt of what landed:

"captured": {
  "source": "meta",
  "attribution": ["utm_source", "utm_campaign", "landing_page"],
  "service_interest": "TRT consult",
  "patient_status": "New patient",
  "referral_source": "Dr. Smith",
  "note": "saved",
  "form_fields": ["best_time_to_call"],
  "ignored_keys": []
}

A field missing from captured did not land: check its key name against the table. ignored_keys lists fields past the 25-field cap.

Event and webinar sign-ups

A sign-up for an event or webinar that runs through a Facebook, Google or TikTok funnel is filed under Event or Webinar, not under the ad platform, when any of the fields in the table contains webinar (Webinar) or event, seminar or workshop (Event), or is exactly zoom / online event (Webinar) or open house / in person (Event).

Notes from the form

Whatever the visitor typed into the form's free-text box is kept on the lead as a note labelled From the form. Send it as ONE of these keys; the first non-empty one in this order wins: notes (preferred), comments, message, health_history, additional_info.

Service interest

The service the visitor asked about ("Weight loss consult"). Send it as ONE of: service_interest (preferred), service, interest.

When the lead becomes a patient

The clinic can have Moonshot EHR tell your system when its front desk converts a lead to a patient, so your CRM can start its new-patient follow-up. It is sent once, when staff click Convert to patient. A lead that already matched a patient when it arrived does not fire it.

{
  "event": "lead.converted",
  "tenant_slug": "northside",
  "lead_id": "8d1c…",
  "patient_id": "f47a…",
  "email": "dana@example.com",
  "phone": "6125550142",
  "first_name": "Dana",
  "last_name": "Prospect",
  "source": "event",
  "attribution": { "utm_source": "facebook", "utm_campaign": "oct-event" },
  "service_interest": "TRT consult",
  "converted_at": "2026-10-02T15:04:05.000Z"
}

Signature, headers and delivery rules: Receive signed events.

With the clinic's secret API key

For the clinic's own integration, from a server: create the lead, correct its contact details, and read whether the person has booked, finished onboarding or become a member. It uses the clinic's secret API key (see Secret API keys) instead of the lead key above, and lands on the Leads board exactly like a form submission: same source mapping, same 24 hour duplicate rule, same note from the form.

RoutePermissionPer key, per minute
POST /api/v1/leadsleads:write60
PATCH /api/v1/leads/{id}leads:write60
GET /api/v1/leads/{id}/statusleads:read120
curl -s https://api.moonshotehr.com/api/v1/leads \
  -H 'Authorization: Bearer <key>' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: form-submission-81723' \
  -d '{ "email": "dana@example.com", "first_name": "Dana", "last_name": "Prospect", "phone": "612-555-0142",
        "source": "website", "service_interest": "Weight loss consult", "notes": "Mornings work best.",
        "attribution": { "utm_source": "google", "utm_campaign": "fall" } }'
{ "data": { "id": "8d1c2b3a-4e5f-4a6b-9c7d-0e1f2a3b4c5d", "status": "new", "created_at": "2026-11-16T15:04:05.000Z", "duplicate": false, "note_saved": true }, "next_cursor": null }

Progress, with the same definitions as the clinic's own conversion report: booked (an appointment booked after the lead came in, even if later cancelled or missed), onboarding_completed (forms completed, none outstanding), member (a membership started after the lead came in). No names, contact details, clinical data or amounts.

{ "data": { "id": "8d1c2b3a-4e5f-4a6b-9c7d-0e1f2a3b4c5d", "booked": true, "onboarding_completed": false, "member": false, "updated_at": "2026-11-18T14:00:00.000Z" }, "next_cursor": null }