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_idoderdeal_idliefert404, 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:
- Im Cockpit Einstellungen → Workspace → Tab „API" öffnen (nur als Workspace-Admin sichtbar).
- Neuen Key anlegen, einen Namen vergeben (z. B. den Namen deines Produkts).
- 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
422abgelehnt, nicht still ignoriert. Ein?page=2oder ein"status": "gewonnen"im Body ist ein Fehler. - Kein
400. Ungültiges JSON landet ebenfalls als422 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_atsetzen. Diese Felder sind abgeleitet.probabilitygegen eine Gewonnen-/Verloren-Phase durchsetzen: Wird beides in einer Anfrage geschickt, gewinnt die Ableitung (100 bzw. 0).custom_fieldsschreiben (→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
statussenden;stage_idverschieben. contact_namevs. Vor-/Nachname. Sende bevorzugtfirst_nameundlast_name. Ein alleinigercontact_namewird 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.
sourcegehö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.createddeckt nicht jeden Entstehungsweg ab (Matrix in 2.4). Es feuert, wenn ein Nutzer einen Deal in der Oberfläche anlegt oder deine IntegrationPOST /api/v1/dealsaufruft. 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ägtdeal_id) undappointment.created(trägtdeal_idin der Termin-Zeile). Wenn dein Produkt selbst Leads einspielt und gleichzeitig auf neue Deals hören will, lege sie überPOST /api/v1/dealsan.lead.importedfeuert nur bei Datei- und Pipeline-Importen in der Oberfläche, nicht für Deals aus REST-API oder Lead-Import-Webhook.deal_idbeifunnel.submission_completedistnull, solange die Einreichung noch keinem Deal zugeordnet ist;quiz_scoreistnullaußerhalb von Quiz-Funnels.appointment.createdträgtattribution(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
- Sofort
2xxantworten, dann verarbeiten. Alles, was länger als 10 Sekunden dauert, gilt als Fehlschlag und wird wiederholt. - Idempotent verarbeiten. Dedupliziere über
X-Set2Sell-Delivery-Id(oderidim Umschlag); Wiederholungen tragen dieselbe ID. - Signatur und Timestamp prüfen, nicht die Absender-IP.
- Unbekannte Felder ignorieren. Payloads wachsen additiv.
- Pausen überwachen: Poll
GET /webhook-subscriptionsgelegentlich 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-idist die Nutzer-ID eines Mitglieds des Ziel-Workspace (beginnt mituser_). 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äfixsha256=) ist vorgesehen, aber für Partner nicht erforderlich.- Rate-Limit: 50 Anfragen pro Minute und Nutzer-ID (ohne Header: pro IP).
Bei Überschreitung
429mitretryAfterin 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 |
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:
- gleiche E-Mail (normalisiert),
- sonst gleiche Telefonnummer (normalisiert),
- 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.createdund keinlead.importedaus. - Im
update-Modus lösen Phasenwechsel die Webhook-Abosdeal.stage_changedsowiedeal.won/deal.lostaus, 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 HeaderAuthorization: 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
- Kontakt an
info@set2sell.iomit kurzer Beschreibung: Was macht euer Produkt, welche Richtung (Daten rein, Daten raus, beides), welche Ereignisse braucht ihr. - Test-Workspace. Wir legen einen Workspace für euch an, in dem ihr Admin seid: API-Keys, Webhook-Abos, Pipelines, Testdaten — alles selbst steuerbar.
- Bauen gegen die Produktionsumgebung mit dem Test-Workspace.
- Abnahme: Wir prüfen gemeinsam Signaturprüfung, Idempotenz, Fehlerbehandlung und Rate-Limit-Verhalten.
- 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.
-
401führt zu einer klaren Meldung an den Kunden („Key widerrufen? Neuen Key eintragen"). -
429wird mitRetry-Afterrespektiert, nicht sofort wiederholt. -
422-Meldungen (error.message) werden dem Kunden oder eurem Support sichtbar gemacht. -
sourceist auf euren Produktnamen gesetzt. - Keine Duplikate durch Retries (eigene Deduplizierung oder
GET /deals?search=). -
statuswird nie gesendet; Phasenwechsel überstage_id.
Webhooks
- Signatur und Timestamp werden geprüft.
- Antwort
2xxinnerhalb 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-
resultswerden 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
v2nebenv1;v1bleibt mindestens 12 Monate nach Ankündigung erreichbar. - Strenge Eingabeprüfung: Weil unbekannte Felder mit
422abgelehnt 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 |