Zum Inhalt springen

API / Webhooks

SalesFrank lässt sich vollständig programmatisch anbinden. Es gibt zwei Richtungen:

  • Rein: die Lead-API - dein System schickt Leads an SalesFrank, wir rufen an (bei aktiver Kampagne in der Regel innerhalb von 60 Sekunden).
  • Raus: Webhooks - nach jedem Anruf schickt SalesFrank Ergebnis, Zusammenfassung und Transkript an dein System.

Zusammen ergibt das den geschlossenen Kreis: Facebook Lead Ads, dein CRM, Zapier oder n8n legen den Lead an, Frank ruft an, das Ergebnis fließt automatisch zurück.

Zeitpunkt Wofür Beispiel
Vor dem Anruf Kontext laden, bevor das Gespräch startet Nummer ruft an → Webhook an n8n → Kundendaten aus CRM → personalisierte Begrüßung
Während des Anrufs Live auf ein System zugreifen „Wann kommt meine Bestellung?“ → Webhook zu Shopify → Lieferstatus → Antwort im Gespräch
Nach dem Anruf Ergebnis zurückspielen Anruf beendet → Ergebnis + Transkript → CRM-Eintrag oder n8n-Workflow

Über die Lead-API spielen externe Systeme Leads direkt in eine Kampagne ein und verwalten sie über ihren gesamten Lebenszyklus.

Alle Endpunkte liegen unter https://api.salesfrank.ai. Jede Anfrage muss über HTTPS gesendet werden und bei schreibenden Zugriffen den Header Content-Type: application/json setzen.

Jede Anfrage authentifiziert sich mit einem Kampagnen-API-Schlüssel als Bearer-Token. Der Schlüssel ist an genau eine Kampagne gebunden - du übergibst nie eine Kampagnen-ID, der Schlüssel bestimmt, zu welcher Kampagne der Lead gehört.

Terminal-Fenster
Authorization: Bearer sf_live_ihr_api_schluessel

So kommst du an den Schlüssel:

  1. Im Dashboard zu Kampagne → Leads → Leads importieren → API-Integration gehen.
  2. Schlüssel generieren - er sieht aus wie sf_live_….
  3. Sofort kopieren: Der vollständige Schlüssel wird nur einmal angezeigt. Später kannst du ihn nur neu generieren, nicht erneut anzeigen.

Anfragen mit fehlendem oder ungültigem Schlüssel werden mit 401 Unauthorized abgelehnt: fehlender Authorization-Header, falsches Format (nicht Bearer <Schlüssel>) oder ein ungültiger bzw. widerrufener Schlüssel.

Sende deinen ersten Lead und löse einen Anruf aus:

Terminal-Fenster
curl -X POST https://api.salesfrank.ai/api/v1/leads/ingest \
-H "Authorization: Bearer sf_live_ihr_api_schluessel" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "+491701234567",
"first_name": "Maria",
"surname": "Schmidt",
"email": "maria@example.com",
"company_name": "Example GmbH",
"source": "facebook",
"custom_variables": { "produkt": "Solar", "budget": "5000" }
}'

Antwort:

{
"success": true,
"lead_id": "9c04a7e2-1f3b-4d5a-8c6e-2b1a0f9e8d7c",
"is_duplicate": false,
"scheduled_at": null,
"message": "Lead created successfully. Call will be placed within 60 seconds."
}

POST /api/v1/leads/ingest

Erstellt einen neuen Lead und plant einen Anruf. Duplikate innerhalb von 24 Stunden werden automatisch zusammengeführt (siehe Duplikat-Behandlung).

Feld Typ Beschreibung
phone_number string Pflichtfeld. Telefonnummer, empfohlen im E.164-Format (z.B. +491701234567). Andere Formate werden nach Möglichkeit normalisiert.
first_name string Vorname des Leads.
name string Vollständiger Name - Alternative zu first_name, wenn du Vor-/Nachname nicht trennst.
surname string Nachname des Leads.
email string E-Mail-Adresse. Wird geprüft, wenn vorhanden.
company_name string Firmenname.
source string Herkunft des Leads (z.B. facebook, crm).
timezone string IANA-Zeitzone des Leads (z.B. Europe/Berlin). Fällt auf die Kampagnen-Zeitzone zurück.
custom_variables object Benutzerdefinierte Schlüssel-Wert-Paare, die dem Agenten als {{variablen_name}} im Prompt zur Verfügung stehen (siehe Benutzerdefinierte Variablen).

Antwortfelder

Feld Bedeutung
success true, wenn der Lead angenommen wurde.
lead_id ID des erstellten (bei einem Duplikat: des vorhandenen) Leads - für Abrufen / Aktualisieren / Löschen.
is_duplicate true, wenn die Nummer in dieser Kampagne schon innerhalb der letzten 24 Stunden existierte - der bestehende Lead wurde aktualisiert und kein neuer Anruf geplant.
scheduled_at Reserviert für eine künftige explizite Planungszeit. Derzeit immer null - der Dispatcher platziert den Anruf asynchron.
message Menschenlesbarer Status, z.B. „Call will be placed within 60 seconds.“ bzw. „…when campaign is activated.“ bei inaktiver Kampagne.
const res = await fetch("https://api.salesfrank.ai/api/v1/leads/ingest", {
method: "POST",
headers: {
Authorization: "Bearer sf_live_ihr_api_schluessel",
"Content-Type": "application/json",
},
body: JSON.stringify({ phone_number: "+491701234567", first_name: "Maria" }),
});
const data = await res.json();
import requests
res = requests.post(
"https://api.salesfrank.ai/api/v1/leads/ingest",
headers={"Authorization": "Bearer sf_live_ihr_api_schluessel"},
json={"phone_number": "+491701234567", "first_name": "Maria"},
)
data = res.json()

POST /api/v1/leads/batch

Erstellt bis zu 100 Leads in einer Anfrage. Jeder Lead wird unabhängig verarbeitet - eine fehlerhafte Zeile lässt den Batch nie fehlschlagen, ihr Fehler wird in results gemeldet.

Terminal-Fenster
curl -X POST https://api.salesfrank.ai/api/v1/leads/batch \
-H "Authorization: Bearer sf_live_ihr_api_schluessel" \
-H "Content-Type: application/json" \
-d '{
"leads": [
{ "phone_number": "+491701234567", "first_name": "Maria" },
{ "phone_number": "+491709876543", "first_name": "Tom", "source": "crm" }
]
}'
{
"total": 2,
"created": 2,
"duplicates": 0,
"errors": 0,
"results": [
{ "index": 0, "success": true, "lead_id": "", "is_duplicate": false },
{ "index": 1, "success": true, "lead_id": "", "is_duplicate": false }
]
}

GET /api/v1/leads

Ruft Leads mit Paginierung, Filterung und Sortierung ab. Gelöschte Leads werden standardmäßig ausgeschlossen.

Parameter Beschreibung
page Seitennummer. Standard 1.
limit Ergebnisse pro Seite.
sort / order Sortierfeld und Reihenfolge (asc / desc).
status Nach Status filtern (siehe Lead-Status-Werte).
source Nach Lead-Quelle filtern.
phone_number Nach Telefonnummer filtern (E.164).
email Nach E-Mail-Adresse filtern.
created_after / created_before Nur Leads, die an/nach bzw. an/vor diesem Datum erstellt wurden (ISO 8601).
is_test Nach Test-Flag filtern.
Terminal-Fenster
curl "https://api.salesfrank.ai/api/v1/leads?status=NEW&limit=20&sort=created_at&order=desc" \
-H "Authorization: Bearer sf_live_ihr_api_schluessel"
{
"data": [
{
"id": "9c04a7e2-…",
"campaign_id": "db922785-…",
"name": "Maria",
"surname": "Schmidt",
"phone_number": "+491701234567",
"status": "NEW",
"attempts": 0,
"source": "facebook",
"custom_variables": { "produkt": "Solar" },
"is_test": false,
"created_at": "2026-07-19T10:12:00.000Z"
}
],
"pagination": { "page": 1, "limit": 20, "total": 1, "total_pages": 1 }
}

GET /api/v1/leads/{id}

Liefert einen Lead inklusive Status, Anrufversuchen, benutzerdefinierten Variablen und KI-extrahierten Daten. Antwortet 404, wenn die ID kein Lead dieser Kampagne ist.

Terminal-Fenster
curl https://api.salesfrank.ai/api/v1/leads/9c04a7e2-1f3b-4d5a-8c6e-2b1a0f9e8d7c \
-H "Authorization: Bearer sf_live_ihr_api_schluessel"

PATCH /api/v1/leads/{id}

Aktualisiert die Eigenschaften eines Leads. Ein Lead kann nicht aktualisiert werden, während ein Anruf läuft (Status CALLING).

Aktualisierbare Felder: first_name (max. 100 Zeichen), surname (max. 100), salutation (max. 20), company_name (max. 200), email (validiert), phone_number (E.164, wird auf Konflikte mit anderen Leads der Kampagne geprüft), source (max. 50), timezone und custom_variables.

Terminal-Fenster
curl -X PATCH https://api.salesfrank.ai/api/v1/leads/9c04a7e2-… \
-H "Authorization: Bearer sf_live_ihr_api_schluessel" \
-H "Content-Type: application/json" \
-d '{ "email": "neu@example.com", "custom_variables": { "budget": "8000" } }'

PATCH /api/v1/leads/batch

Aktualisiert bis zu 100 Leads in einer Anfrage. Jede Aktualisierung wird unabhängig angewendet, Einzelfehler stehen in results.

Terminal-Fenster
curl -X PATCH https://api.salesfrank.ai/api/v1/leads/batch \
-H "Authorization: Bearer sf_live_ihr_api_schluessel" \
-H "Content-Type: application/json" \
-d '{
"updates": [
{ "id": "9c04a7e2-…", "source": "crm" },
{ "id": "1b3c5d7e-…", "email": "tom@example.com" }
]
}'

DELETE /api/v1/leads/{id}

Soft-löscht einen Lead und schließt ihn vom Dispatch aus. Die Daten bleiben erhalten, der Lead erscheint aber nicht mehr in der Standardliste und wird nicht angerufen.

Terminal-Fenster
curl -X DELETE https://api.salesfrank.ai/api/v1/leads/9c04a7e2-… \
-H "Authorization: Bearer sf_live_ihr_api_schluessel"
{ "success": true }

Batch: POST /api/v1/leads/batch/delete soft-löscht bis zu 200 Leads in einer Anfrage.

Terminal-Fenster
curl -X POST https://api.salesfrank.ai/api/v1/leads/batch/delete \
-H "Authorization: Bearer sf_live_ihr_api_schluessel" \
-H "Content-Type: application/json" \
-d '{ "ids": ["9c04a7e2-…", "1b3c5d7e-…"] }'
Feld Beschreibung
id Eindeutige Lead-ID.
campaign_id Kampagne, zu der dieser Lead gehört.
name / surname Vorname (oder vollständiger Name) und Nachname.
phone_number Normalisierte Telefonnummer (E.164).
status Aktueller Status (siehe unten).
attempts Anzahl der durchgeführten Anrufversuche.
submission_count Wie oft dieser Lead übermittelt wurde (steigt bei Duplikat-Übermittlungen).
custom_variables Die benutzerdefinierten Schlüssel-Wert-Paare des Leads.
extracted_data Strukturierte Daten, die die KI während des Anrufs extrahiert hat.
failure_reason Grund für den Status FAILED, sofern zutreffend.
is_test Ob es sich um einen Test-Lead handelt.
created_at Erstellungszeitpunkt (ISO 8601).
next_attempt_at Wann der nächste Anrufversuch geplant ist.
callback_requested_at Gewünschte Rückrufzeit, wenn der Lead um einen Rückruf gebeten hat.
deleted_at Soft-Delete-Zeitstempel; null bei aktiven Leads.
Status Bedeutung
NEW Erstellt, wartet auf Dispatch.
QUEUED Vom Dispatcher übernommen, wird gleich angerufen.
CALLING Ein Anruf läuft gerade.
CONTACTED Ein Mensch wurde erreicht, das Gespräch wurde klassifiziert.
COMPLETED Der Anrufzyklus des Leads ist abgeschlossen (keine weiteren Versuche).
FAILED Der Lead konnte nicht angerufen werden (siehe failure_reason).

custom_variables sind beliebige Schlüssel-Wert-Paare, die mit dem Lead mitreisen und dem Agenten als {{variablen_name}}-Platzhalter im Prompt zur Verfügung stehen - so kann ein Anruf den Lead mit seinem Produktinteresse, genanntem Preis oder jedem anderen Kontext ansprechen.

  • Schlüssel werden zu snake_case normalisiert. Beispiel: "Produkt Interesse"produkt_interesse, im Prompt referenzierbar als {{produkt_interesse}}. Werte werden exakt wie gesendet gespeichert.
  • Neue Variablenschlüssel werden automatisch in der Kampagne registriert und erscheinen sofort in der Variablenliste, sodass du sie im Agenten-Skript verwenden kannst.
  • Beim Aktualisieren werden Variablen mit den vorhandenen Werten zusammengeführt, nicht ersetzt.

Nimmst du eine Telefonnummer auf, die in derselben Kampagne innerhalb der letzten 24 Stunden bereits existiert, wird kein zweiter Lead erstellt. Stattdessen wird der bestehende Lead mit den neuen Feldern aktualisiert, sein submission_count erhöht, und die Antwort liefert is_duplicate: true sowie scheduled_at: null - es wird kein neuer Anruf geplant. Übermittlungen außerhalb des 24-Stunden-Fensters erstellen einen neuen Lead.

Fehler nutzen Standard-HTTP-Statuscodes und liefern einen JSON-Body mit einer Nachricht (und teilweise einem maschinenlesbaren error-Code).

Status Ursache
401 Nicht autorisiert Fehlender Authorization-Header, falsches Format (muss Bearer <Schlüssel> sein) oder ein ungültiger/widerrufener Schlüssel.
400 Ungültige Eingabe Nicht interpretierbare phone_number, fehlerhafte email oder ungültige IANA-timezone. Die Nachricht nennt das betroffene Feld.
400 CAMPAIGN_NOT_FOUND Die Kampagne, auf die der Schlüssel verweist, existiert nicht mehr.
400 WALLET_NOT_ACTIVATED Der Kampagnen-Inhaber hat kein aktiviertes Guthabenkonto, daher können noch keine Anrufe eingeplant werden.
400 Lead wird gerade angerufen Aktualisierung während eines laufenden Anrufs (CALLING). Nach Gesprächsende erneut versuchen.
400 Telefonnummern-Konflikt Die gesetzte phone_number gehört bereits einem anderen Lead derselben Kampagne.
404 Nicht gefunden Die Lead-ID existiert nicht in dieser Kampagne.

Batch-Endpunkte antworten immer mit 200. Prüfe das results-Array pro Eintrag, um zu sehen, welche Zeilen erfolgreich waren.


Der Pre-Call-Webhook läuft, bevor das Gespräch startet: SalesFrank ruft deine URL auf, du antwortest mit Daten zum Anrufer, und der Agent hat sie im Gespräch als Variablen parat. Typischer Fall bei eingehenden Anrufen: Anhand der Rufnummer ziehst du den Kunden aus dem CRM und begrüßt ihn direkt mit Namen.

Anlegen kannst du ihn im Tab Call-to-Action unter Aktionen vor dem Anruf → Webhook.

Dialog für den Webhook vor dem Anruf

Methode, Ziel-URL, optionale Parameter und Header - und der Test-Button, bevor du speicherst.

Antworte mit einem JSON-Objekt. Diese Schlüssel füllen die eingebauten Felder des Agenten und des Kontakts:

Anrede · Vorname · Nachname · Firma · email · telefon

Groß- und Kleinschreibung ist dabei egal (Anrede, anrede und ANREDE wirken identisch).

Jeder andere Schlüssel wird zur Kampagnen-Variable: Er wird am Kontakt gespeichert, steht im Prompt als snake_case-Platzhalter zur Verfügung und wird dem Agenten im Gespräch übergeben.

{
"Anrede": "Herr",
"Vorname": "Linus",
"Nachname": "Meister",
"strasse": "Weidenweg 29",
"letzter_auftrag": "Wärmepumpe, März 2026"
}

Antwortet dein System mit vielen Feldern, willst du selten alle als Variablen anlegen - sonst entstehen unnötige Spalten. Dafür gibt es eine Hülle: Nur was unter variables steht, wird übernommen. Zusätzlich kannst du mit extraction_variables direkt festlegen, was die KI nach dem Anruf aus dem Gespräch extrahieren soll.

{
"variables": { "vorname": "Linus", "strasse": "Weidenweg 29" },
"extraction_variables": [
{ "name": "interesse", "type": "yes_no", "description": "" }
]
}

Mehr zu den extrahierten Feldern unter Analytics.


HTTP-Aktion mit Methode, URL und Variablen

Die HTTP-Aktion in der Plattform: Methode, Ziel-URL, verfügbare Variablen und die Bedingung, wann sie feuert.

Der umgekehrte Weg: Sobald ein Anruf endet, kann SalesFrank das Ergebnis automatisch an dein System senden - als Post-Call-Aktion. Damit schließt sich der Kreis: Du spielst Leads über die API ein, wir rufen an, und das Ergebnis (Ausgang, Zusammenfassung, Transkript) fließt in beliebigem Format zurück in dein CRM, deinen Webhook oder deine Automatisierung.

So richtest du es ein:

  1. Aktion anlegen: In der Kampagne zu Call-to-Action gehen und eine HTTP-Aktion anlegen.
  2. Ziel festlegen: Methode wählen (POST, PUT, …), die Webhook-URL deines Systems eintragen und bei Bedarf eigene Header ergänzen (z.B. ein Auth-Header für dein System).
  3. Nutzdaten wählen: Body leer lassen für die Standard-Payload - oder einen eigenen JSON-Body mit {{platzhaltern}} angeben, exakt in der Form, die dein System erwartet.
  4. Optional: Bedingung - die Aktion feuert nur bei bestimmten Ausgängen (z.B. nur wenn ein Termin gebucht wurde).

Ohne eigenen Body sendet SalesFrank diese Struktur:

{
"event": "call.completed",
"call": {
"id": "{{call.id}}",
"duration": {{call.duration}},
"outcome": "{{call.outcome}}",
"reach": "{{call.reach}}",
"result": "{{call.result}}",
"summary": "{{call.summary}}"
},
"lead": {
"name": "{{lead.name}}",
"phone": "{{lead.phone}}",
"email": "{{lead.email}}",
"company": "{{lead.company}}"
},
"campaign": { "id": "{{campaign.id}}", "name": "{{campaign.name}}" }
}

Diese Platzhalter kannst du in der URL, den Headern und im Body verwenden - sie werden vor dem Senden durch die Werte des Anrufs ersetzt.

Variable Bedeutung
{{call.id}} Eindeutige ID des Anrufs.
{{call.duration}} Gesprächsdauer in Sekunden (Zahl).
{{call.outcome}} Zusammengefasster Ausgang des Anrufs.
{{call.reach}} Erreichbarkeit (Mensch erreicht, Mailbox, nicht erreicht).
{{call.result}} Ergebnis-Klassifizierung des Gesprächs.
{{call.summary}} KI-Zusammenfassung des Gesprächs.
{{call.transcript}} Vollständiges Transkript des Gesprächs.
{{lead.name}} Name des Leads.
{{lead.phone}} Telefonnummer des Leads.
{{lead.email}} E-Mail-Adresse des Leads.
{{lead.company}} Firma des Leads.
{{campaign.id}} ID der Kampagne.
{{campaign.name}} Name der Kampagne.

Dein System erwartet eine bestimmte Struktur? Dann gib einfach den passenden JSON-Body an. Beispiel für ein CRM mit flachen Feldern:

{
"contact_phone": "{{lead.phone}}",
"call_result": "{{call.result}}",
"notes": "{{call.summary}}",
"full_transcript": "{{call.transcript}}"
}

Über den Test-Button im Aktions-Dialog testest du den Webhook mit einer Beispiel-Payload, bevor du ihn scharf schaltest.