Sattelpass in Ihr System einbinden
Händler, Hersteller und Sattler arbeiten mit unterschiedlichen Systemen. Deshalb gibt es mehrere Wege zum Sattelpass, die alle denselben Kern nutzen: Ein per Schnittstelle angelegter Sattel ist vom Dashboard nicht zu unterscheiden, bekommt seinen Kurzcode für NFC-Chip und QR-Code und erscheint sofort in der Chronik. Ereignisse melden wir per Webhook zurück.
Zugang erhalten
API-Schlüssel und Webhook-Endpunkte richtet das Sattelpass-Team für Ihr Konto ein (Sattler- oder Herstellerkonto). Schreiben Sie kurz, welches System Sie anbinden möchten, an kontakt@sattelpass.com. Im Dashboard sehen Sie danach unter „Schnittstelle“ Ihre Schlüssel, Webhooks, Zustellungen und die letzten Anfragen. Die Nutzung ist im Tarif enthalten; per Schnittstelle angelegte Sättel zählen wie im Dashboard angelegte.
- Server-Schlüssel (sp_live_…): für CRM, Warenwirtschaft, Backend, Skripte. Niemals in Browser- oder App-Code einbauen.
- Testschlüssel (sp_test_…): für die Entwicklung. Prüft jede Anfrage wie ein echter Schlüssel, speichert aber nichts – siehe Testen.
- Einbett-Schlüssel (sp_embed_…): darf im Browser verwendet werden, ist an Ihre Domain(s) gebunden und kann nur Sättel anlegen und Auswahlwerte lesen.
REST-API
Basis-URL https://sattelpass.com/api/v1. Authentifizierung per Header Authorization: Bearer sp_live_… (alternativ X-Api-Key). Alle Antworten sind JSON (UTF-8). Fehler: { error: { code, message, details } }. Jede Antwort trägt X-Request-Id für Rückfragen.
# Sattel anlegen
curl -X POST https://sattelpass.com/api/v1/saddles \
-H "Authorization: Bearer sp_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: bestellung-48213" \
-d '{
"brand": "Musterbrand",
"model": "Elegance Dressage",
"seriennummer": "SP-2026-004812",
"seat_size": "17,5\"",
"kammerweite": "W3 / 32 cm",
"art": "Dressur",
"external_id": "ERP-48213",
"customer": { "email": "reiterin@example.com", "first_name": "Anna", "last_name": "Muster" },
"created_note": "Dieser Sattel wurde ab Werk im Sattelpass erfasst."
}'
# Antwort 201
{ "saddle": { "id": "9c1f…", "short_code": "K7PQ2XM9",
"pass_url": "https://sattelpass.com/s/K7PQ2XM9", "external_id": "ERP-48213", … } }
# Sattel über Ihre ID oder Seriennummer finden
curl "https://sattelpass.com/api/v1/saddles?external_id=ERP-48213" -H "Authorization: Bearer sp_live_…"
curl "https://sattelpass.com/api/v1/saddles?serial=SP-2026-004812" -H "Authorization: Bearer sp_live_…"
# Chronik-Eintrag (z. B. nach Anpassung)
curl -X POST https://sattelpass.com/api/v1/saddles/9c1f…/events \
-H "Authorization: Bearer sp_live_…" -H "Content-Type: application/json" \
-d '{ "event_type": "fitting", "title": "Anpassung", "public_note": "Kammerweite auf W4 angepasst." }'Endpunkte
| Methode | Pfad | Beschreibung | Recht |
|---|---|---|---|
| GET | /me | Konto und Schlüsselrechte | — |
| GET | /options | Auswahlwerte (Konfiguration) und Feldliste | options:read |
| POST | /saddles | Sattel anlegen (Kurzcode, erster Chronik-Eintrag) | saddles:write |
| POST | /saddles/batch | Bis zu 100 Sättel in einer Anfrage anlegen, Ergebnis je Sattel (z. B. eine Produktionscharge) | saddles:write |
| GET | /saddles | Eigene Sättel suchen: ?serial= ?external_id= ?short_code= ?q= ?updated_since= ?limit= ?offset= | saddles:read |
| GET | /saddles/{id} | Sattel lesen | saddles:read |
| POST | /saddles/claim | Sattel per Kurzcode übernehmen (Hersteller-Chip → eigenes Konto, Kunde zuordnen). Nur Sättel aus einem Herstellerkonto ohne Kunde, nur mit geheimem Schlüssel | saddles:write |
| PATCH | /saddles/{id} | Stammdaten ändern, Kunde zuordnen | saddles:write |
| GET | /saddles/{id}/events | Chronik lesen | events:read |
| GET | /events | Chronik-Einträge über alle eigenen Sättel: ?since= (neu seit diesem Zeitpunkt) ?limit= ?offset= | events:read |
| POST | /saddles/{id}/events | Chronik-Eintrag anlegen (verifizierte Konten) | events:write |
| PATCH | /saddles/{id}/events/{event_id} | Eigenen Chronik-Eintrag ändern | events:write |
| DELETE | /saddles/{id}/events/{event_id} | Eigenen Chronik-Eintrag entfernen (archivieren) | events:write |
| GET | /customers | Eigenen Kundenstamm lesen: ?email= ?external_id= ?q= ?updated_since= | customers:read |
| POST | /customers | Kunde anlegen oder aktualisieren (Abgleich über external_id, sonst E-Mail) | customers:write |
| PATCH | /customers/{id} | Kunde ändern | customers:write |
| GET | /public/saddles/{short_code} | Öffentlicher Sattelpass, wie beim Scan | — |
| GET | /webhooks | Eigene Webhook-Endpunkte und letzte Zustellungen | — |
| POST | /webhooks/test | Test-Zustellung an Ihre Endpunkte auslösen, wahlweise mit Beispiel eines Ereignisses | — |
Idempotenz: Senden Sie bei POST /saddles den Header Idempotency-Key (z. B. Ihre Belegnummer). Wiederholungen liefern dieselbe Antwort, ohne einen zweiten Sattel anzulegen. Zusätzlich verhindert external_id Doppelanlagen je Konto.
Limit: standardmäßig 120 Anfragen pro Minute je Schlüssel (Header X-RateLimit-Remaining). Für Massenimporte bitte kurz melden, wir erhöhen das Limit.
Mehrere Sättel auf einmal: POST /saddles/batch nimmt { saddles: [ … ] } mit bis zu 100 Einträgen in der Form von POST /saddles. Jeder Sattel wird für sich geprüft; die Antwort nennt je Eintrag created, exists (zu dieser external_id gibt es schon einen Sattel) oder error mit Grund. Dieselbe Anfrage lässt sich also gefahrlos wiederholen.
Abgleich ohne Webhooks: Kann Ihr System keine Webhooks empfangen, fragen Sie regelmäßig ab, was sich geändert hat: GET /saddles?updated_since=2026-11-01T00:00:00Z, GET /customers?updated_since=… und GET /events?since=…. Mit Zeitpunkt kommen die ältesten Änderungen zuerst; merken Sie sich den Zeitpunkt des letzten Eintrags und fragen Sie beim nächsten Mal ab dort. Blättern mit limit (höchstens 100) und offset.
Maschinenlesbare Beschreibung: OpenAPI 3.1 (JSON).
Testen ohne Risiko
Mit einem Testschlüssel (sp_test_…) entwickeln Sie gegen dieselbe Adresse und dieselben Regeln wie später im Betrieb. Lesen funktioniert wie gewohnt. Schreibvorgänge werden vollständig geprüft – Pflichtfelder, unbekannte Felder, doppelte external_id, Rechte – und mit einer Antwort in der echten Form beantwortet, aber nicht gespeichert: kein Sattel, kein Webhook, keine E-Mail.
- Simuliert werden POST /saddles, POST /saddles/batch, PATCH /saddles/{id}, POST /saddles/{id}/events und POST /brand/saddles/{id}/events. Andere Schreibvorgänge lehnt ein Testschlüssel mit TEST_NOT_SUPPORTED ab.
- Jede Antwort trägt den Header X-Sattelpass-Test: true, simulierte Antworten zusätzlich "test": true. Der Kurzcode eines simulierten Sattels beginnt mit TEST; einen Pass dazu gibt es nicht.
- Ihren Webhook-Empfänger prüfen Sie mit POST /webhooks/test. Das Ereignis heißt immer test.ping; mit { "event": "repair.opened" } liegt unter data.sample ein Beispiel in genau der Form, in der dieses Ereignis später ankommt.
Für Hersteller: Rückkanal nach dem Werkstor
Hersteller legen Sättel ab Werk an (einzeln, als Charge oder per Import). Übernimmt ein Händler den Sattel, gehört er zu dessen Konto und verschwindet aus GET /saddles. Unter /brand bleibt er für Sie erreichbar – mit genau den Angaben, die Sie auch im Dashboard sehen.
| GET | /brand/saddles | An Händler gelieferte Sättel mit Händler und Lieferdatum: ?delivered_since= ?limit= ?offset=. Mit ?serial= oder ?short_code= genau ein Sattel Ihrer Marke, auch wenn ihn ein anderer Betrieb angelegt hat | saddles:read |
| GET | /brand/saddles/{id} | Stammdaten und Ausstattung eines Sattels Ihrer Marke | saddles:read |
| GET | /brand/saddles/{id}/events | Ihre eigenen Chronik-Einträge; die vollständige Chronik, solange der Sattel bei Ihnen in Reparatur ist | events:read |
| POST | /brand/saddles/{id}/events | Werksbericht, Garantie- oder Reparaturnotiz in den Pass schreiben | events:write |
| GET | /brand/repairs | Reparatur-Eingänge: ?status=open|closed|all ?since= | saddles:read |
| POST | /brand/repairs/{id}/close | Reparatur abschließen | events:write |
- Was Sie sehen: an welchen Händler Sie wann geliefert haben, Stammdaten und Ausstattung, Ihre eigenen Chronik-Einträge, Reparatur-Eingänge und eine Diebstahlmeldung.
- Was beim Händler bleibt: Endkunde, Lagerstand, Verkaufsdatum und die Chronik-Einträge anderer Betriebe. Die vollständige Chronik sehen Sie nur, solange ein Händler den Sattel bei Ihnen zur Reparatur eingeschickt hat (visibility: "full").
- Für wen: jedes Herstellerkonto mit zugewiesener Marke. Andere Konten erhalten NOT_MANUFACTURER.
# Charge ab Werk anlegen
curl -X POST https://sattelpass.com/api/v1/saddles/batch \
-H "Authorization: Bearer sp_live_…" -H "Content-Type: application/json" \
-d '{ "saddles": [
{ "brand": "Musterbrand", "model": "Elegance", "seriennummer": "SP-2026-004812", "external_id": "ERP-48213" },
{ "brand": "Musterbrand", "model": "Elegance", "seriennummer": "SP-2026-004813", "external_id": "ERP-48214" } ] }'
# Antwort 200
{ "created": 2, "existing": 0, "failed": 0,
"results": [ { "index": 0, "status": "created", "external_id": "ERP-48213",
"saddle": { "id": "9c1f…", "short_code": "K7PQ2XM9", "pass_url": "…" } }, … ] }
# Was ist seit gestern an Händler gegangen?
curl "https://sattelpass.com/api/v1/brand/saddles?delivered_since=2026-11-10T00:00:00Z" -H "Authorization: Bearer sp_live_…"
# Offene Reparatur-Eingänge
curl "https://sattelpass.com/api/v1/brand/repairs" -H "Authorization: Bearer sp_live_…"
# Werksbericht in den Pass schreiben
curl -X POST https://sattelpass.com/api/v1/brand/saddles/9c1f…/events \
-H "Authorization: Bearer sp_live_…" -H "Content-Type: application/json" \
-d '{ "event_type": "repair", "title": "Sattelbaum getauscht", "public_note": "Werksreparatur, Baum erneuert." }'Felder eines Sattels
Pflicht sind nur brand (Marke) und model (Modell). Alle Werte sind Freitext; Hersteller können unter „Konfiguration“ Auswahlwerte hinterlegen, die GET /options liefert.
Fehlercodes
| MISSING_KEY / INVALID_KEY | 401 | Schlüssel fehlt, ist falsch oder widerrufen. |
| INSUFFICIENT_SCOPE | 403 | Schlüssel hat dieses Recht nicht (z. B. Einbett-Schlüssel schreibt keine Chronik). |
| ORIGIN_NOT_ALLOWED | 403 | Einbett-Schlüssel von einer nicht freigegebenen Domain verwendet. |
| NOT_VERIFIED | 403 | Konto noch nicht verifiziert – Chronik-Einträge erst danach. |
| NOT_FOUND | 404 | Sattel/Kunde nicht gefunden oder gehört nicht zu Ihrem Konto. |
| DUPLICATE_EXTERNAL_ID | 409 | Für diese external_id existiert bereits ein Sattel (details.saddle_id). |
| VALIDATION | 422 | Pflichtfeld fehlt oder Wert ungültig (details.field). |
| IDEMPOTENCY_MISMATCH | 422 | Gleicher Idempotency-Key mit anderen Daten. |
| NOT_MANUFACTURER | 403 | Endpunkt unter /brand mit einem Konto aufgerufen, das kein Herstellerkonto ist. |
| TEST_NOT_SUPPORTED | 403 | Diesen Schreibvorgang simuliert ein Testschlüssel nicht. |
| NO_WEBHOOK_ENDPOINT | 409 | Test-Zustellung angefordert, aber für das Konto ist kein Webhook-Endpunkt hinterlegt. |
| RATE_LIMITED | 429 | Anfragen-Limit erreicht; Retry-After beachten. |
Webhooks (Rückkanal)
Sattelpass ruft Ihre HTTPS-Adresse bei Ereignissen auf – auch dann, wenn die Änderung im Dashboard, in der App oder per Import erfolgte. Ihr System antwortet mit einem 2xx-Status innerhalb von 10 Sekunden; andernfalls wiederholen wir die Zustellung mit wachsendem Abstand (1 Min, 5 Min, 30 Min, 2 Std, 12 Std). Zustellungen und Fehler sehen Sie im Dashboard unter „Schnittstelle“, wo Sie auch ein Testereignis auslösen können.
| saddle.created | Neuer Sattel – egal ob per API, Dashboard, App, Import oder Einbett-Formular. |
| saddle.updated | Stammdaten geändert (auch NFC-Chip beschrieben). |
| saddle.owner_changed | Kunde/Besitzer zugeordnet oder geändert. |
| transfer.accepted | Übergabe an einen neuen Besitzer angenommen. |
| saddle.stolen_reported / saddle.stolen_cleared | Diebstahl gemeldet bzw. Meldung aufgehoben. |
| event.created | Neuer Chronik-Eintrag (Arbeitsauftrag, Kontrolle, Anpassung …). |
| saddle.transferred | Hersteller: Ein Händler hat den Sattel übernommen (ohne Kundendaten des Händlers). |
| repair.opened / repair.closed | Hersteller: Ein Händler hat einen Sattel Ihrer Marke zur Reparatur eingeschickt bzw. die Reparatur ist abgeschlossen (der Abschluss geht auch an den Händler). |
| brand.event.created | Hersteller: Neuer Chronik-Eintrag an einem Sattel, der bei Ihnen in Reparatur ist. |
| brand.saddle.stolen_reported / brand.saddle.stolen_cleared | Hersteller: Ein Sattel Ihrer Marke wurde gestohlen gemeldet bzw. die Meldung aufgehoben. |
| test.ping | Testereignis aus dem Dashboard oder per POST /webhooks/test. |
POST https://crm.beispiel.de/hooks/sattelpass
Content-Type: application/json
User-Agent: Sattelpass-Webhooks/1.0
X-Sattelpass-Event: saddle.created
X-Sattelpass-Delivery: 7c0e… (eindeutige ID der Zustellung – für Duplikat-Erkennung)
X-Sattelpass-Timestamp: 1789487011 (Unix-Sekunden)
X-Sattelpass-Signature: v1=9f3a… (hex HMAC-SHA256(secret, timestamp + "." + body))
{ "id": "7c0e…", "event": "saddle.created", "created_at": "2026-09-15T09:03:11Z", "attempt": 1,
"api_version": "v1",
"data": { "saddle": { "id": "9c1f…", "short_code": "K7PQ2XM9", "pass_url": "https://sattelpass.com/s/K7PQ2XM9",
"external_id": "ERP-48213", "created_via": "api", "brand": "Musterbrand", … } } }Signatur prüfen (Node 18+, Deno, Cloudflare, Supabase Edge Functions – der Rohtext der Anfrage muss unverändert verwendet werden):
import { verifyWebhookSignature } from "https://sattelpass.com/sdk/sattelpass-api.js";
export async function handler(req) {
const rawBody = await req.text();
const ok = await verifyWebhookSignature({
secret: process.env.SATTELPASS_WEBHOOK_SECRET, // whsec_… aus dem Dashboard
timestamp: req.headers.get("x-sattelpass-timestamp"),
signature: req.headers.get("x-sattelpass-signature"),
rawBody,
});
if (!ok) return new Response("invalid signature", { status: 401 });
const delivery = JSON.parse(rawBody);
// … delivery.event, delivery.data.saddle …
return new Response("ok");
}
// PHP: hash_equals("v1=".hash_hmac("sha256", $ts.".".$raw, $secret), $sig)
// Python: hmac.compare_digest("v1="+hmac.new(secret, f"{ts}.{raw}".encode(), "sha256").hexdigest(), sig)Einbett-Formular „Sattel anlegen“
Für Webseiten, Web-Apps und Apps ohne eigenes Backend: ein Script-Tag genügt. Das Formular läuft auf sattelpass.com, kennt die Auswahlwerte Ihres Kontos und meldet den angelegten Sattel an Ihre Seite zurück. Der Einbett-Schlüssel ist an Ihre Domain gebunden.
<div id="sattelpass"></div>
<script src="https://sattelpass.com/embed/sattelpass.js"></script>
<script>
Sattelpass.mount({
el: "#sattelpass",
key: "sp_embed_…",
prefill: { brand: "Musterbrand", model: "Elegance", seriennummer: "SP-2026-0001" },
externalId: "ERP-48213",
customer: true, // Kundenfelder anzeigen
onCreated: (saddle) => console.log(saddle.short_code, saddle.pass_url),
onError: (message) => console.warn(message)
});
</script>In React (auch Capacitor-Apps):
import { useEffect, useRef } from "react";
export function SattelpassCreate({ prefill, onCreated }) {
const ref = useRef(null);
useEffect(() => {
const s = document.createElement("script");
s.src = "https://sattelpass.com/embed/sattelpass.js";
let widget;
s.onload = () => {
widget = window.Sattelpass.mount({ el: ref.current, key: import.meta.env.VITE_SATTELPASS_EMBED_KEY, prefill, onCreated });
};
document.body.appendChild(s);
return () => { widget?.destroy(); s.remove(); };
}, []);
return <div ref={ref} />;
}Ohne Script-Tag: iframe auf https://sattelpass.com/embed/sattel-anlegen?key=sp_embed_…&brand=… und auf Nachrichten { source: "sattelpass", type: "sattelpass:saddle-created", saddle } hören. Mit demselben Schlüssel kann eine Web-App die API auch direkt aufrufen (nur POST /saddles und GET /options).
SDK (JavaScript/TypeScript)
Ein kleiner Client ohne Abhängigkeiten, mit Typdefinitionen: sattelpass-api.js · sattelpass-api.d.ts.
import { SattelpassClient } from "https://sattelpass.com/sdk/sattelpass-api.js";
const sp = new SattelpassClient({ apiKey: process.env.SATTELPASS_API_KEY });
const existing = await sp.saddles.findByExternalId("ERP-48213");
const { saddle } = existing
? { saddle: existing }
: await sp.saddles.create({ brand: "Musterbrand", model: "Elegance", external_id: "ERP-48213" },
{ idempotencyKey: "ERP-48213" });
await sp.events.create(saddle.id, { event_type: "check", title: "Kontrolle", public_note: "Alles in Ordnung." });In einer Supabase Edge Function (Deno) funktioniert derselbe Import; der Server-Schlüssel bleibt dort als Secret und gelangt nie in die App.
Tiefen-Link ins Dashboard
Der einfachste Weg für Systeme, die nur eine Adresse öffnen können: Das Sattelpass-Dashboard öffnet das Formular „Sattel anlegen“ vorbefüllt. Nach dem Anlegen führt ein Button zurück in Ihr System – mit Kurzcode, Sattelpass-Adresse und ID als Parameter (sattelpass_code, sattelpass_url, sattelpass_id).
https://sattelpass.com/dashboard?section=saddles&neu=1
&brand=Musterbrand&model=Elegance&seriennummer=SP-2026-0001
&external_id=ERP-48213
&return_url=https%3A%2F%2Fcrm.beispiel.de%2Fauftrag%2F48213
// oder mit dem Embed-Script: Sattelpass.dashboardLink({ brand: "…", model: "…" }, { returnUrl: "https://…" })CSV/XLSX-Import ohne Programmierung
Listen aus Warenwirtschaft oder Produktion laden Sie im Dashboard unter „Werksimport“ hoch. Spalten werden automatisch erkannt, jeder Sattel bekommt seinen Kurzcode, die Codes lassen sich als CSV zurück exportieren. Auch importierte Sättel lösen Webhooks aus.