Leadflo Tracker

HTTP API reference for scrape → webhook → note write-back.

Dashboard

API docs

This service polls Leadflo for Implant leads every minute, POSTs each new lead to your webhook, then accepts an AI response and writes it back as a Leadflo note (test-named leads only by default).

Base URL https://your-app.azurewebsites.net

All JSON endpoints accept Content-Type: application/json.

Auth

  • Dashboard + read APIs are open (no API key) unless you put the app behind Azure Easy Auth / network rules.
  • Inbound AI webhook can require INBOUND_WEBHOOK_SECRET via header X-Webhook-Secret or Authorization: Bearer ….
  • Outbound webhooks are signed with X-Signature (HMAC-SHA256 of body) when WEBHOOK_SECRET is set.

Flow

  1. Poller scrapes Leadflo /actions/due for configured stages.
  2. Filters to tracked treatment types (default: Implant).
  3. Unknown patientId → outbound lead.created webhook.
  4. Your agent replies to POST /api/webhooks/ai-response.
  5. Service writes POST /v3/patients/:id/notes on Leadflo when allowed.

Endpoints

GET/api/health

Liveness + basic config flags.

{
  "ok": true,
  "mode": "live",
  "trackedTypes": ["implant"],
  "pollIntervalMs": 60000,
  "notesOnlyTestNames": true,
  "webhookConfigured": true
}
GET/api/status

Leadflo ping, stats, latest poll run, runtime config.

GET/api/leads

Tracked implant leads (newest first).

{
  "leads": [
    {
      "patientId": "…",
      "fullName": "asif test",
      "phone": "07599 211739",
      "email": "asif@smilefast.com",
      "treatmentType": "Implant",
      "source": "Practice Website",
      "stage": "newLead",
      "isTestName": true,
      "status": "note_written",
      "firstSeenAt": "2026-08-07T13:57:57.353Z",
      "webhookSentAt": "…",
      "noteWrittenAt": "…",
      "aiNote": "…"
    }
  ]
}
GET/api/events

Recent activity log (polls, webhooks, notes).

POST/api/poll

Run one scrape immediately (same work as the 1-minute timer).

{ "discovered": 2, "newLeads": 1, "leads": [/* NormalizedLead */] }
POST/api/webhooks/ai-response

Inbound: AI / n8n posts the note to write into Leadflo.

{
  "patientId": "leadflo-patient-id",
  "note": "AI-written note content",
  "title": "",
  "force": false
}

Responses:

  • note_written — posted to Leadflo
  • note_skipped — stored here, skipped Leadflo write (non-test name)
  • note_failed / unknown_lead — error
POST/api/leads/:patientId/notes

Same as inbound webhook; used by the dashboard Note dialog.

Outbound webhook (new lead)

When a new implant lead is discovered, we POST to WEBHOOK_URL:

{
  "event": "lead.created",
  "platform": "leadflo",
  "trackedTreatmentFilter": ["implant"],
  "lead": {
    "patientId": "…",
    "firstName": "asif",
    "lastName": "test",
    "fullName": "asif test",
    "phone": "07599 211739",
    "email": "asif@smilefast.com",
    "treatmentType": "Implant",
    "source": "Practice Website",
    "stage": "newLead",
    "dueDate": "2026-08-07",
    "labels": ["Completed Implant Contact Form"],
    "isTestName": true,
    "scrapedAt": "2026-08-07T13:57:57.353Z"
  },
  "callback": {
    "noteWebhook": "https://your-app.azurewebsites.net/api/webhooks/ai-response",
    "description": "POST JSON { patientId, note, title? } when AI has a response."
  }
}

Headers: X-Webhook-Secret, X-Signature (if WEBHOOK_SECRET set).

Inbound AI note

With NOTES_ONLY_TEST_NAMES=true (default), Leadflo notes are only written when fullName contains test. Pass "force": true to override.

Examples

# Health
curl -s "$BASE/api/health" | jq

# Force a scrape
curl -s -X POST "$BASE/api/poll" | jq

# List tracked leads
curl -s "$BASE/api/leads" | jq '.leads[] | {fullName, status, isTestName}'

# AI note write-back (test lead)
curl -s -X POST "$BASE/api/webhooks/ai-response" \
  -H 'Content-Type: application/json' \
  -H "X-Webhook-Secret: $INBOUND_WEBHOOK_SECRET" \
  -d '{"patientId":"…","note":"Thanks for your implant enquiry — we will call shortly."}' | jq