Überblick
Die Schnittstelle gehört zum Modul „Anbindungen“. Es ist ab Pro enthalten und lässt sich ab Starter einzeln dazubuchen.
- Adresse
https://app.salesfy.de/<konto>/api/v1–<konto>ist deine Kontonummer, zum Beispiel4839-2017. Sie steht in der Adresszeile, wenn du angemeldet bist. Die fertige Adresse zeigt das CRM unter „Anbindungen“ → „Webhooks & API“. - FormatJSON in UTF-8, nur über HTTPS. Beim Anlegen schickst du JSON mit dem Kopf
Content-Type: application/json. - ZeitenISO 8601 mit Zeitzone, zum Beispiel
2026-10-05T09:00:00+02:00. - BeträgeAls Text mit zwei Nachkommastellen, zum Beispiel
"4200.00". Der Wert eines Vorgangs ist netto. - UmfangKontakte, Firmen und Vorgänge abrufen und anlegen, Aufgaben und Notizen anlegen, Webhooks an- und abmelden. Ändern und Löschen von Datensätzen gibt es über die API nicht.
- Grenze120 Aufrufe je Minute. Danach antwortet die API mit 429, bis die Minute um ist.
Schlüssel und Anmeldung
Jeder Aufruf braucht einen Schlüssel. Du erzeugst ihn selbst im CRM – am besten einen je Anbindung, dann lässt sich jeder einzeln widerrufen.
Seite öffnen
Im CRM im Menü „Anbindungen“ wählen, dort oben „Webhooks & API“.
Schlüssel erzeugen
Im Abschnitt „REST-API“ eintragen, wofür der Schlüssel ist – etwa „Zapier“ oder „Website“ – und „Schlüssel erzeugen“ wählen.
Sofort kopieren
Der Schlüssel wird nur dieses eine Mal angezeigt. Im CRM bleibt nur ein Prüfwert gespeichert, dazu die letzten vier Zeichen, damit du ihn in der Liste wiedererkennst.
Den Schlüssel schickst du im Kopf X-Schluessel mit – oder als Authorization: Bearer …, wenn dein Werkzeug das so vorsieht. In der Adresse (?schluessel=…) nimmt Salesfy ihn nicht an: Dort stünde er in jedem Zugriffsprotokoll.
curl https://app.salesfy.de/<konto>/api/v1/pruefen \
-H "X-Schluessel: DEIN_SCHLUESSEL"
{
"ok": true,
"meldung": "Schlüssel gilt.",
"produkt": "Salesfy",
"version": 1
}
Gut zu wissen: Ein Schlüssel darf alles, was die API kann. Gib ihn nicht in den Code einer Webseite, die im Browser läuft – dort kann ihn jeder lesen. Für Formulare auf deiner Webseite gibt es die Formular-Schnittstelle ohne Schlüssel.
Antworten und Fehler
Jede Antwort ist JSON und beginnt mit ok – true oder false. Dazu kommt meist eine meldung in einem Satz. Der Status sagt, was geschehen ist:
- 200
- Abruf gelungen – oder beim Anlegen: Den Datensatz gab es schon (
"vorhanden": true). - 201
- Angelegt.
- 401
- Kein Schlüssel mitgeschickt, oder der Schlüssel stimmt nicht oder ist widerrufen.
- 404
- Datensatz nicht gefunden – oder das Modul „Anbindungen“ ist in diesem Konto ausgeschaltet.
- 422
- Die Angaben passen nicht.
meldungsagt in einem Satz, was fehlt,feldernennt es je Feld. - 429
- Mehr als 120 Aufrufe in einer Minute. Der Kopf
Retry-Afternennt die Sekunden bis zum nächsten Versuch.
{
"ok": false,
"meldung": "Da stimmt etwas nicht: Name fehlt.",
"felder": {
"name": ["Name fehlt."]
}
}
Daten abrufen
Alle Pfade hängen an der Adresse https://app.salesfy.de/<konto>/api/v1. Listen kommen mit den neuesten Einträgen zuerst, höchstens 100 je Seite.
GET/pruefen
Prüft den Schlüssel. Gut als erster Aufruf und als Verbindungstest.
GET/kontakte
Kontakte, die neuesten zuerst.
- suche
- Teil von Vorname, Nachname oder E-Mail-Adresse
- genaue E-Mail-Adresse, Groß- und Kleinschreibung egal
- seit
- nur, was seit diesem Zeitpunkt geändert wurde – etwa
2026-10-01oder2026-10-01T08:00:00+02:00 - limit
- Einträge je Seite, 1 bis 100, ohne Angabe 25
- seite
- welche Seite, ohne Angabe 1
GET/kontakte/{id}
Ein Kontakt.
GET/firmen
Firmen, die neuesten zuerst.
- suche
- Teil des Namens
- seit, limit, seite
- wie bei den Kontakten
GET/firmen/{id}
Eine Firma.
GET/vorgaenge
Vorgänge, die neuesten zuerst.
- firma_id
- nur Vorgänge dieser Firma
- kontakt_id
- nur Vorgänge dieses Kontakts
- stufe
- nur diese Stufe – der Schlüssel aus
/stufen - seit, limit, seite
- wie bei den Kontakten
GET/vorgaenge/{id}
Ein Vorgang.
GET/stufen
Die Stufen von Kontakten, Firmen und Vorgängen: der feste Schlüssel und der Name aus deinem Konto, bei Vorgängen je Pipeline.
GET/ereignisse
Die Ereignisse, die ein Webhook melden kann.
GET/ereignisse/{ereignis}/beispiel
Eine Beispiel-Nachricht zu diesem Ereignis, gefüllt mit dem jüngsten passenden Datensatz aus deinem Konto.
curl "https://app.salesfy.de/<konto>/api/v1/kontakte?suche=Pohl" \
-H "X-Schluessel: DEIN_SCHLUESSEL"
{
"ok": true,
"anzahl": 1,
"seite": 1,
"kontakte": [
{
"id": 17,
"anrede": "Herr",
"vorname": "Daniel",
"nachname": "Pohl",
"email": "daniel.pohl@post.example",
"telefon": "0176 04069032",
"strasse": "Ahornweg 14",
"plz": "85716",
"ort": "Unterschleißheim",
"stufe": "vermittelt",
"quelle": "Web-Formular",
"betreuer_id": 1,
"created_at": "2026-09-10T10:15:00+02:00",
"updated_at": "2026-10-05T19:19:42+02:00",
"name": "Daniel Pohl",
"link": "https://app.salesfy.de/<konto>/kontakte/17",
"newsletter": false
}
]
}
Daten anlegen
Angelegt wird mit POST und JSON. Was über die API hereinkommt, steht danach wie jeder andere Eintrag im CRM – mit einem Vermerk im Verlauf der Akte, dass es über die API kam.
POST/kontakte
Legt einen Kontakt an. Gibt es schon einen mit derselben E-Mail-Adresse oder Telefonnummer, entsteht kein zweiter: Die Antwort trägt "vorhanden": true und den vorhandenen Kontakt, eine mitgeschickte Notiz wird dort ergänzt.
- nachname Pflicht, wenn weder E-Mail noch Telefon angegeben ist
- vorname, email, telefon
- strasse, plz, ort
- quelle
- woher der Kontakt kommt, frei wählbar; ohne Angabe „API“
- stufe
- Schlüssel aus
/stufen - notiz
- erscheint als Notiz im Verlauf der Akte
- newsletter
truevermerkt die Einwilligung,falsenimmt sie zurück
POST/firmen
Legt eine Firma an. Gibt es schon eine mit diesem Namen – Groß- und Kleinschreibung und Satzzeichen spielen keine Rolle –, kommt sie mit "vorhanden": true zurück.
- name Pflicht
- email, telefon, website
- strasse, plz, ort
- art
interessent,kundeoderverloren- quelle, notiz
- wie beim Kontakt
POST/vorgaenge
Legt einen Vorgang an – also das, was in deinem Konto Auftrag, Projekt, Mandat oder Buchung heißt.
- titel Pflicht
- höchstens 80 Zeichen
- firma_id / kontakt_id eins von beiden
- zu wem der Vorgang gehört
- stufe
- Schlüssel aus
/stufen; ohne Angabe die erste Stufe - wert
- Betrag netto als Zahl, etwa
4200oder4200.50 - notiz
- erscheint als Notiz im Verlauf
POST/aufgaben
Legt eine Aufgabe an, auf Wunsch an einer Akte.
- titel Pflicht
- höchstens 200 Zeichen
- beschreibung
- faellig_am
- Datum, etwa
2026-10-12; ohne Angabe heute - prioritaet
hoch,mitteloderniedrig; ohne Angabe mittel- firma_id, kontakt_id, vorgang_id
- hängt die Aufgabe an diese Akte
POST/notizen
Schreibt eine Notiz in den Verlauf einer Akte.
- text Pflicht
- höchstens 5.000 Zeichen
- firma_id / kontakt_id / vorgang_id eins davon
- in welche Akte
curl -X POST https://app.salesfy.de/<konto>/api/v1/kontakte \
-H "X-Schluessel: DEIN_SCHLUESSEL" \
-H "Content-Type: application/json" \
-d '{"vorname": "Daniel", "nachname": "Pohl",
"email": "daniel.pohl@post.example",
"quelle": "Website", "notiz": "Bittet um Rückruf am Vormittag"}'
Die Antwort hat den Status 201 und enthält den neuen Datensatz – beim Kontakt unter kontakt, bei der Firma unter firma und so weiter.
Die Felder
So sehen die Datensätze aus – in den Antworten der API und in den Nachrichten der Webhooks. Leere Felder kommen als null.
Kontakt
- id
- Nummer des Kontakts
- anrede, vorname, nachname, name
nameist Vor- und Nachname zusammen- email, telefon
- strasse, plz, ort
- stufe
- Schlüssel der Stufe, siehe
/stufen - quelle
- woher der Kontakt kam
- betreuer_id
- Nummer des zuständigen Nutzers
- newsletter
true, wenn eine Einwilligung vorliegt und Werbung nicht gesperrt ist (nur in der REST-API)- created_at, updated_at
- angelegt und zuletzt geändert
- link
- Adresse der Akte im CRM
Firma
- id, name
- art
interessent,kundeoderverloren- stufe
- Schlüssel der Stufe, siehe
/stufen - typ
- Branche der Firma, freier Text
- email, telefon, website
- strasse, plz, ort, ust_id
- quelle, betreuer_id
- wie beim Kontakt
- created_at, updated_at, link
- wie beim Kontakt
Vorgang
- id, titel
- stufe, stufe_name
- Schlüssel der Stufe und ihr Name in der Pipeline des Vorgangs
- stufe_vorher
- nur im Ereignis
vorgang.stufe: die Stufe davor - firma_id, kontakt_id
- zu wem der Vorgang gehört
- pipeline_id
- Nummer der Pipeline, siehe
/stufen - wert
- Betrag netto
- wahrscheinlichkeit
- Abschlusswahrscheinlichkeit in Prozent, wenn sie am Vorgang eingetragen ist
- abschluss_erwartet
- erwartetes Abschlussdatum
- betreuer_id, created_at, updated_at, link
- wie beim Kontakt
Rechnung (nur Webhooks)
- id, nummer, bezeichnung
- status
- Stand der Rechnung, etwa
bezahlt - datum, faellig_am
- netto, ust_satz, brutto
- bezahlt_am, bezahlt_betrag
- firma_id, kontakt_id, vorgang_id
- zu wem die Rechnung gehört
- kunde
- Name des Kunden
- created_at, updated_at, link
Termin (nur Webhooks)
- id, titel, art
- beginn, ende, ort
- status
- etwa
geplant - firma_id, kontakt_id, vorgang_id
- zu wem der Termin gehört
- created_at, link
Eingang über ein Web-Formular (nur Webhooks)
- id, formular_id, formular
- Nummer des Eingangs, Nummer und Name des Formulars
- kontakt_id, firma_id, vorgang_id
- was aus dem Eingang angelegt oder wiedergefunden wurde
- werte
- die ausgefüllten Felder
- kanal
- ob der Eingang über die Seite oder die JSON-Schnittstelle kam
- created_at, link
Webhooks
Ein Webhook dreht die Richtung um: Salesfy ruft deine Adresse auf, sobald etwas passiert. Du musst nicht regelmäßig nachfragen.
- Im CRMUnter „Anbindungen“ → „Webhooks & API“ ein Ziel anlegen: Name, Art (JSON für eigene Programme – oder Slack, Microsoft Teams, Google Chat für eine Nachricht im Kanal), Adresse und die Ereignisse. Mit „Probe senden“ schickst du eine Test-Nachricht.
- Über die API
POST /webhooksmiturlundereignis(oder einer Listeereignisse,"*"steht für alle). Die Antwort nenntidundgeheimnis.GET /webhookslistet die Ziele,DELETE /webhooks/{id}meldet eines ab. So melden sich Zapier, Make und n8n selbst an.
curl -X POST https://app.salesfy.de/<konto>/api/v1/webhooks \
-H "X-Schluessel: DEIN_SCHLUESSEL" \
-H "Content-Type: application/json" \
-d '{"url": "https://beispiel.example/salesfy",
"ereignisse": ["vorgang.stufe", "rechnung.bezahlt"]}'
{
"ok": true,
"meldung": "Webhook angemeldet.",
"id": 4,
"geheimnis": "whsf_…",
"hinweis": "Signatur im Kopf X-Salesfy-Signatur: t=<Zeit>,v1=HMAC-SHA256(geheimnis, \"<t>.<Rumpf>\")."
}
Das Geheimnis siehst du nur dieses eine Mal. Du brauchst es, um die Signatur zu prüfen.
Die Ereignisse
- kontakt.angelegt
- Ein Kontakt wurde angelegt.
- kontakt.geaendert
- Ein Kontakt wurde geändert.
- firma.angelegt
- Eine Firma wurde angelegt.
- firma.geaendert
- Eine Firma wurde geändert.
- vorgang.angelegt
- Ein Vorgang wurde angelegt.
- vorgang.geaendert
- Ein Vorgang wurde geändert.
- vorgang.stufe
- Ein Vorgang ist in eine andere Stufe gewechselt.
- rechnung.erstellt
- Eine Rechnung wurde erstellt.
- rechnung.bezahlt
- Eine Rechnung ist bezahlt.
- termin.angelegt
- Ein Termin wurde angelegt.
- formular.eingang
- Über ein Web-Formular ist eine Anfrage eingegangen.
So sieht eine Nachricht aus
Salesfy schickt ein POST mit JSON. Unter daten steht der Datensatz mit den Feldern von oben.
POST /salesfy HTTP/1.1
Content-Type: application/json
User-Agent: Salesfy-Webhooks/1.0
X-Salesfy-Ereignis: vorgang.stufe
X-Salesfy-Zustellung: 6f1c2f0e-8f5b-4f0e-9a51-3c1f7b0f2a11
X-Salesfy-Versuch: 1
X-Salesfy-Signatur: t=1791183600,v1=5d1f…
{
"id": "6f1c2f0e-8f5b-4f0e-9a51-3c1f7b0f2a11",
"ereignis": "vorgang.stufe",
"zeit": "2026-10-05T09:00:00+02:00",
"daten": {
"id": 12,
"titel": "Heizungstausch / Wärmepumpe",
"stufe": "zusage",
"firma_id": null,
"kontakt_id": 4,
"pipeline_id": 1,
"wert": "29800.00",
"wahrscheinlichkeit": 80,
"abschluss_erwartet": "2026-11-14T00:00:00+01:00",
"betreuer_id": 1,
"created_at": "2026-09-17T11:00:00+02:00",
"updated_at": "2026-10-05T09:00:00+02:00",
"stufe_name": "Mündliche Zusage",
"stufe_vorher": "gespraech",
"link": "https://app.salesfy.de/<konto>/vorgaenge/12"
}
}
Zustellung und Wiederholung
- ErfolgJede Antwort mit einem Status von 200 bis 299 gilt als zugestellt. Dein Server hat dafür zehn Sekunden Zeit – antworte also sofort und arbeite danach.
- WiederholungSchlägt die Zustellung fehl, versucht Salesfy es nach 1 Minute, 5 Minuten, 30 Minuten, 2 Stunden und 12 Stunden erneut – insgesamt sechs Versuche. Danach bekommst du im CRM eine Mitteilung.
- Doppelte erkennenEine Wiederholung trägt dieselbe
id(und denselben KopfX-Salesfy-Zustellung). Merk dir die Kennungen, die du schon verarbeitet hast. - Änderungen in FolgeWird derselbe Datensatz mehrmals kurz hintereinander geändert, bekommst du eine Nachricht mit dem letzten Stand statt einer je Änderung.
- AbmeldenAntwortet dein Server mit 410, meldet Salesfy das Ziel ab. Dasselbe geschieht nach 50 Fehlern in Folge.
- AdresseNur
https://und nur öffentlich erreichbare Server. Weiterleitungen folgt Salesfy nicht. - NachsehenDie letzten 40 Zustellungen stehen im CRM unter „Webhooks & API“ – mit Status, Antwort und einem Knopf „Erneut“.
Signatur prüfen
Jede Nachricht trägt den Kopf X-Salesfy-Signatur in der Form t=<Zeit>,v1=<Signatur>. Die Zeit ist ein Unix-Zeitstempel in Sekunden. Die Signatur ist HMAC-SHA256 über die Zeit, einen Punkt und den unveränderten Rumpf der Nachricht – mit dem Geheimnis des Ziels als Schlüssel, in Hex geschrieben.
Prüfe sie, bevor du der Nachricht glaubst – und lehne Nachrichten ab, deren Zeit mehr als fünf Minuten zurückliegt. Wichtig ist der rohe Rumpf: Wer das JSON erst einliest und neu schreibt, bekommt eine andere Signatur.
PHP
function salesfy_signatur_gilt(string $rumpf, string $kopf, string $geheimnis): bool
{
$da = preg_match('/t=(\d+)/', $kopf, $t)
&& preg_match('/v1=([0-9a-f]{64})/', $kopf, $v);
if (! $da || abs(time() - (int) $t[1]) > 300) {
return false; // unvollständig oder älter als fünf Minuten
}
$soll = hash_hmac('sha256', $t[1] . '.' . $rumpf, $geheimnis);
return hash_equals($soll, $v[1]);
}
$rumpf = file_get_contents('php://input'); // genau so, wie er ankam
$kopf = $_SERVER['HTTP_X_SALESFY_SIGNATUR'] ?? '';
if (! salesfy_signatur_gilt($rumpf, $kopf, getenv('SALESFY_GEHEIMNIS'))) {
http_response_code(401);
exit;
}
$nachricht = json_decode($rumpf, true);
http_response_code(204);
Node.js
const crypto = require('crypto');
function salesfySignaturGilt(rumpf, kopf, geheimnis) {
const t = /t=(\d+)/.exec(kopf || '');
const v = /v1=([0-9a-f]{64})/.exec(kopf || '');
if (!t || !v) return false;
if (Math.abs(Date.now() / 1000 - Number(t[1])) > 300) return false; // zu alt
const soll = crypto.createHmac('sha256', geheimnis)
.update(t[1] + '.' + rumpf).digest('hex');
return crypto.timingSafeEqual(Buffer.from(soll), Buffer.from(v[1]));
}
// Express: den Rumpf roh lesen, nicht als fertiges Objekt
app.post('/salesfy', express.raw({ type: 'application/json' }), (req, res) => {
const rumpf = req.body.toString('utf8');
const kopf = req.get('X-Salesfy-Signatur');
if (!salesfySignaturGilt(rumpf, kopf, process.env.SALESFY_GEHEIMNIS)) {
return res.sendStatus(401);
}
const nachricht = JSON.parse(rumpf);
res.sendStatus(204);
});
Web-Formular per JSON
Für das Kontaktformular auf deiner eigenen Webseite brauchst du keinen Schlüssel. Jedes Web-Formular im CRM hat eine eigene Adresse, an die du die Eingaben als JSON schickst. Was ankommt, wird wie beim eingebetteten Formular zu Kontakt, Firma und Vorgang.
curl -X POST https://app.salesfy.de/<konto>/api/formular/<kennung> \
-H "Content-Type: application/json" \
-d '{"vorname": "Daniel", "name": "Pohl", "email": "daniel.pohl@post.example",
"telefon": "0176 04069032", "nachricht": "Bitte um Rückruf",
"einwilligung": true}'
- AdresseSteht im CRM beim Formular unter „Eigene Webseite: JSON-Schnittstelle“. Ein Schlüssel ist nicht nötig; die Kennung in der Adresse gehört zu genau diesem Formular.
- Felder
vorname,name,firma,email,telefon,nachricht– je nachdem, was du im Formular eingeschaltet und zur Pflicht gemacht hast. Eigene Felder kommen untereigen. - Einwilligung
"einwilligung": trueist Pflicht. Salesfy speichert dazu den Wortlaut des Hinweises und den Zeitpunkt. - SpamschutzDas Feld
webseiteist eine Falle für Programme: leer lassen. Dazu kommen eine Grenze für Links im Text und eine Drossel je Absender. - Antworten201 mit
"ok": true, dem Dankestext und – falls eingestellt – der Adresse zum Weiterleiten. 422 mitfehlerje Feld, 429 bei zu vielen Eingängen in kurzer Zeit, 404, wenn es das Formular nicht (mehr) gibt. - Aus dem BrowserDer Aufruf ist von fremden Seiten aus erlaubt (CORS), du kannst das Formular also mit eigenem JavaScript abschicken.
Die Web-Formulare sind ein eigenes Modul, enthalten ab Business. Einbetten ohne Programmieren – als Link, im Rahmen oder mit einer Zeile Skript – beschreibt die Seite Kundenportal und Online-Buchung.
Ohne Programmieren
Zapier, Make und n8n sprechen mit Salesfy über genau diese API und diese Webhooks. Du trägst dort die Adresse und den Schlüssel ein und baust den Ablauf mit der Maus zusammen. Für Dienste mit eigener Schnittstelle gibt es im CRM außerdem den Baukasten „Eigene Anbindungen“: Daten abrufen, Daten empfangen, Daten senden – mit Zuordnung der Felder und Vorschau.
Häufige Fragen
Brauche ich für die API ein bestimmtes Paket?
Die REST-API und die Webhooks gehören zum Modul „Anbindungen“. Es ist ab Pro enthalten und lässt sich ab Starter für 4,90 € im Monat einzeln dazubuchen. Die JSON-Schnittstelle der Web-Formulare gehört zum Modul „Web-Formulare“ (ab Business, ebenfalls zubuchbar).
Kann ich Datensätze über die API ändern oder löschen?
Nein. Die API legt an und liest. Geändert und gelöscht wird im CRM. Beim Anlegen erkennt die API vorhandene Kontakte und Firmen und legt sie nicht doppelt an.
Wie hole ich alle Kontakte ab, wenn es mehr als 100 sind?
Seite für Seite: /kontakte?limit=100&seite=1, dann seite=2 und so weiter, bis eine Seite weniger als 100 Einträge hat. Für den laufenden Abgleich danach reicht seit mit dem Zeitpunkt des letzten Abrufs.
Warum heißen manche Stufen in der API anders als bei mir im CRM?
Jede Stufe hat einen festen Schlüssel und einen Namen, den du selbst festlegst. Die API arbeitet mit dem Schlüssel, damit eine Anbindung weiterläuft, wenn du eine Stufe umbenennst. Welcher Schlüssel zu welchem Namen gehört, sagt GET /stufen; Vorgänge bringen den Namen zusätzlich als stufe_name mit.
Was passiert, wenn mein Server gerade nicht erreichbar ist?
Salesfy versucht die Zustellung bis zu sechsmal über gut 14 Stunden. Scheitert auch der letzte Versuch, steht die Zustellung im CRM als fehlgeschlagen und lässt sich dort von Hand erneut senden.
Gibt es eine Testumgebung?
Eine eigene Testumgebung gibt es nicht. Zum Ausprobieren eignet sich ein zweites Konto: Im kostenlosen Testzeitraum von 10 Tagen hast du den vollen Umfang des gewählten Pakets, mit Pro also auch die Anbindungen.
Probier es mit deinen eigenen Kunden aus.
10 Tage kostenlos, ohne Zahlungsdaten. Danach buchst du ein Paket oder arbeitest im Free-Paket weiter – es wird nichts von selbst zum Abo.