Partner API
Show a NeuSynapse account inside your platform
NeuSynapse runs AI agents that answer a business's calls, forms, WhatsApp and website chat, qualify the person, book appointments and write a summary on the lead's card. This API lets your platform see that work: the account's status, its leads and what the AI wrote about them. To change anything, the user opens NeuSynapse with the link that comes with every lead.
Overview
| Piece | What it does |
|---|---|
| Read API | Four GET endpoints with a per-account API key: account status, leads, one lead with its summaries. |
| Webhooks | We POST a signed event to your URL when a lead is created or changes, a call is summarized, an appointment is booked or a form arrives. |
| Deep links | Every lead carries url, which opens it in NeuSynapse. It asks the user to log in — the link alone shows nothing. |
The API is read-only by design. No key can send messages, place calls, change the agent, read conversations, recordings or transcripts, or see another account. Notes a person writes by hand in NeuSynapse are never shared.
Getting access
Each NeuSynapse account owner creates a key in Settings → Integrations (API) and gives it to you. One key gives access to exactly one account. The owner chooses what it can see and can remove its access at any time — it stops working immediately.
| Scope | Allows |
|---|---|
status:read | GET /status |
leads:read | GET /leads, GET /leads/{id} |
Keys look like nsk_live_<12 characters>_<43 characters>. We only store a hash: if one is lost, the owner creates a new one.
Authentication
Send the key in the Authorization header of every request. Base URL:
https://zvwbauwimsrntekwinvh.supabase.co/functions/v1/partner-api/v1curl https://zvwbauwimsrntekwinvh.supabase.co/functions/v1/partner-api/v1/status \
-H "Authorization: Bearer nsk_live_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"Call the API from your servers only. Keys are secrets: never put them in a browser, a mobile app or a URL. The API sends no CORS headers, so browsers can't call it directly. Responses carry Cache-Control: no-store.
Errors
Errors use HTTP status codes and a JSON body:
{ "error": { "code": "insufficient_scope", "message": "This key doesn't have the leads:read scope." } }| Status | code | Meaning |
|---|---|---|
| 400 | invalid_parameter | A query parameter is not valid. |
| 401 | invalid_api_key | Missing, malformed, unknown or revoked key (same answer for all four). |
| 403 | insufficient_scope | The key doesn't have the scope this endpoint needs. |
| 403 | account_inactive | The NeuSynapse account is suspended or cancelled. |
| 403 | not_in_plan | The account's plan doesn't include integrations. |
| 404 | not_found | Unknown endpoint, or a lead that doesn't exist in this account. |
| 405 | method_not_allowed | Only GET is accepted. |
| 429 | rate_limited | Too many requests. Wait the seconds in Retry-After. |
| 500 / 503 | internal_error | Our side. Retry with backoff (unavailable comes with Retry-After). |
Rate limits
120 requests per minute per key and 300 per minute per IP address. Over that, the answer is 429 with Retry-After. Prefer webhooks over polling.
GET /me
Which account and key you are using. Useful to check a key when the user pastes it.
{
"account_id": "1f4e…",
"key": { "name": "Realeflow", "prefix": "ab12cd34ef56", "scopes": ["status:read", "leads:read"], "created_at": "2026-11-13T15:02:11.000Z" }
}GET /status
A summary for a dashboard widget. days: 1–30, default 7.
{
"account_id": "1f4e…",
"agents": { "total": 2, "answering": 1 },
"period": { "from": "2026-11-06T00:00:00.000Z", "to": "2026-11-13T00:00:00.000Z", "days": 7 },
"counts": {
"leads_new": 38,
"calls_summarized": 52,
"appointments_booked": 6,
"forms_submitted": 14,
"hot_leads": 11
},
"recent_leads": [ …up to 10 lead objects, most recently updated first… ],
"links": {
"dashboard": "https://app.neusynapse.com/dashboard",
"pipeline": "https://app.neusynapse.com/dashboard/crm/pipeline"
}
}hot_leads is the current number of active leads marked hot, not limited to the period.
GET /leads
| Parameter | Description |
|---|---|
updated_since | ISO 8601 date or date-time. Only leads changed at or after it. |
limit | 1–100, default 50. |
cursor | The next_cursor of the previous page, exactly as it came. |
include_archived | true to include leads removed from the Pipeline (archived: true). Default false. |
Leads come oldest change first. Follow next_cursor until it is null. To sync, store the newest updated_at you saw and ask again with updated_since a few minutes earlier, deduplicating by id.
{ "data": [ …lead objects… ], "next_cursor": "eyJ1IjoiMjAyNi0xMS0xM1Qw…" }GET /leads/{id}
One lead plus its last 20 automatic notes (newest first): AI call summaries, calls made from the NeuSynapse portal, appointments and forms.
{
…lead object…,
"notes": [
{ "id": "…", "type": "call", "created_at": "…", "call": { "handled_by": "ai", "direction": "inbound", "duration_seconds": 252, "summary": "…" } },
{ "id": "…", "type": "appointment", "created_at": "…", "appointment": { "starts_at": "2026-11-14T15:00:00.000Z", "timezone": "America/New_York", "services": ["Property visit"], "attendant": "Ana", "channel": "voice" } },
{ "id": "…", "type": "form", "created_at": "…", "form": { "name": "Sell your house fast", "page_url": "https://example.com/sell", "fields": [{ "label": "Property address", "value": "2218 Pine Hollow Dr" }] } }
]
}The lead object
{
"id": "6f1c2a1e-6c1f-4f2b-9d77-2b7f0f1f9a10",
"name": "Margaret Collins",
"phone": "+14075550142",
"email": null,
"stage": { "name": "Qualified", "kind": "open" },
"temperature": "hot",
"source": "ai_call",
"archived": false,
"created_at": "2026-11-12T20:41:07.512Z",
"updated_at": "2026-11-13T01:47:00.000Z",
"stage_changed_at": "2026-11-13T01:47:00.000Z",
"url": "https://app.neusynapse.com/dashboard/crm/pipeline?card=6f1c2a1e-6c1f-4f2b-9d77-2b7f0f1f9a10"
}| Field | Type | Notes |
|---|---|---|
id | uuid | Stable. Use it to match the lead in your system. |
name | string | null | Up to 120 characters. |
phone | string | null | E.164, e.g. +14075550142. |
email | string | null | |
stage | object | null | name as the account named it; kind: open, won or lost. |
temperature | string | null | hot, warm or cold. |
source | string | How it arrived: ai_call, portal_call, form, whatsapp, instagram, messenger, sms, web, appointment, manual (or other). |
archived | boolean | Removed from the Pipeline. |
created_at / updated_at / stage_changed_at | ISO 8601 | UTC. |
url | string | Opens the lead in NeuSynapse (login required). |
New fields may be added at any time. Ignore the ones you don't know.
Webhooks
The account owner adds your URL in Settings → Integrations (API), picks the events and receives a signing secret (whsec_…), shown once — they give it to you. Each account can have up to 3 webhook URLs.
- Your URL must be
httpson port 443 and resolve to a public address. - Answer with any
2xxwithin 10 seconds. Do the work afterwards. - Redirects are not followed (a
3xxcounts as a failure). We read at most 4 KB of your answer and never store it.
| Header | Value |
|---|---|
NeuSynapse-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256> — see below. |
NeuSynapse-Event-Id | Same as the body's id. Unique per event; retries reuse it. |
NeuSynapse-Event-Type | e.g. call.summarized. |
NeuSynapse-Delivery-Attempt | 1, 2, 3… |
Content-Type | application/json |
{
"id": "0b6f3c8e-2f0e-4a51-8c55-7f3b8f6f4f21",
"type": "call.summarized",
"api_version": "2026-11-13",
"created_at": "2026-11-13T01:47:00.000Z",
"account_id": "1f4e…",
"data": {
"lead": { …the lead object… },
"note_id": "c2a9…",
"call": {
"handled_by": "ai",
"direction": "inbound",
"duration_seconds": 252,
"ended_at": "2026-11-13T01:46:41.000Z",
"summary": "Inherited the house, lives out of state, wants to sell within 30 days…",
"campaign": null,
"temperature": null,
"next_step": null
}
}
}Event types
| type | When | data |
|---|---|---|
lead.created | A person is added to the account's Pipeline (AI marked them interested, a form, an appointment, or by hand). | lead |
lead.updated | Stage, temperature or archived changed. | lead, changes e.g. {"stage":{"from":"New","to":"Qualified"}} |
call.summarized | An AI call (or a call made from the NeuSynapse portal) ended with a summary. | lead, note_id, call |
appointment.booked | An appointment was booked for the lead. | lead, note_id, appointment |
form.submitted | The lead sent a NeuSynapse form. | lead, note_id, form |
ping | The owner pressed “Test” in NeuSynapse. | message |
data.lead is the lead object as it was when the event was sent. call, appointment and form have the same shape as the notes of GET /leads/{id}.
Verifying signatures
Always verify before trusting an event. Compute HMAC-SHA256 of <t>.<raw body> with the signing secret and compare it with v1in constant time. Reject events whose tis more than 5 minutes away from your clock. Use the raw body — re-serialized JSON won't match.
import crypto from "node:crypto";
// rawBody: the request body EXACTLY as received (a string or Buffer), before JSON.parse.
export function verifyNeuSynapse(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(
String(header || "").split(",").map((p) => p.split("=").map((s) => s.trim()))
);
const t = Number(parts.t);
if (!Number.isInteger(t) || !parts.v1) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false; // replay protection
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(parts.v1, "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}import hmac, hashlib, time
def verify_neusynapse(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.strip().split("=", 1) for p in (header or "").split(",") if "=" in p)
try:
t = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - t) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))When the owner creates a new secret in NeuSynapse, the old one stops working immediately.
Delivery and retries
Events are sent within about a minute. A failed delivery (no answer in 10 s, network error, 3xx, 4xx or 5xx) is retried after 1 min, 5 min, 15 min, 1 h, 3 h, 6 h and 12 h — 8 attempts over about a day — then dropped.
- Delivery is at least once: the same event can arrive twice. Deduplicate by
id. - Order is not guaranteed. Use
created_atand the lead'supdated_at, or fetch the lead withGET /leads/{id}. - After 20 failures in a row with no success in 24 hours, the webhook is switched off and the owner is notified in NeuSynapse. They turn it back on there.
Security notes
- Store keys and signing secrets like passwords (a secrets manager or encrypted at rest). Use a separate key for each environment.
- Form answers, names and summaries contain text written by members of the public. Escape them before showing them in HTML; never run them as code or queries.
- The data is personal information of the account's customers. Use it only to show it to that account's users, and delete it when the account disconnects.
- Found a security issue? Write to
contact@neusynapse.com.
Versioning
Current version: 2026-11-13 (header NeuSynapse-Api-Version, and api_version in every event). We may add endpoints, fields and event types without notice. Removing or changing an existing field means a new version, announced in advance.
Questions: contact@neusynapse.com.