Set2Sell
Set2Sell
For integration partners · Last updated 11. September 2026

Integration handbook for partners

Dieses Handbuch richtet sich an Produkt- und Entwicklerteams, die ihr Werkzeug mit Set2Sell Cockpit verbinden wollen — etwa Funnel- und Formular-Tools, Werbe- und Tracking-Plattformen, Telefonie, Buchhaltung oder KI-Assistenten. Es beschreibt ausschließlich Schnittstellen, die heute live sind. Jede Angabe wurde gegen den aktuellen Programmcode und per Live-Aufruf gegen die Produktionsumgebung geprüft.


1. Auf einen Blick

Set2Sell Cockpit ist ein CRM für Vertriebsteams: Leads laufen als Deals durch Pipelines mit Phasen, dazu kommen Termine, Anrufe, Aufgaben, Funnels und Automationen. Für die Anbindung von außen gibt es fünf Wege:

Weg Richtung Authentifizierung Wofür
REST-API v1 Partner → Cockpit (lesen + schreiben) API-Key (Bearer s2s_live_…) Deals anlegen, lesen, ändern, löschen; Pipelines, Phasen und eigene Felder auslesen; Webhook-Abos verwalten
Webhook-Abos Cockpit → Partner HMAC-Signatur pro Abo Über Ereignisse informiert werden: neuer Deal, Phasenwechsel, gewonnen/verloren, Termin gebucht, Formular abgeschickt, …
Lead-Import-Webhook Partner → Cockpit (nur anlegen/anreichern) Nutzer-ID im Header Leads aus No-Code-Tools einspielen, inklusive Dublettenerkennung und Pipeline-Auswahl per Name
Zapier-App beide API-Key Fertige Trigger und Aktionen ohne eigene Programmierung
MCP-Server KI-Client → Cockpit OAuth 2.1 (pro Nutzer) KI-Assistenten (z. B. Claude) bedienen das CRM eines Nutzers mit 67 Werkzeugen

Welcher Weg passt?

  • Du baust eine eigene Integration in deinem Produkt: REST-API v1 für das Schreiben und Lesen, Webhook-Abos für das Reagieren auf Ereignisse. Beides zusammen deckt den Normalfall vollständig ab.
  • Du willst nur Leads abliefern und hast ein No-Code-Tool ohne eigene Auth-Logik: Lead-Import-Webhook. Für neue, professionelle Integrationen empfehlen wir trotzdem POST /api/v1/deals, weil dort Rate-Limit, Fehlerformat und Berechtigungen sauberer sind.
  • Deine Kunden nutzen Zapier: Zapier-App verlinken, fertig. Make und n8n arbeiten über deren HTTP-Module direkt mit REST-API und Webhook-Abos.
  • Du baust einen KI-Agenten: MCP-Server.

2. Grundlagen

2.1 Datenmodell in acht Begriffen

Begriff Bedeutung Wichtig für Integrationen
Workspace (intern: Team) Ein Kundenkonto mit Mitgliedern, Pipelines, Deals. Alles ist workspace-getrennt. Ein API-Key gehört zu genau einem Workspace. Jede Abfrage ist automatisch auf diesen Workspace begrenzt.
Pipeline Ein Vertriebsprozess mit sortierten Phasen. Ein Workspace kann mehrere Pipelines haben. Beim Anlegen eines Deals ist pipeline_id Pflicht. Eigene Felder sind pro Pipeline definiert.
Phase (intern: Stage) Eine Spalte im Board, z. B. „Neu", „Termin vereinbart", „Gewonnen". Jede Phase hat einen mapped_status. Ein Deal wird über seine stage_id bewegt. Der Status folgt automatisch.
Deal Der zentrale Datensatz: Lead, Kontakt und Verkaufschance in einem. Trägt Kontaktdaten, Wert, Tags, Notizen, eigene Felder. Alle Lese- und Schreibwege arbeiten auf Deals.
Status Einer von sechs festen Werten: offen, termin, nachfassen, ungeeignet, gewonnen, verloren. Nie direkt setzbar. Der Status wird immer aus der Phase abgeleitet. Wer einen Deal auf „gewonnen" setzen will, verschiebt ihn in eine Phase mit mapped_status = gewonnen.
Eigene Felder (Custom Fields) Vom Kunden pro Pipeline definierte Zusatzfelder mit Typ (Text, Zahl, Auswahl, Datum, …). Werden über ihren Namen angesprochen, exakte Schreibweise. Über die REST-API nur lesbar; schreibbar über den Lead-Import-Webhook und MCP.
Termin / Terminart Gebuchte Termine (aus Buchungsseiten, Funnels oder von Hand) und deren Vorlagen. Ereignisse appointment.* in den Webhook-Abos; volle Verwaltung über MCP.
Funnel Vom Kunden gebaute Landingpage-Strecke mit Formular, Quiz oder Buchung. Ereignis funnel.submission_completed.

2.2 Domains und Serverstandort

Zweck Adresse
Anwendung und alle Schnittstellen https://cockpit.set2sell.io
REST-API https://cockpit.set2sell.io/api/v1
Lead-Import-Webhook https://cockpit.set2sell.io/api/webhooks/lead-import
MCP-Server https://cockpit.set2sell.io/api/mcp
OAuth-Autorisierungsserver (für MCP) https://clerk.set2sell.io

Die Anwendung läuft in Frankfurt am Main (Vercel-Region fra1), die Datenbank liegt bei Supabase in der Region eu-central-1 (ebenfalls Frankfurt). Ausgehende Webhook-Aufrufe kommen aus dieser Umgebung; eine feste Absender-IP-Liste gibt es nicht. Empfänger sollten deshalb die Signatur prüfen (Abschnitt 4.4), nicht die IP.

Es gibt keine separate Sandbox-Umgebung. Für Tests legt Set2Sell auf Anfrage einen eigenen Test-Workspace an (Abschnitt 9).

2.3 Sicherheitsmodell

  • Nur HTTPS. Webhook-Ziele müssen https:// sein; lokale und private Adressen werden abgelehnt.
  • API-Keys werden nur als SHA-256-Hash gespeichert und genau einmal im Klartext angezeigt. Sie können jederzeit vom Workspace-Admin widerrufen werden. Nur Workspace-Admins können Keys anlegen.
  • Webhook-Signaturen sind HMAC-SHA256 mit einem Secret pro Abo, das ebenfalls nur einmal angezeigt wird.
  • Rate-Limits gelten pro Key beziehungsweise pro Absender und werden über Antwort-Header sichtbar gemacht.
  • Mandantentrennung ist serverseitig erzwungen: Jede Abfrage der REST-API wird auf den Workspace des Keys gefiltert. Eine fremde pipeline_id oder deal_id liefert 404, nie fremde Daten.

2.4 Was welcher Weg auslöst

Im Cockpit reagieren zwei getrennte Systeme auf Änderungen: Webhook-Abos (nach außen, Abschnitt 4) und die Automationen des Kunden (im Produkt: E-Mail-Strecken, Aufgaben, Phasenwechsel). Nicht jeder Entstehungsweg eines Deals stößt beide an. Diese Matrix zeigt den heutigen Stand; plane deine Integration danach.

Wenn ein Deal entsteht:

Entstehungsweg Webhook-Abo deal.created Automation „Deal: Neu erstellt"
Von Hand in der Oberfläche
POST /api/v1/deals (auch Zapier-Aktion „Deal anlegen")
Lead-Import-Webhook (Abschnitt 5)
Funnel-Formular – (stattdessen funnel.submission_completed) – (stattdessen Automation „Funnel: Lead bewertet")
Buchungsseite / Funnel-Buchung – (stattdessen appointment.created) – (stattdessen Automation „Termin: Gebucht")
MCP create_deal / import_leads

Wenn ein Deal die Phase wechselt:

Weg des Phasenwechsels Webhook-Abo deal.stage_changed (+ deal.won / deal.lost) Automation „Deal: Stage gewechselt" / „Deal: Status geändert"
Oberfläche (Board, Deal-Detail, Abschluss)
PATCH /api/v1/deals/{id} (auch Zapier-Aktion „Deal aktualisieren")
Lead-Import-Webhook mit mode: "update"
MCP move_deal

Praktische Folge: Wenn dein Produkt Deals anlegt oder verschiebt und der Kunde erwartet, dass seine Automationen darauf reagieren, ist das heute nur über den Lead-Import-Webhook (beim Anlegen) gegeben. Sprich uns an, wenn du diesen Fall hast — die Lücke ist bekannt und auf der Liste. Für deine eigenen Reaktionen auf Ereignisse sind die Webhook-Abos unabhängig davon vollständig.


3. REST-API v1

3.1 API-Key anlegen (Kundenseite)

Der Kunde erzeugt den Key selbst:

  1. Im Cockpit Einstellungen → Workspace → Tab „API" öffnen (nur als Workspace-Admin sichtbar).
  2. Neuen Key anlegen, einen Namen vergeben (z. B. den Namen deines Produkts).
  3. Der Key s2s_live_… wird genau einmal angezeigt. Der Kunde kopiert ihn in deine Integration.

In der Übersicht sieht der Kunde später nur die ersten Zeichen (s2s_live_ab12cd…), das Anlegedatum und die letzte Nutzung, und kann den Key widerrufen. Ein widerrufener Key verhält sich nach außen wie ein unbekannter Key (401).

Deals, die über einen Key angelegt werden, gehören dem Nutzer, der den Key erstellt hat.

3.2 Authentifizierung, Rate-Limit, Fehlerformat

Header bei jeder Anfrage:

Authorization: Bearer s2s_live_…
Content-Type: application/json

Rate-Limit: 120 Anfragen pro Minute und Key. Jede erfolgreiche Antwort (200, 201, 204) und jede 429 trägt diese Header:

Header Bedeutung
X-RateLimit-Limit Anfragen pro Minutenfenster (120)
X-RateLimit-Remaining im laufenden Fenster noch frei
X-RateLimit-Reset Unix-Sekunden, wann das Fenster neu beginnt
Retry-After nur bei 429: Sekunden bis zum nächsten Versuch

Fehlerantworten aus der Route selbst (404, 422, 500) tragen die Zähler-Header nicht; orientiere dich an der letzten erfolgreichen Antwort. Der Zähler ist über alle Server-Instanzen geteilt, das Limit gilt also global pro Key.

Zusätzlich werden fehlgeschlagene Authentifizierungen pro IP gezählt (20 pro Minute). Wer ungültige Keys durchprobiert, bekommt 429 statt weiterer 401.

Fehlerformat (einheitlich für alle v1-Routen):

{ "error": { "code": "validation_error", "message": "pipeline_id: Invalid UUID" } }
HTTP code Wann
401 unauthorized Key fehlt, ist unbekannt oder widerrufen
404 not_found Deal/Pipeline/Abo gehört nicht zum Workspace des Keys oder existiert nicht
422 validation_error Ungültiger Body oder Query-Parameter, unbekanntes Feld, Phase passt nicht zur Pipeline
429 rate_limited Limit erreicht, siehe Retry-After
500 internal_error Datenbankfehler auf unserer Seite

Zwei Regeln, die viele Integratoren überraschen:

  • Unbekannte Felder und Query-Parameter werden mit 422 abgelehnt, nicht still ignoriert. Ein ?page=2 oder ein "status": "gewonnen" im Body ist ein Fehler.
  • Kein 400. Ungültiges JSON landet ebenfalls als 422 validation_error.

3.3 Endpunkt-Referenz

Methode Pfad Zweck
GET /me Verbindungstest, liefert den Workspace des Keys
GET /pipelines Alle Pipelines mit Phasen
GET /deals Deals auflisten, filtern, blättern
POST /deals Deal anlegen
GET /deals/{id} Einzelnen Deal lesen
PATCH /deals/{id} Deal ändern oder in eine andere Phase verschieben
DELETE /deals/{id} Deal löschen (Soft-Delete, 30 Tage wiederherstellbar)
GET /custom-fields Definitionen der eigenen Felder
POST /hooks Webhook-Abo per API anlegen (für Zapier-artige Integrationen)
DELETE /hooks/{id} Per API angelegtes Abo wieder entfernen
GET /webhook-subscriptions Alle Abos des Workspace inklusive Pause-Zustand
GET /webhook-subscriptions/{id}/deliveries Zustellprotokoll eines Abos

Alle Pfade relativ zu https://cockpit.set2sell.io/api/v1.

GET/me

curl https://cockpit.set2sell.io/api/v1/me \
  -H "Authorization: Bearer s2s_live_…"
{ "data": { "team": { "id": "3f2c…", "name": "Beispiel GmbH" } } }

GET/pipelines

Liefert alle aktiven (nicht gelöschten, nicht archivierten) Pipelines, sortiert nach Position, jeweils mit ihren Phasen.

{
  "data": [
    {
      "id": "8a1e…", "name": "Vertrieb", "is_default": true,
      "stages": [
        { "id": "c0d1…", "name": "Neu",        "position": 0, "mapped_status": "offen" },
        { "id": "c0d2…", "name": "Termin",     "position": 1, "mapped_status": "termin" },
        { "id": "c0d3…", "name": "Gewonnen",   "position": 2, "mapped_status": "gewonnen" },
        { "id": "c0d4…", "name": "Verloren",   "position": 3, "mapped_status": "verloren" }
      ]
    }
  ]
}

Die mapped_status-Werte brauchst du, um zu wissen, welche Phase einen Deal auf „gewonnen" oder „verloren" setzt.

GET/deals

Query-Parameter (alle optional; unbekannte Parameter → 422):

Parameter Typ Bedeutung
pipeline_id UUID nur Deals dieser Pipeline
stage_id UUID nur Deals dieser Phase
status offen|termin|nachfassen|ungeeignet|gewonnen|verloren nach Status filtern
updated_since ISO-8601 mit Zeitzone nur Deals mit updated_at >= Wert
search Text, max. 200 Zeichen Teilstring-Suche über Titel, Kontaktname, E-Mail, Telefon, Firma
limit 1–100, Standard 25 Seitengröße
cursor UUID Fortsetzung, Wert aus next_cursor der vorigen Antwort
{ "data": [ …Deal… ], "next_cursor": "9b7f…" }

Blättern: Solange next_cursor nicht null ist, die Anfrage mit ?cursor=<wert> wiederholen. Die Reihenfolge ist stabil, aber nicht chronologisch (sortiert nach id). Für „alles seit Zeitpunkt X" deshalb updated_since verwenden, nicht die Cursor-Reihenfolge.

POST/deals

curl -X POST https://cockpit.set2sell.io/api/v1/deals \
  -H "Authorization: Bearer s2s_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "pipeline_id": "8a1e…",
    "first_name": "Maria",
    "last_name": "Beispiel",
    "email": "maria@example.com",
    "phone": "+49 170 1234567",
    "company": "Beispiel GmbH",
    "value": 2500,
    "source": "dein-produkt",
    "tags": ["webinar-2026-09"],
    "notes": "Hat sich über das Herbst-Webinar angemeldet."
  }'

Body-Felder:

Feld Typ Pflicht Hinweis
pipeline_id UUID ja muss zum Workspace gehören, sonst 404
stage_id UUID nein Standard: erste Phase der Pipeline; muss zur Pipeline gehören, sonst 422
title Text ≤ 255 nein Standard: contact_name, sonst E-Mail, sonst „API Lead"
first_name, last_name Text ≤ 255 nein empfohlen statt contact_name
contact_name Text ≤ 255 nein wird serverseitig am letzten Leerzeichen in Vor-/Nachname geteilt, wenn first_name/last_name fehlen
email E-Mail ≤ 255 nein wird kleingeschrieben gespeichert
phone Text ≤ 50 nein
company Text ≤ 255 nein
value Zahl 0 … 9.999.999.999 nein Deal-Wert in der Workspace-Währung
notes Text ≤ 10.000 nein
source Text ≤ 100 nein Standard api; setze hier den Namen deines Produkts
tags Liste, max. 50 × 100 Zeichen nein
priority low|medium|high|urgent nein
expected_close_date YYYY-MM-DD nein

Antwort: 201 mit { "data": Deal } (Deal-Objekt siehe 3.4). Beim Anlegen wird eine Notiz „Deal über die öffentliche API erstellt" in die Zeitleiste des Deals geschrieben und das Webhook-Ereignis deal.created ausgelöst. Automationen des Kunden mit dem Auslöser „Deal: Neu erstellt" starten für API-Deals derzeit nicht (Matrix in 2.4).

Keine Idempotenz: Ein wiederholter POST (etwa durch einen Netzwerk-Retry) legt einen zweiten Deal an. Es gibt in v1 keinen Idempotency-Key-Header und keine serverseitige Dublettenprüfung. Integrationen mit eigener Retry-Logik sollten vor dem Anlegen per GET /deals?search=<email> prüfen oder ihre eigene Deduplizierung führen. (Der Lead-Import-Webhook in Abschnitt 5 hat eine Dublettenerkennung — das ist der wesentliche funktionale Unterschied zwischen den beiden Wegen.)

GET/deals/{id}

200 mit { "data": Deal }; 404, wenn der Deal nicht zum Workspace gehört oder gelöscht ist; 422, wenn id keine UUID ist.

PATCH/deals/{id}

Akzeptiert dieselben Felder wie POST außer pipeline_id, zusätzlich probability (ganze Zahl 0–100) und expected_close_date: null zum Leeren. Nur gesendete Felder werden geändert.

curl -X PATCH https://cockpit.set2sell.io/api/v1/deals/9b7f… \
  -H "Authorization: Bearer s2s_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "stage_id": "c0d3…" }'

Ein stage_id-Wechsel setzt Status, Gewinn-/Abschlussdatum und Wahrscheinlichkeit automatisch. Ausgelöste Webhook-Ereignisse: immer deal.updated; bei Phasenwechsel zusätzlich deal.stage_changed; beim Kippen des Status zusätzlich deal.won oder deal.lost. Automationen des Kunden („Deal: Stage gewechselt", „Deal: Status geändert", „Deal: Tag hinzugefügt") reagieren auf API-Änderungen derzeit nicht (Matrix in 2.4).

Was PATCH bewusst nicht kann:

  • pipeline_id ändern. Ein Pipeline-Wechsel würde die Phasen-Zuordnung ungültig machen. Dafür einen neuen Deal anlegen.
  • status, won_date, closed_at setzen. Diese Felder sind abgeleitet.
  • probability gegen eine Gewonnen-/Verloren-Phase durchsetzen: Wird beides in einer Anfrage geschickt, gewinnt die Ableitung (100 bzw. 0).
  • custom_fields schreiben (→ 422). Eigene Felder werden über die Oberfläche, den Lead-Import-Webhook oder MCP befüllt.

DELETE/deals/{id}

204 ohne Rumpf. Der Deal verschwindet sofort aus Oberfläche und API, bleibt aber 30 Tage wiederherstellbar (Soft-Delete). Laufende E-Mail-Sequenzen des Deals werden beendet, und deal.deleted wird ausgelöst. 404, wenn der Deal nicht zum Workspace gehört oder bereits gelöscht ist.

GET/custom-fields

Optionaler Filter ?pipeline_id=<uuid> (404, wenn die Pipeline nicht zum Workspace gehört). Ohne Filter: Felder aller aktiven Pipelines.

{
  "data": [
    {
      "id": "f1a2…", "pipeline_id": "8a1e…",
      "name": "Wunsch-Coaching", "type": "select",
      "required": false,
      "options": ["Einzel", "Gruppe"],
      "description": null
    }
  ]
}

name ist zugleich der Schlüssel, unter dem der Wert im Deal-Objekt (custom_fields) und im Lead-Import-Payload steht — exakte Schreibweise inklusive Groß-/Kleinschreibung und Leerzeichen. type ist eines von text | number | select | multiselect | boolean | date | email | phone | url.

POST/hooks·DELETE/hooks/{id}

Damit legt eine Integration ein Webhook-Abo programmatisch an, ohne dass der Kunde die Oberfläche bedienen muss (so arbeitet unsere Zapier-App).

curl -X POST https://cockpit.set2sell.io/api/v1/hooks \
  -H "Authorization: Bearer s2s_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Dein Produkt",
    "url": "https://hooks.dein-produkt.de/set2sell/abc123",
    "events": ["deal.created", "deal.stage_changed", "appointment.created"]
  }'

201 mit { "data": { "id": "…", "secret": "…" } }. Das Secret wird nur in dieser Antwort ausgegeben — sofort speichern, du brauchst es für die Signaturprüfung. 422, wenn die URL kein HTTPS ist oder auf eine private Adresse zeigt. name ist optional (Standard „Zapier"). Erlaubte events siehe Abschnitt 4.2.

Per API angelegte Abos erscheinen dem Kunden in der Oberfläche als schreibgeschützt („von Zapier verwaltet"): Er sieht sie samt Zustellprotokoll, kann sie aber nicht bearbeiten. Anlegen und Entfernen liegt bei deiner Integration.

DELETE /hooks/{id}200 { "ok": true }. 404, wenn die ID unbekannt ist oder das Abo vom Kunden in der Oberfläche angelegt wurde — solche Abos sind über die API unantastbar.

GET/webhook-subscriptions

Alle Abos des Workspace, egal ob per Oberfläche oder API angelegt — ohne Secret.

{
  "data": [
    {
      "id": "…", "name": "Dein Produkt", "url": "https://…",
      "events": ["deal.stage_changed"], "active": true,
      "paused_at": null, "paused_reason": null,
      "failure_count": 0, "created_at": "2026-09-01T10:00:00.000Z"
    }
  ]
}

failure_count zählt endgültig gescheiterte Zustellungen in Folge; jede erfolgreiche Zustellung setzt ihn auf 0. Ab 20 wird das Abo automatisch pausiert (active: false, paused_at und paused_reason gesetzt). Damit kann deine Integration selbst erkennen, dass sie nichts mehr bekommt, statt auf den Kunden zu warten.

GET/webhook-subscriptions/{id}/deliveries

Zustellprotokoll eines Abos, neueste zuerst. Query: limit (1–100, Standard 50), status (pending | delivering | succeeded | failed | dead), since (ISO-8601, created_at >= since).

{
  "data": [
    {
      "id": "…", "event": "deal.stage_changed", "status": "succeeded",
      "attempt": 1, "last_response_status": 200, "last_response_body": "ok",
      "last_error": null, "duration_ms": 184,
      "created_at": "…", "delivered_at": "…", "next_attempt_at": null
    }
  ]
}

Bewusst ohne Cursor: Zustellungen eines Ereignis-Stapels teilen exakt denselben created_at-Stempel. Inkrementell pollst du mit since (leicht überlappend) und dedupliziert über die Delivery-id.

3.4 Das Deal-Objekt

So sieht ein Deal in allen v1-Antworten aus:

{
  "id": "9b7f…",
  "title": "Maria Beispiel",
  "status": "offen",
  "pipeline_id": "8a1e…",
  "stage_id": "c0d1…",
  "value": 2500,
  "contact": {
    "name": "Maria Beispiel",
    "first_name": "Maria",
    "last_name": "Beispiel",
    "email": "maria@example.com",
    "phone": "+49 170 1234567",
    "company": "Beispiel GmbH"
  },
  "tags": ["webinar-2026-09"],
  "custom_fields": { "Wunsch-Coaching": "Einzel" },
  "priority": "medium",
  "probability": 50,
  "source": "dein-produkt",
  "notes": "Hat sich über das Herbst-Webinar angemeldet.",
  "expected_close_date": null,
  "won_date": null,
  "closed_at": null,
  "created_at": "2026-09-11T08:00:00.000Z",
  "updated_at": "2026-09-11T08:00:00.000Z"
}

custom_fields ist nach Feldname geschlüsselt und zeigt dieselben Werte wie die Oberfläche. Fehlende Werte sind null, nicht weggelassen.

Achtung, zwei Formen: Die REST-API liefert Kontaktdaten verschachtelt (contact.email). Webhook-Payloads liefern dagegen die flache Datenbankzeile (contact_email). Eine Integration, die beides verarbeitet, muss beide Formen kennen. Die Zuordnung steht in Abschnitt 4.3.

3.5 Verhaltensregeln auf einen Blick

  • Status folgt der Phase. Nie status senden; stage_id verschieben.
  • contact_name vs. Vor-/Nachname. Sende bevorzugt first_name und last_name. Ein alleiniger contact_name wird am letzten Leerzeichen geteilt („Anna Maria Müller" → Vorname „Anna Maria", Nachname „Müller"). Werden alle drei gesendet, gewinnen Vor- und Nachname.
  • Kein Idempotency-Key. Dedupliziere selbst oder nutze den Lead-Import-Webhook.
  • Cursor ist nicht chronologisch. Für Deltas updated_since.
  • Löschen ist ein Soft-Delete. 30 Tage wiederherstellbar durch den Kunden.
  • source gehört dir. Setze deinen Produktnamen; der Kunde sieht ihn als Lead-Quelle und kann danach filtern.

3.6 Beispiel-Ablauf: Anbindung in vier Aufrufen

KEY="s2s_live_…"; BASE="https://cockpit.set2sell.io/api/v1"

# 1. Verbindung prüfen
curl -s "$BASE/me" -H "Authorization: Bearer $KEY"

# 2. Pipelines und Phasen holen, Ziel-Pipeline merken
curl -s "$BASE/pipelines" -H "Authorization: Bearer $KEY"

# 3. Deal anlegen
curl -s -X POST "$BASE/deals" -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"pipeline_id":"8a1e…","first_name":"Maria","last_name":"Beispiel","email":"maria@example.com","source":"dein-produkt"}'

# 4. Später: Deal in die Phase „Termin" verschieben
curl -s -X PATCH "$BASE/deals/9b7f…" -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"stage_id":"c0d2…"}'

4. Ausgehende Webhooks (Ereignis-Abos)

Set2Sell Cockpit schickt Ereignisse per HTTP POST an eine URL deiner Wahl, sobald sie passieren. Die Zustellung ist signiert, wird bei Fehlern wiederholt und ist für den Kunden und für dich (per API) nachvollziehbar.

4.1 Abo einrichten

Über die Oberfläche (durch den Kunden): Einstellungen → Workspace → Tab „Webhooks" → Neuer Webhook: Name, HTTPS-URL, Ereignisse auswählen, optional eigene Header (z. B. ein eigener Auth-Token) mitgeben. Nach dem Speichern wird das Signing-Secret einmalig angezeigt.

Per API (durch deine Integration): POST /api/v1/hooks, siehe Abschnitt 3.3.

Über die Oberfläche kann der Kunde außerdem jederzeit ein Testereignis (webhook.test) auslösen, fehlgeschlagene Zustellungen erneut anstoßen und ein pausiertes Abo reaktivieren.

4.2 Ereignisse

Ereignis Wann Form von data
deal.created Deal von Hand in der Oberfläche oder über POST /api/v1/deals angelegt Deal-Zeile
deal.updated Deal geändert Deal-Zeile
deal.stage_changed Deal in andere Phase verschoben (Board, API, Automation, MCP, Import) { deal, previous_stage_id, new_stage_id }
deal.won Status kippt auf gewonnen Deal-Zeile
deal.lost Status kippt auf verloren Deal-Zeile
deal.deleted Deal gelöscht Deal-Zeile (Stand vor dem Löschen)
appointment.created Termin gebucht (Buchungsseite, Funnel, Oberfläche, MCP) Termin-Zeile + event_type_name, bei Buchungen zusätzlich attribution
appointment.cancelled Termin abgesagt Termin-Zeile
appointment.rescheduled Start- oder Endzeit geändert { appointment, previous_start_time, previous_end_time }
task.created Aufgabe angelegt Aufgaben-Zeile
task.completed Aufgabe erledigt Aufgaben-Zeile
call.completed Telefonat über die Cockpit-Telefonie beendet { id, team_id, call_sid, phone, direction, duration, result, user_id, deal_id, created_at }
note.created Nutzer schreibt eine Notiz an einen Deal (keine automatischen Aktivitäten) Aktivitäts-Zeile
lead.imported Kunde importiert Deals per CSV/Datei oder Pipeline-Import in der Oberfläche { import_id, count, deals[] } bzw. { source: "pipeline_import", pipeline_id, count }
funnel.submission_completed Besucher schließt ein Funnel-Formular ab { funnel_id, funnel_name, deal_id, fields, quiz_score }
webhook.test Kunde klickt „Test-Event senden" { message, triggered_by_user_id, timestamp }

Hinweise:

  • deal.created deckt nicht jeden Entstehungsweg ab (Matrix in 2.4). Es feuert, wenn ein Nutzer einen Deal in der Oberfläche anlegt oder deine Integration POST /api/v1/deals aufruft. Deals aus Funnel, Buchungsseite, Lead-Import-Webhook oder MCP lösen es nicht aus. Für diese Wege abonniere die Quell-Ereignisse: funnel.submission_completed (trägt deal_id) und appointment.created (trägt deal_id in der Termin-Zeile). Wenn dein Produkt selbst Leads einspielt und gleichzeitig auf neue Deals hören will, lege sie über POST /api/v1/deals an.
  • lead.imported feuert nur bei Datei- und Pipeline-Importen in der Oberfläche, nicht für Deals aus REST-API oder Lead-Import-Webhook.
  • deal_id bei funnel.submission_completed ist null, solange die Einreichung noch keinem Deal zugeordnet ist; quiz_score ist null außerhalb von Quiz-Funnels.
  • appointment.created trägt attribution (Werbe-Klick-IDs, UTM-Parameter, Landing-URL), wenn die Buchung über eine Buchungsseite oder einen Funnel kam.

4.3 Zustellformat

Jede Zustellung ist ein POST mit Content-Type: application/json und diesen Headern:

X-Set2Sell-Event: deal.stage_changed
X-Set2Sell-Delivery-Id: 6d0c1c1e-…            ← eindeutig pro Zustellung
X-Set2Sell-Timestamp: 1757577600               ← Unix-Sekunden
X-Set2Sell-Signature: sha256=3f9a…             ← siehe 4.4

Dazu kommen die vom Kunden konfigurierten eigenen Header. Der Body ist ein Umschlag:

{
  "id": "evt_6d0c1c1e-…",
  "event": "deal.stage_changed",
  "created_at": "2026-09-11T08:00:00.000Z",
  "team_id": "3f2c…",
  "data": {
    "deal": {
      "id": "9b7f…",
      "title": "Maria Beispiel",
      "status": "termin",
      "pipeline_id": "8a1e…",
      "stage_id": "c0d2…",
      "value": 2500,
      "contact_name": "Maria Beispiel",
      "first_name": "Maria",
      "last_name": "Beispiel",
      "contact_email": "maria@example.com",
      "contact_phone": "+49 170 1234567",
      "company": "Beispiel GmbH",
      "tags": ["webinar-2026-09"],
      "custom_fields": { "Wunsch-Coaching": "Einzel" },
      "source": "dein-produkt",
      "user_id": "user_…",
      "created_at": "…",
      "updated_at": "…"
    },
    "previous_stage_id": "c0d1…",
    "new_stage_id": "c0d2…"
  }
}

id im Umschlag ist evt_ + Delivery-ID. Bei einem erneuten Zustellversuch bleiben id, Body und Delivery-ID gleich; nur X-Set2Sell-Timestamp und die Signatur werden neu berechnet.

Deal-Zeile (flach) ↔ REST-Deal (verschachtelt):

Webhook (data) REST-API
contact_name contact.name
first_name, last_name contact.first_name, contact.last_name
contact_email contact.email
contact_phone contact.phone
company contact.company
custom_fields (nach Feldname) custom_fields (identisch)
weitere Spalten der Datenbankzeile (user_id, probability, notes, …) Teilmenge davon

Die Deal-Zeile ist die vollständige Datenbankzeile. Neue Spalten können jederzeit dazukommen; verlasse dich nur auf die hier genannten Felder und ignoriere unbekannte.

4.4 Signatur prüfen

Signatur = sha256= + Hex(HMAC-SHA256(Secret, <timestamp>.<raw-body>)).

  • Raw Body heißt: die Bytes, wie sie ankommen — vor dem JSON-Parsen und ohne Neuformatierung.
  • Vergleiche timing-sicher.
  • Weise Zustellungen ab, deren Timestamp mehr als 5 Minuten von deiner Uhr abweicht (Replay-Schutz).

Node.js

const crypto = require('crypto')

function verify(secret, headers, rawBody) {
  const ts = headers['x-set2sell-timestamp']
  const sig = headers['x-set2sell-signature'] || ''
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false
  const expected = 'sha256=' + crypto.createHmac('sha256', secret)
    .update(`${ts}.${rawBody}`).digest('hex')
  return expected.length === sig.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))
}

Python

import hmac, hashlib, time

def verify(secret: str, headers: dict, raw_body: bytes) -> bool:
    ts = headers.get('X-Set2Sell-Timestamp', '')
    sig = headers.get('X-Set2Sell-Signature', '')
    if abs(time.time() - int(ts or 0)) > 300:
        return False
    expected = 'sha256=' + hmac.new(
        secret.encode(), f'{ts}.'.encode() + raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, sig)

PHP

function verify(string $secret): bool {
    $ts  = $_SERVER['HTTP_X_SET2SELL_TIMESTAMP'] ?? '';
    $sig = $_SERVER['HTTP_X_SET2SELL_SIGNATURE'] ?? '';
    $body = file_get_contents('php://input');
    if (abs(time() - intval($ts)) > 300) return false;
    $expected = 'sha256=' . hash_hmac('sha256', "{$ts}.{$body}", $secret);
    return hash_equals($expected, $sig);
}

4.5 Zustellung, Wiederholung, Auto-Pause

Regel Wert
Erfolg jede Antwort mit Status 2xx
Timeout 10 Sekunden pro Versuch
Versuche bis zu 5
Abstände nach Fehlschlag 1 Min → 5 Min → 30 Min → 2 Std → 12 Std (insgesamt rund 14 Stunden)
nach dem 5. Fehlschlag Zustellung dead; der Kunde kann sie in der Oberfläche erneut anstoßen
Auto-Pause 20 dead-Zustellungen in Folge → Abo pausiert, Ersteller wird per E-Mail informiert
Reaktivierung durch den Kunden in der Oberfläche; setzt den Zähler zurück
Versandtakt Ausgehende Zustellungen werden im Minutentakt verschickt; rechne mit bis zu ~60 Sekunden Verzug
Reihenfolge nicht garantiert; sortiere bei Bedarf nach created_at im Umschlag

Gespeichert werden pro Zustellung Antwortstatus, die ersten 4.096 Zeichen des Antwortkörpers, Antwort-Header, Fehlermeldung und Dauer — abrufbar für den Kunden in der Oberfläche und für dich über GET /webhook-subscriptions/{id}/deliveries.

4.6 Empfehlungen für Empfänger

  1. Sofort 2xx antworten, dann verarbeiten. Alles, was länger als 10 Sekunden dauert, gilt als Fehlschlag und wird wiederholt.
  2. Idempotent verarbeiten. Dedupliziere über X-Set2Sell-Delivery-Id (oder id im Umschlag); Wiederholungen tragen dieselbe ID.
  3. Signatur und Timestamp prüfen, nicht die Absender-IP.
  4. Unbekannte Felder ignorieren. Payloads wachsen additiv.
  5. Pausen überwachen: Poll GET /webhook-subscriptions gelegentlich oder bitte den Kunden, die Pause-Mail zu beachten.

4.7 Zielsystem-Format „Trakyo"

Neben dem Standard-Umschlag gibt es ein festes Sonderformat für die YouTube-Attributions-Plattform Trakyo: flacher Body (trakyo_id, email, name, phone, event_name) nur für appointment.created aus Buchungsseiten und Funnels, ohne Umschlag und ohne Signatur. Das wählt der Kunde beim Anlegen des Abos als „Zielsystem". Andere Partner-spezifische Formate sind auf Anfrage möglich; Standard ist der Umschlag aus 4.3.


5. Eingehender Lead-Import-Webhook

Der Lead-Import-Webhook ist der ältere, auf No-Code-Tools zugeschnittene Weg, Leads einzuspielen. Sein Mehrwert gegenüber POST /api/v1/deals: Dublettenerkennung mit Anreicherung, Pipeline und Phase per Name statt UUID, Schreiben von eigenen Feldern und ein Batch-Modus.

Dafür ist die Authentifizierung schwächer (Nutzer-ID statt Secret), das Fehlerformat ein anderes, und aus per Webhook angelegten Deals feuert kein deal.created-Abo. Für neue, produktive Partner-Integrationen empfehlen wir deshalb die REST-API v1; dieser Abschnitt gilt vor allem für Zapier-Catch-Hooks, Make, n8n und Formular-Tools.

5.1 Endpunkt und Header

POST https://cockpit.set2sell.io/api/webhooks/lead-import
Content-Type: application/json
x-user-id: user_…
  • x-user-id ist die Nutzer-ID eines Mitglieds des Ziel-Workspace (beginnt mit user_). Der Kunde findet sie unter Einstellungen → Webhooks zusammen mit einem Konfigurations-Generator, der die fertige Zapier-/Make-Konfiguration erzeugt. Fehlt der Header: 401; falsches Format: 403.
  • x-webhook-signature (HMAC-SHA256 über den Rohbody, Hex, optional mit Präfix sha256=) ist vorgesehen, aber für Partner nicht erforderlich.
  • Rate-Limit: 50 Anfragen pro Minute und Nutzer-ID (ohne Header: pro IP). Bei Überschreitung 429 mit retryAfter in Sekunden.

Ein GET auf denselben Pfad liefert eine maschinenlesbare Selbstbeschreibung (Felder, Formate); mit ?options=true und x-user-id zusätzlich die Pipelines und Phasen des Nutzers samt IDs.

5.2 Einzel-Lead

{
  "title": "Maria Beispiel",
  "pipeline": "Vertrieb",
  "stage": "Neu",
  "first_name": "Maria",
  "last_name": "Beispiel",
  "contact_email": "maria@example.com",
  "contact_phone": "+49 170 1234567",
  "company": "Beispiel GmbH",
  "value": 2500,
  "notes": "Webinar-Anmeldung",
  "source": "dein-produkt",
  "source_id": "lead_48213",
  "tags": ["webinar-2026-09"],
  "custom_fields": { "Wunsch-Coaching": "Einzel" },
  "mode": "enrich"
}
Feld Typ Pflicht Hinweis
title Text ja Deal-Titel
pipeline_id oder pipeline UUID / Name nein* Name wird ohne Groß-/Kleinschreibung unter den Pipelines gesucht, die dieser Nutzer angelegt hat. Für Pipelines anderer Mitglieder pipeline_id verwenden
stage_id oder stage UUID / Name nein Standard: erste Phase
first_name, last_name, contact_name Text nein wie in der REST-API; contact_name wird am ersten Leerzeichen geteilt
contact_email E-Mail nein Achtung: hier contact_email, nicht email
contact_phone, company Text nein
value Zahl ≥ 0 nein Standard 0
probability 0–100 nein Standard 0
expected_close_date Text (ISO-Datum) nein
notes Text nein
source, source_id Text nein eigene Herkunftskennung, source_id = ID in deinem System
tags Liste von Text nein
custom_fields Objekt, Schlüssel = Feldname nein wird gegen die Felddefinitionen der Pipeline geprüft (Pflichtfelder, Typen)
mode enrich (Standard) / update nein Verhalten bei Dubletten, siehe 5.4

* Fehlen pipeline_id und pipeline, wird die Standard-Pipeline des Nutzers verwendet. Gibt es keine, schlägt der Aufruf mit einer Fehlermeldung fehl.

5.3 Batch (bis 100 Leads)

{
  "default_pipeline_name": "Vertrieb",
  "default_stage_name": "Neu",
  "leads": [
    { "title": "Lead 1", "contact_email": "a@example.com" },
    { "title": "Lead 2", "contact_email": "b@example.com", "stage": "Termin" }
  ]
}

Statt der Namen gehen auch default_pipeline_id / default_stage_id. Jeder Lead darf die Vorgaben mit eigenen pipeline/stage-Angaben überschreiben. Jeder Lead wird einzeln verarbeitet; ein fehlerhafter Lead bricht den Stapel nicht ab.

5.4 Dublettenerkennung und Anreicherung

Vor dem Anlegen wird innerhalb des Workspace nach einem bestehenden Deal gesucht, in dieser Reihenfolge:

  1. gleiche E-Mail (normalisiert),
  2. sonst gleiche Telefonnummer (normalisiert),
  3. sonst gleiche Firma + Kontaktname.

Wird ein Treffer gefunden, wird kein neuer Deal angelegt, sondern der bestehende bearbeitet:

mode Felder Phase
enrich (Standard) füllt nur leere Felder des bestehenden Deals; custom_fields ergänzt nur neue Schlüssel; tags werden vereinigt; notes werden mit Datum angehängt bleibt unverändert
update überschreibt gesendete, nicht-leere Felder wird in die ausdrücklich angegebene Phase verschoben — nur vorwärts und nur innerhalb derselben Pipeline; dabei feuern deal.stage_changed und ggf. deal.won/deal.lost

Die Antwort sagt dir, was passiert ist:

{
  "success": true,
  "action": "enriched",
  "deal_id": "9b7f…",
  "message": "Existing deal enriched with 2 new field(s): contact_phone, company",
  "fields_updated": ["contact_phone", "company"]
}

5.5 Antworten

HTTP Body Bedeutung
200 { "success": true, "deal_id": "…", "message": "Lead imported successfully" } neuer Deal angelegt
200 { "success": true, "action": "enriched", "deal_id": "…", "fields_updated": [...] } Dublette gefunden, bestehender Deal bearbeitet
200 { "success": true, "message": "Processed 3 leads", "results": [...], "summary": { "total", "successful", "failed" } } Batch verarbeitet; results[i] trägt success, deal_id bzw. error je Lead
400 { "success": false, "error": "Invalid payload format", "details": [...] } Schema verletzt (z. B. title fehlt, ungültige UUID)
400 { "success": false, "error": "Custom field validation failed", "details": [...] } Pflichtfeld fehlt oder Typ passt nicht
401 { "success": false, "error": "User authentication required…" } x-user-id fehlt
403 { "success": false, "error": "Invalid user ID format" } x-user-id beginnt nicht mit user_
429 { "success": false, "error": "Webhook rate limit exceeded…", "retryAfter": 30 } Limit erreicht
500 { "success": false, "error": "…", "hint": "…" } z. B. keine Pipeline gefunden; die Meldung nennt die Ursache

5.6 Was der Lead-Import auslöst und was nicht

  • Angelegte Deals starten Automationen des Kunden mit Auslöser „Deal: Neu erstellt" — der einzige Integrationsweg, der das heute tut (Matrix in 2.4).
  • Angelegte Deals lösen kein Webhook-Abo deal.created und kein lead.imported aus.
  • Im update-Modus lösen Phasenwechsel die Webhook-Abos deal.stage_changed sowie deal.won/deal.lost aus, aber keine Automation „Deal: Stage gewechselt".
  • Jeder Aufruf wird serverseitig protokolliert (Status, Dauer, Fehler; Signaturen und Auth-Header geschwärzt). Bei Support-Anfragen hilft deshalb der Zeitstempel des Aufrufs.

6. Zapier, Make und n8n

6.1 Zapier-App „Set2Sell Cockpit"

Es gibt eine fertige Zapier-App. Sie ist als private App veröffentlicht, das heißt Kunden erreichen sie über einen Einladungslink, nicht über die Zapier-Suche:

https://zapier.com/developer/public-invite/244466/98564210ea8523c8d3d4a36cb30a2a4a/

Der Kunde findet denselben Link mit einer Schritt-für-Schritt-Anleitung im Cockpit unter Einstellungen → Workspace → Tab „API".

Verbindung: API-Key (s2s_live_…). Die App ruft GET /me als Verbindungstest auf und zeigt den Workspace-Namen als Verbindungsbezeichnung.

Trigger (alle instant, per Webhook-Abo):

Trigger Ereignis Payload
Neuer Lead lead.imported Deal-Felder flach (nur bei Datei-/Pipeline-Import in der Oberfläche)
Deal gewonnen deal.won Deal-Felder flach
Deal verloren deal.lost Deal-Felder flach
Deal-Phase geändert deal.stage_changed deal, previous_stage_id, new_stage_id
Termin gebucht appointment.created Termin-Felder flach
Funnel-Formular abgeschickt funnel.submission_completed funnel_id, funnel_name, deal_id, fields, quiz_score

Jeder Zap-Datensatz trägt zusätzlich id (Delivery-ID) und event.

Aktionen:

Aktion Ruft auf Felder
Deal/Lead anlegen POST /api/v1/deals Pipeline und Phase als Dropdown (dynamisch geladen), Titel, Kontaktname oder Vor-/Nachname, E-Mail, Telefon, Firma, Wert, Notizen, Quelle
Deal aktualisieren PATCH /api/v1/deals/{id} Deal-ID plus dieselben Felder außer Pipeline

Die Abos, die Zapier anlegt, erscheinen dem Kunden im Webhook-Tab als „von Zapier verwaltet" und werden beim Ausschalten des Zaps automatisch wieder entfernt.

Beide Aktionen laufen über die REST-API. Damit gilt auch hier: Ein per Zap angelegter oder verschobener Deal startet derzeit keine Automation des Kunden (Matrix in 2.4). Wer das braucht, nutzt für das Anlegen „Webhooks by Zapier → POST" auf den Lead-Import-Webhook (Abschnitt 5).

Lücke, die du kennen solltest: Es gibt in der Zapier-App keinen Trigger für deal.created. Wer auf jeden neuen Deal reagieren will, legt im Cockpit ein Webhook-Abo auf deal.created an und nutzt in Zapier „Webhooks by Zapier → Catch Hook".

6.2 Make (ehemals Integromat) und n8n

Für beide gibt es keine eigene App. Sie arbeiten über ihre generischen Module:

  • Leads anlegen / Deals lesen: HTTP-Modul gegen https://cockpit.set2sell.io/api/v1/… mit Header Authorization: Bearer s2s_live_… (Abschnitt 3).
  • Auf Ereignisse reagieren: „Custom Webhook" (Make) bzw. „Webhook"-Node (n8n) als Ziel-URL eines Webhook-Abos eintragen (Abschnitt 4). Die Signaturprüfung ist dort optional; das Secret kann aber als zweiter Faktor über einen eigenen Header mitgegeben werden (Kunde trägt beim Abo einen eigenen Header ein, das Szenario prüft ihn).
  • Alternativ ohne API-Key: Lead-Import-Webhook (Abschnitt 5) mit x-user-id.

7. KI-Anbindung per MCP

Set2Sell Cockpit stellt einen MCP-Server (Model Context Protocol) bereit. Damit kann ein KI-Assistent des Kunden — etwa Claude im Web, als Desktop-App oder in Claude Code, aber auch jeder andere MCP-Client mit OAuth-Unterstützung — das CRM des Nutzers direkt bedienen: Deals suchen und anlegen, Termine buchen, Terminarten pflegen, Funnels bauen und veröffentlichen, Leads importieren.

7.1 Verbindung

Connector-URL https://cockpit.set2sell.io/api/mcp
Transport Streamable HTTP (kein SSE)
Authentifizierung OAuth 2.1 mit PKCE, Autorisierungsserver https://clerk.set2sell.io
Client-Registrierung Dynamic Client Registration wird unterstützt; ein Client meldet sich selbst an, es braucht keine vorab hinterlegte Client-ID
Discovery GET https://cockpit.set2sell.io/.well-known/oauth-protected-resource/mcp → nennt den Autorisierungsserver; dessen Metadaten liegen unter https://clerk.set2sell.io/.well-known/oauth-authorization-server
Rechte pro Nutzer. Der Nutzer meldet sich mit seinem Cockpit-Konto an; jedes Werkzeug prüft bei jedem Aufruf die Workspace-Mitgliedschaft neu. Der Client sieht genau das, was der Nutzer sieht

Ein Aufruf ohne Token liefert 401 mit WWW-Authenticate: Bearer … resource_metadata="…/.well-known/oauth-protected-resource/mcp" — MCP-Clients starten daraus automatisch den Anmeldefluss. Der Kunde findet die Anleitung im Cockpit unter Profil → Tab „KI-Anbindung (MCP)".

7.2 Werkzeuge (67)

Bereich Werkzeuge
Workspace list_workspaces, list_workspace_members, invite_workspace_members, list_pending_invitations, revoke_invitation
Pipelines list_pipelines, get_pipeline, list_pipeline_templates, create_pipeline, list_custom_fields, create_custom_field
Deals search_deals, get_deal, create_deal, update_deal, move_deal, add_deal_note, add_deal_tags, list_deal_activities, summarize_deal, import_leads, list_lead_conversations, document_call_result
Auswertung get_deal_stats, get_pipeline_stats, query_workspace (nur lesend, über eine feste Tabellen-Auswahl)
Termine list_appointments, get_appointment, book_appointment, update_appointment, reschedule_appointment, cancel_appointment, delete_appointment, get_available_slots
Terminarten list_event_types, get_event_type, create_event_type, update_event_type, duplicate_event_type, delete_event_type, set_event_type_members, set_event_type_availability, set_event_type_notifications
Verfügbarkeit & Kalender get_availability, set_availability, list_availability_overrides, set_availability_override, delete_availability_override, list_calendar_connections, trigger_calendar_sync
Funnels list_funnels, get_funnel, get_funnel_content, list_funnel_templates, create_funnel, create_funnel_from_template, update_funnel, publish_funnel, check_funnel_readiness, describe_funnel_capabilities, describe_workspace_knowledge, start_funnel_split_test, stop_funnel_split_test
Communities list_communities, list_community_members, grant_community_access, revoke_community_access

Jedes Werkzeug nimmt eine workspace_id entgegen; bei Mehrdeutigkeit ruft der Client zuerst list_workspaces auf. Schreibende Werkzeuge protokollieren in der Zeitleiste des Deals. Phasenwechsel über move_deal lösen deal.stage_changed sowie deal.won / deal.lost aus; create_deal über MCP löst derzeit kein deal.created aus (siehe Hinweise in 4.2).

7.3 Abgrenzung zur REST-API

REST-API v1 MCP
Identität Workspace (API-Key) Nutzer (OAuth)
Typischer Nutzer dein Backend ein KI-Client des Kunden
Umfang Deals, Pipelines, Felder, Abos 67 Werkzeuge quer durch das Produkt
Stabilität versioniert (v1) Werkzeugliste wächst laufend; Clients sollten sie zur Laufzeit abfragen

Wenn dein Produkt selbst ein KI-Agent ist, der im Namen eines Nutzers arbeitet, ist MCP der richtige Weg. Für Server-zu-Server-Integrationen bleibt es die REST-API.


8. Einbettungen

Diese Punkte betreffen weniger Partner-Backends als Website-Baukästen und Agenturen, die Cockpit-Elemente in Kundenseiten einbauen.

8.1 Buchungsseite

Eigenständige Seite https://cockpit.set2sell.io/b/<slug>
Einbett-Variante (ohne Rahmen) https://cockpit.set2sell.io/b/<slug>/embed als <iframe>
Auto-Höhe Die Einbett-Seite sendet postMessage({ type: "s2s.booking.height", height }) an die Elternseite; das iframe kann sich damit ohne inneren Scrollbalken anpassen
Eigene Domain Der Kunde kann Buchungsseiten und Funnels unter eigener Domain betreiben (Einstellungen → Domains)

Jede Buchung erzeugt einen Termin und, sofern noch keiner existiert, einen Deal, und löst appointment.created aus.

8.2 Formulare

<script src="https://cockpit.set2sell.io/embed.js" data-form="<slug>" defer></script>

Das Script rendert das Formular an Ort und Stelle in einem iframe, passt die Höhe automatisch an und löst nach dem Absenden auf der Elternseite ein DOM-Ereignis aus:

document.addEventListener('set2sell-form:complete', (e) => {
  console.log(e.detail.submission_id)
})

Optionale Attribute am Script-Tag: data-min-height="<px>" (Starthöhe, Standard 400) und data-host="<url>", falls das Formular unter einer eigenen Domain läuft. Mehrere Formulare auf einer Seite sind möglich.

Alternativ direkt als iframe: https://cockpit.set2sell.io/form/<slug>/embed.

8.3 Funnels

Veröffentlichte Funnels laufen unter https://cockpit.set2sell.io/f/<slug> oder der eigenen Domain des Kunden. Formular-Abschlüsse lösen funnel.submission_completed, Buchungen appointment.created aus. Beide tragen Attribution (UTM, fbclid, gclid, Landing-URL), soweit vom Besucher mitgebracht.


9. Partner-Onboarding

9.1 So läuft eine Partnerschaft typischerweise

  1. Kontakt an info@set2sell.io mit kurzer Beschreibung: Was macht euer Produkt, welche Richtung (Daten rein, Daten raus, beides), welche Ereignisse braucht ihr.
  2. Test-Workspace. Wir legen einen Workspace für euch an, in dem ihr Admin seid: API-Keys, Webhook-Abos, Pipelines, Testdaten — alles selbst steuerbar.
  3. Bauen gegen die Produktionsumgebung mit dem Test-Workspace.
  4. Abnahme: Wir prüfen gemeinsam Signaturprüfung, Idempotenz, Fehlerbehandlung und Rate-Limit-Verhalten.
  5. Listung. Auf Wunsch nehmen wir euch in die Integrationsübersicht im Cockpit auf und stellen eine Kundenanleitung bereit.

9.2 Checkliste vor dem Go-live

REST-API

  • Key wird verschlüsselt gespeichert und nie geloggt.
  • 401 führt zu einer klaren Meldung an den Kunden („Key widerrufen? Neuen Key eintragen").
  • 429 wird mit Retry-After respektiert, nicht sofort wiederholt.
  • 422-Meldungen (error.message) werden dem Kunden oder eurem Support sichtbar gemacht.
  • source ist auf euren Produktnamen gesetzt.
  • Keine Duplikate durch Retries (eigene Deduplizierung oder GET /deals?search=).
  • status wird nie gesendet; Phasenwechsel über stage_id.

Webhooks

  • Signatur und Timestamp werden geprüft.
  • Antwort 2xx innerhalb weniger Sekunden, Verarbeitung asynchron.
  • Dedup über Delivery-ID.
  • Unbekannte Felder werden ignoriert.
  • Umgang mit pausierten Abos (Poll oder Kundenhinweis).

Lead-Import-Webhook (falls genutzt)

  • Nutzer-ID wird als Konfigurationswert vom Kunden abgefragt, nicht erraten.
  • action: "enriched" wird korrekt als „Dublette, vorhandener Deal" behandelt.
  • Batch-results werden je Lead ausgewertet.

9.3 Support

  • Technische Fragen und Test-Workspace: info@set2sell.io
  • Fehlerberichte bitte mit Zeitstempel (UTC), Workspace-ID (aus GET /me), Delivery-ID (bei Webhooks) oder Request-Body (bei API-Fehlern, ohne Key).

10. Änderungspolitik

  • Additive Änderungen ohne Ankündigung: neue Felder in Antworten und Payloads, neue Ereignisse, neue Endpunkte, neue MCP-Werkzeuge. Integrationen müssen unbekannte Felder ignorieren.
  • Nicht ohne neue Version: Entfernen oder Umbenennen von Feldern, Ändern von Typen oder Semantik bestehender Endpunkte, Ändern des Signaturverfahrens. Solche Änderungen erscheinen als v2 neben v1; v1 bleibt mindestens 12 Monate nach Ankündigung erreichbar.
  • Strenge Eingabeprüfung: Weil unbekannte Felder mit 422 abgelehnt werden, ändert sich das Verhalten bestehender Anfragen nicht, wenn wir neue Felder einführen — sie werden erst gültig, wenn ihr sie bewusst sendet.
  • Ankündigungen gehen per E-Mail an die bei uns hinterlegte technische Kontaktadresse des Partners.

11. Anhang

11.1 Status-Werte

Status Bedeutung
offen neuer oder unbearbeiteter Lead
termin Termin vereinbart
nachfassen Wiedervorlage nötig
ungeeignet disqualifiziert
gewonnen Abschluss
verloren kein Abschluss

Der Status ist immer aus dem mapped_status der aktuellen Phase abgeleitet.

11.2 Feld-Limits der REST-API

Feld Limit
title, contact_name, first_name, last_name, email, company 255 Zeichen
phone 50 Zeichen
source 100 Zeichen
notes 10.000 Zeichen
tags max. 50 Einträge à 100 Zeichen
value 0 bis 9.999.999.999
probability ganze Zahl 0 bis 100
search (Query) 200 Zeichen
limit (Query) 1 bis 100

11.3 Typen eigener Felder

text, number, select, multiselect, boolean, date, email, phone, url

11.4 Alle Webhook-Ereignisse

deal.created, deal.updated, deal.stage_changed, deal.won, deal.lost, deal.deleted, appointment.created, appointment.cancelled, appointment.rescheduled, task.created, task.completed, call.completed, lead.imported, note.created, funnel.submission_completed, webhook.test

11.5 Glossar Deutsch ↔ API

Oberfläche API
Workspace team, team_id
Pipeline pipeline, pipeline_id
Phase stage, stage_id, mapped_status
Deal / Lead / Kontakt deal
Eigenes Feld custom_field, Schlüssel = Feldname
Terminart event_type
Termin appointment
Notiz note (Aktivität vom Typ note)
Aufgabe task

Want to become a partner?

We set up a test workspace for you and support the integration through to sign-off.

Get in touch