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.
Wann ein Webhook greift
Abschnitt betitelt „Wann ein Webhook greift“| 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 |
Lead-API
Abschnitt betitelt „Lead-API“Ü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.
Authentifizierung
Abschnitt betitelt „Authentifizierung“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.
Authorization: Bearer sf_live_ihr_api_schluesselSo kommst du an den Schlüssel:
- Im Dashboard zu Kampagne → Leads → Leads importieren → API-Integration gehen.
- Schlüssel generieren - er sieht aus wie
sf_live_…. - 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.
Schnellstart
Abschnitt betitelt „Schnellstart“Sende deinen ersten Lead und löse einen Anruf aus:
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."}Lead anlegen
Abschnitt betitelt „Lead anlegen“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()Leads im Batch anlegen
Abschnitt betitelt „Leads im Batch anlegen“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.
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 } ]}Leads auflisten
Abschnitt betitelt „Leads auflisten“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. |
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 }}Einzelnen Lead abrufen
Abschnitt betitelt „Einzelnen Lead abrufen“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.
curl https://api.salesfrank.ai/api/v1/leads/9c04a7e2-1f3b-4d5a-8c6e-2b1a0f9e8d7c \ -H "Authorization: Bearer sf_live_ihr_api_schluessel"Lead aktualisieren
Abschnitt betitelt „Lead aktualisieren“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.
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" } }'Batch-Aktualisierung
Abschnitt betitelt „Batch-Aktualisierung“PATCH /api/v1/leads/batch
Aktualisiert bis zu 100 Leads in einer Anfrage. Jede Aktualisierung wird unabhängig angewendet, Einzelfehler stehen in results.
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" } ] }'Lead löschen
Abschnitt betitelt „Lead löschen“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.
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.
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-…"] }'Das Lead-Objekt
Abschnitt betitelt „Das Lead-Objekt“| 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. |
Lead-Status-Werte
Abschnitt betitelt „Lead-Status-Werte“| 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). |
Benutzerdefinierte Variablen
Abschnitt betitelt „Benutzerdefinierte Variablen“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_casenormalisiert. 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.
Duplikat-Behandlung
Abschnitt betitelt „Duplikat-Behandlung“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.
Kontext vor dem Anruf laden
Abschnitt betitelt „Kontext vor dem Anruf laden“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.

Was deine Antwort zurückgeben darf
Abschnitt betitelt „Was deine Antwort zurückgeben darf“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"}Optionale Hülle: kuratieren und extrahieren
Abschnitt betitelt „Optionale Hülle: kuratieren und extrahieren“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.
Ergebnisse per Webhook zurücksenden
Abschnitt betitelt „Ergebnisse per Webhook zurücksenden“
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:
- Aktion anlegen: In der Kampagne zu Call-to-Action gehen und eine HTTP-Aktion anlegen.
- 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). - 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. - Optional: Bedingung - die Aktion feuert nur bei bestimmten Ausgängen (z.B. nur wenn ein Termin gebucht wurde).
Standard-Payload
Abschnitt betitelt „Standard-Payload“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}}" }}Verfügbare Variablen
Abschnitt betitelt „Verfügbare Variablen“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. |
Eigener Body - beliebiges Format
Abschnitt betitelt „Eigener Body - beliebiges Format“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.

