REST-API-Referenz
Die öffentliche REST-API von The-Y CRM ist für Server-zu-Server-Integrationen gedacht: Kontakte anlegen/lesen/aktualisieren und Ereignisse per Webhook empfangen. Authentifiziert wird mit einem API-Schlüssel.
Authentifizierung
Jede Anfrage braucht deinen API-Schlüssel im Header — entweder als Bearer-Token oder als X-API-Key:
Authorization: Bearer <DEIN_API_SCHLÜSSEL>
# oder
X-API-Key: <DEIN_API_SCHLÜSSEL>
Schlüssel erstellen: In der App unter Einstellungen → Schnittstellen (unter „Erweitert“ → Verbindungen) → „API-Schlüssel" → neuer Schlüssel. Der Schlüssel wird nur einmal angezeigt — sicher speichern (in The-Y wird nur ein Hash abgelegt). Der Schlüssel bindet an deinen Mandanten.
Rechte, Ablauf und Erneuern (seit 1.1.60): Jeder Schlüssel hat eigene Rechte: contacts:read (Kontakte lesen, GET) und/oder contacts:write (Kontakte anlegen und ändern, POST/PATCH). Auf Wunsch bekommt er ein Ablaufdatum (höchstens 5 Jahre). „Erneuern“ erzeugt einen neuen Schlüssel; der alte gilt noch die gewählte Zeit (sofort, 24 Stunden, 7 oder 30 Tage). Schlüssel von vor 1.1.60 haben weiter volle Rechte ohne Ablauf, bis du „Rechte festlegen“ wählst.
Konventionen
- Format: Requests & Responses in
application/json(UTF-8). - Paginierung: Listen akzeptieren
?limit=(Standard/Max serverseitig gedeckelt) und?offset=; die Antwort enthälthasMore. - Rate-Limit: Anfragen sind pro Schlüssel/Minute gedeckelt; bei Überschreitung kommt
429 Too Many Requests— mit Backoff erneut versuchen. - Fehler: als JSON
{ "error": { "code": "..." } }mit passendem HTTP-Status.
| Status | Bedeutung |
|---|---|
200 / 201 | OK / angelegt |
400 | Validierungsfehler (z. B. Pflichtfeld fehlt) |
401 | Schlüssel fehlt, ist ungültig oder abgelaufen |
403 | Feature nicht freigeschaltet (Paid), dem Schlüssel fehlt das Recht (insufficient_scope) oder das Konto ist gesperrt (tenant_suspended) |
404 | Ressource nicht gefunden |
429 | Rate-Limit überschritten |
Kontakte
GET/contacts
Kontakte auflisten (paginiert). Optional Suche via ?q=.
# Anfrage curl "https://crm.the-y.at/api/ext/v1/contacts?limit=20&offset=0" \ -H "Authorization: Bearer $API_KEY" # Antwort { "data": [ { "id": "c_123", "name": "Maria Muster", "email": "maria@example.com", "phone": "+4366012345678", "company": "ACME" } ], "hasMore": false }
POST/contacts
Einen Kontakt anlegen. Pflichtfeld ist mindestens ein Name oder eine E-Mail.
curl -X POST "https://crm.the-y.at/api/ext/v1/contacts" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Maria Muster", "email": "maria@example.com", "phone": "+4366012345678", "company": "ACME" }'
# 201 Created
{ "data": { "id": "c_123", "name": "Maria Muster", "email": "maria@example.com" } }
GET/contacts/{id}
Einen einzelnen Kontakt lesen.
curl "https://crm.the-y.at/api/ext/v1/contacts/c_123" \ -H "Authorization: Bearer $API_KEY"
PATCH/contacts/{id}
Einen Kontakt teilweise aktualisieren — nur die gesendeten Felder werden geändert (bestehende bleiben erhalten).
curl -X PATCH "https://crm.the-y.at/api/ext/v1/contacts/c_123" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "phone": "+4366099999999" }'
Ausgehende Webhooks (Events)
Statt zu pollen, lässt du dir Ereignisse an deine URL schicken. Unter Einstellungen → Schnittstellen → „Daten senden (Webhook)" legst du Ziel-URL und Ereignisse an (z. B. contact.created, contact.updated). Das Geheimnis zum Signieren erzeugt das CRM.
- The-Y sendet ein
POSTmit JSON an deine URL, z. B.{ "event": "contact.created", "tenant": "<Konto-ID>", "at": "2026-11-08T12:00:00.000Z", "data": { "id": "…", "name": "…", "emails": [], "phones": [] } }. - Ziel: nur öffentliche
http(s)-Adressen. Interne Ziele (Heim- oder Firmennetz,localhost, Docker-Dienste, Cloud-Metadaten) lehnt das CRM beim Speichern ab und ruft sie beim Senden nicht auf – auch nicht nach einer Weiterleitung. Das gilt auch selbst gehostet. - Wiederholung: Bei
429,5xxoder Netzfehler bis zu 3 Versuche innerhalb weniger Sekunden – mit derselbenX-TheY-Delivery-ID, demselben Zeitstempel und derselben Signatur. Die ID eignet sich zum Erkennen doppelter Zustellungen. Dein Endpunkt sollte2xxzurückgeben.
Webhook-Signatur prüfen (seit 1.1.60)
Jede Sendung ist signiert. Damit prüft dein System, dass die Daten wirklich von The-Y CRM kommen und unterwegs nicht verändert wurden.
Geheimnis: Es wird beim Anlegen des Webhooks einmal angezeigt (whsec_…). Danach ist es nur noch verschlüsselt gespeichert und nirgends mehr sichtbar. Verloren? „Geheimnis erneuern“ – das alte gilt dann sofort nicht mehr.
| Kopf | Inhalt |
|---|---|
X-TheY-Timestamp | Zeitpunkt der Sendung in Sekunden seit 1970 (Unix-Zeit) |
X-TheY-Signature | v1=<hex> – HMAC-SHA256 mit dem Geheimnis über <X-TheY-Timestamp>.<Inhalt> (Zeitstempel, ein Punkt, der unveränderte Inhalt) |
X-TheY-Event | z. B. contact.created |
X-TheY-Delivery | eindeutige ID der Sendung (bleibt bei Wiederholungen gleich) |
X-KSCRM-Signature, X-KSCRM-Event | alt: HMAC-SHA256 nur über den Inhalt, ohne Zeitstempel. Wird noch mitgeschickt, damit bestehende Empfänger nicht brechen |
X-KSCRM-Signature fällt mit Version 1.2.0 weg. Prüfe bis dahin X-TheY-Signature wie unten.So prüfst du: Signatur selbst berechnen und in konstanter Zeit vergleichen, und Sendungen verwerfen, deren Zeitstempel mehr als 5 Minuten von deiner Uhr abweicht (Schutz gegen erneutes Einspielen abgefangener Sendungen). Wichtig: den Inhalt roh signieren, so wie er ankommt – nicht erst als JSON einlesen und wieder ausgeben.
# Node.js (Express)
import crypto from "node:crypto";
import express from "express";
const SECRET = process.env.THEY_WEBHOOK_SECRET; // whsec_…
const app = express();
app.post("/the-y/webhook", express.raw({ type: "application/json" }), (req, res) => {
const ts = Number(req.get("X-TheY-Timestamp"));
const sig = (req.get("X-TheY-Signature") || "").split(",").map((s) => s.trim()).find((s) => s.startsWith("v1="))?.slice(3) || "";
if (!Number.isInteger(ts) || Math.abs(Date.now() / 1000 - ts) > 300) return res.status(400).send("zu alt");
const want = crypto.createHmac("sha256", SECRET).update(`${ts}.${req.body.toString("utf8")}`).digest("hex");
const a = Buffer.from(sig, "utf8"), b = Buffer.from(want, "utf8");
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.status(401).send("Signatur falsch");
const event = JSON.parse(req.body.toString("utf8"));
// … event.event, event.data verarbeiten
res.sendStatus(204);
});
# PHP
<?php
$secret = getenv('THEY_WEBHOOK_SECRET'); // whsec_…
$body = file_get_contents('php://input');
$ts = (int)($_SERVER['HTTP_X_THEY_TIMESTAMP'] ?? 0);
$sigHdr = $_SERVER['HTTP_X_THEY_SIGNATURE'] ?? '';
$sig = '';
foreach (explode(',', $sigHdr) as $part) { $part = trim($part); if (str_starts_with($part, 'v1=')) $sig = substr($part, 3); }
if (abs(time() - $ts) > 300) { http_response_code(400); exit('zu alt'); }
$want = hash_hmac('sha256', $ts . '.' . $body, $secret);
if (!hash_equals($want, $sig)) { http_response_code(401); exit('Signatur falsch'); }
$event = json_decode($body, true);
// … $event['event'], $event['data'] verarbeiten
http_response_code(204);
Eingehende Webhooks
Externe Systeme können über eine Token-URL Daten in The-Y anlegen/aktualisieren: POST /api/hooks/<token> (Token in den Integrationen erzeugt). Je Webhook wählst du das Zugriffs-Level: upsert (nur anlegen/aktualisieren, Löschungen ignoriert — Anti-Lock-in) oder full (inkl. Löschen).
