Zum Inhalt springen
  1. Start
  2. Hilfe
  3. API und Webhooks

API und Webhooks

Für eigene Programme und für Dienste wie Zapier, Make oder n8n: Über die REST-API legst du Kontakte, Firmen, Vorgänge, Aufgaben und Notizen an und rufst sie ab. Über Webhooks meldet Salesfy, wenn im CRM etwas passiert.

Überblick

Die Schnittstelle gehört zum Modul „Anbindungen“. Es ist ab Pro enthalten und lässt sich ab Starter einzeln dazubuchen.

  • Adressehttps://app.salesfy.de/<konto>/api/v1 – <konto> ist deine Kontonummer, zum Beispiel 4839-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.

  1. Seite öffnen

    Im CRM im Menü „Anbindungen“ wählen, dort oben „Webhooks & API“.

  2. Schlüssel erzeugen

    Im Abschnitt „REST-API“ eintragen, wofür der Schlüssel ist – etwa „Zapier“ oder „Website“ – und „Schlüssel erzeugen“ wählen.

  3. 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.

Aufruf
curl https://app.salesfy.de/<konto>/api/v1/pruefen \
  -H "X-Schluessel: DEIN_SCHLUESSEL"
Antwort
{
  "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. meldung sagt in einem Satz, was fehlt, felder nennt es je Feld.
429
Mehr als 120 Aufrufe in einer Minute. Der Kopf Retry-After nennt die Sekunden bis zum nächsten Versuch.
Antwort mit Status 422
{
  "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
email
genaue E-Mail-Adresse, Groß- und Kleinschreibung egal
seit
nur, was seit diesem Zeitpunkt geändert wurde – etwa 2026-10-01 oder 2026-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.

Aufruf
curl "https://app.salesfy.de/<konto>/api/v1/kontakte?suche=Pohl" \
  -H "X-Schluessel: DEIN_SCHLUESSEL"
Antwort
{
  "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
true vermerkt die Einwilligung, false nimmt 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, kunde oder verloren
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 4200 oder 4200.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, mittel oder niedrig; 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
Aufruf
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
name ist 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, kunde oder verloren
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 APIPOST /webhooks mit url und ereignis (oder einer Liste ereignisse, "*" steht für alle). Die Antwort nennt id und geheimnis. GET /webhooks listet die Ziele, DELETE /webhooks/{id} meldet eines ab. So melden sich Zapier, Make und n8n selbst an.
Aufruf
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"]}'
Antwort
{
  "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.

Nachricht an deinen Server
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 Kopf X-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.

Aufruf
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.
  • Feldervorname, name, firma, email, telefon, nachricht – je nachdem, was du im Formular eingeschaltet und zur Pflicht gemacht hast. Eigene Felder kommen unter eigen.
  • Einwilligung"einwilligung": true ist Pflicht. Salesfy speichert dazu den Wortlaut des Hinweises und den Zeitpunkt.
  • SpamschutzDas Feld webseite ist 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 mit fehler je 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.

Anbindungen im Überblick

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.