Public REST API
Konkrete cURL-, Node.js-, Python- und PHP-Beispiele für die Server-zu-Server-API. Für Architektur und Schema siehe /documentation#api.
Niemals API-Keys im Browser oder einer Mobile-App verwenden. Die API ist ausschließlich für Server-zu-Server-Aufrufe gebaut — sie setzt keine CORS-Header und prüft keinen Origin. Ein API-Key, den du im Frontend einbettest, ist sofort kompromittiert.
Wichtig: In den Beispielen unten findest du Demo-Werte wie kunde@example.com und +49 30 99999999 (DRA-Reservebereich). Verwende dort niemals echte Endkunden-Daten zum Ausprobieren — produziere die Test-Requests immer mit Test-Endkunden-Profilen oder anonymen Werten.
Schnellstart in 4 Schritten
So bringst du deinen ersten erfolgreichen Request in unter 5 Minuten ans Laufen:
- 1. API-Key in der App erzeugen
Öffne /app/settings/api in deinem ShopiPixel-Dashboard, wähle die nötigen Berechtigungen (events:read / events:write / stats:read sind alle vorausgewählt) und klicke auf „Key erzeugen“. Den angezeigten Key sofort kopieren — er wird nur einmal im Klartext gezeigt. - 2. Bearer-Header setzen
Schicke den Key bei jedem Request im Authorization-Header: Authorization: Bearer sp_xxxxxxxxxxxx - 3. Endpoint aufrufen
Drei Endpoints stehen zur Verfügung — GET /api/v1/events (Events lesen), POST /api/v1/events (Events erstellen, Single oder Bulk), GET /api/v1/stats (aggregierte Statistiken). - 4. Test-Request laufen lassen
Kopiere eines der cURL-Beispiele unten und ersetze den Platzhalter sp_xxxxxxxxxxxx durch deinen Key. Bei erfolgreichem 2xx-Response loggt der Server bereits einen Read-Audit-Eintrag (DSGVO Art. 5 Abs. 2).
GET /api/v1/events — Events lesen
Liefert deine letzten Events mit Cursor-Pagination. Filter-Parameter: limit (1-200, Default 50), eventName, from/to (ISO-Datum), source (PIXEL | WEBHOOK | API | MERGED). Klartext-PII (E-Mail, Telefon, Vor-/Nachname) und Click-IDs werden bewusst nicht zurückgegeben — das customData-Objekt wird hingegen unverändert zurückgegeben (1:1 wie beim POST gesendet). Sende daher beim POST keine Endkunden-PII in customData.
curl -X GET "https://app.shopipixel.de/api/v1/events?limit=50&eventName=Purchase" \-H "Authorization: Bearer sp_xxxxxxxxxxxx"
Der Cursor in pagination.nextCursor ist opaque — speichere ihn so wie er kommt und reiche ihn beim nächsten Request unverändert weiter.
POST /api/v1/events — Single Event
Sendet ein einzelnes Event. Idempotenz erfolgt über das Feld eventId — verwende einen stabilen Wert wie purchase_<orderId>. Wird derselbe eventId binnen 20 Minuten erneut gesendet, antwortet der Server mit success: true, duplicate: true ohne erneuten Stats-Increment.
curl -X POST https://app.shopipixel.de/api/v1/events \-H "Authorization: Bearer sp_xxxxxxxxxxxx" \-H "Content-Type: application/json" \-d '{"eventName": "Purchase","eventId": "purchase_1730000000_abc","eventTime": 1730000000,"userData": {"email": "kunde@example.com","phone": "+493099999999"},"customData": {"currency": "EUR","value": 49.99,"orderId": "ORDER-123","contents": [{ "id": "prod-1", "quantity": 1, "itemPrice": 49.99 }]}}'
userData (E-Mail, Telefon) wird vom Server pro Plattform gehashed — du sendest Klartext, der Server normalisiert und hashed nach Plattform-Vertrag. Eigenes SHA256-Hashing ist nicht nötig und führt häufig zu falschem Format.
POST /api/v1/events — Bulk-Insert
Sende ein Array von bis zu 100 Events. Jedes Event wird einzeln verarbeitet — erfolgreiche und fehlgeschlagene Einträge werden im results-Array gemeldet (Partial Success).
curl -X POST https://app.shopipixel.de/api/v1/events \-H "Authorization: Bearer sp_xxxxxxxxxxxx" \-H "Content-Type: application/json" \-d '[{ "eventName": "PageView", "eventId": "pv_001", "eventTime": 1730000000 },{ "eventName": "ViewContent", "eventId": "vc_001", "eventTime": 1730000010, "customData": { "contentIds": ["prod-1"] } },{ "eventName": "AddToCart", "eventId": "atc_001", "eventTime": 1730000020, "customData": { "value": 49.99, "currency": "EUR", "contentIds": ["prod-1"] } },{ "eventName": "InitiateCheckout", "eventId": "ic_001", "eventTime": 1730000030, "customData": { "value": 49.99, "currency": "EUR" } },{ "eventName": "Purchase", "eventId": "p_001", "eventTime": 1730000040, "customData": { "value": 49.99, "currency": "EUR", "orderId": "ORDER-123" } }]'
Beispiel-Response: 4 erfolgreiche Events, 1 fehlgeschlagenes (event_not_found bei nicht-definiertem Custom Event). Erfolgreiche Events behalten ihren Status auch wenn andere im Batch fehlschlagen.
Pagination-Loop für GET /api/v1/events: solange pagination.hasMore true ist, den nextCursor an den Folge-Request weitergeben. 429-Antworten respektieren (Retry-After-Header) und Exponential Backoff bei wiederholten Limits einsetzen.
async function fetchAllEvents(eventName) {const allEvents = [];let cursor = null;do {const params = new URLSearchParams({ limit: "200", eventName });if (cursor) params.set("cursor", cursor);const response = await fetch(`https://app.shopipixel.de/api/v1/events?${params}`,{ headers: { "Authorization": "Bearer sp_xxxxxxxxxxxx" } },);if (response.status === 429) {const retryAfter = Number(response.headers.get("Retry-After") ?? 60);await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));continue;}if (!response.ok) {throw new Error(`API error: ${response.status}`);}const { data, pagination } = await response.json();allEvents.push(...data);cursor = pagination.hasMore ? pagination.nextCursor : null;} while (cursor);return allEvents;}const purchases = await fetchAllEvents("Purchase");console.log(`Insgesamt ${purchases.length} Purchase-Events geladen`);
GET /api/v1/stats — Statistiken
Aggregierte Kennzahlen: Umsatz, Conversion-Rate, Erfolgsraten pro Plattform. Granularity day | week | month | hour. Bei hour ist der Zeitraum auf 7 Tage limitiert.
curl -X GET "https://app.shopipixel.de/api/v1/stats?granularity=day&from=2026-04-01&to=2026-05-01" \-H "Authorization: Bearer sp_xxxxxxxxxxxx"
Multi-Currency wird automatisch in die Shop-Currency konvertiert (ECB-Tageskurs). Das Feld currency in der Response zeigt die Ziel-Currency.
Best Practices
- Idempotenz über eventId: verwende stabile Werte wie purchase_
, lead_ . Replays und Retries werden silent dedupliziert — gleiche eventId binnen 20 Min wirkt wie einmal gesendet. - Dedup-Fenster: 20 Minuten. Innerhalb dieses Fensters werden Events mit identischer eventId (oder fingerprint-äquivalente Events) als Duplikate erkannt. Response: success: true, duplicate: true, kein Stats-Increment, kein Plattform-Send.
- Retry-Strategie bei 429: respektiere den Retry-After-Header (in Sekunden). Bei wiederholten Limit-Hits Exponential Backoff verwenden — z.B. 1s, 2s, 4s, 8s, 16s mit Jitter.
- Bulk-Größe: maximal 100 Events pro Request, optimal sind ~50 für Latenz. Bei sehr großen Imports lieber sequenzielle 50er-Batches als zwei parallele 100er — der Worker verarbeitet pro Job atomar.
- eventTime-Window: Server akzeptiert nur eventTime im Bereich [now-86400, now+300] Sekunden. Älter als 24h oder weiter als 5min in der Zukunft → per-event validation_failed im Bulk-Result.
- PII-Hashing: Sende Plain-Werte (email: "kunde@example.com", phone: "+493099999999") — der Server hashed pro Plattform mit dem korrekten Algorithmus (z.B. Microsofts Gmail-Alias-Stripping, Metas Lowercase-Trim). Eigenes Hashing führt häufig zu falschem Format.
Fehler-Diagnose
- 401 unauthorized
- Authorization-Header fehlt, ist falsch formatiert oder enthält einen ungültigen, abgelaufenen oder widerrufenen Key. Prüfe in /app/settings/api ob der Key noch aktiv ist und das Format Authorization: Bearer sp_… stimmt (Bearer-Wort + Leerzeichen + Key).
- 403 Permission … required
- Der Key hat nicht die nötige Berechtigung. Beispiel: GET /api/v1/events erfordert events:read. In /app/settings/api die Berechtigungen prüfen oder einen neuen Key mit den nötigen Scopes erzeugen.
- 403 Enterprise plan required
- Der Shop ist nicht im Enterprise-Plan oder die Subscription ist nicht ACTIVE. Prüfe im Billing-Bereich den Plan-Status. Tester- und Dev-Stores haben einen Bypass.
- 413 Request body too large
- Der Request-Body überschreitet 512 KB. Bei Bulk-Requests: weniger Events pro Batch (≤50 statt 100), weniger umfangreiche customData-Objekte oder die Daten in mehrere Requests aufsplitten.
- 429 Rate limit exceeded
- Pro API-Key sind 1.000 Requests/h erlaubt. Der Retry-After-Header gibt die Wartezeit in Sekunden an. Bei häufigen 429: weniger Single-POSTs senden, stattdessen Bulk-Insert nutzen.
- event_not_found in Bulk-Response
- Du hast einen Custom-Event-Namen verwendet der noch nicht in /app/event-triggers/events angelegt ist. Lege das Custom Event in der App an (mit isActive: true) und sende den Request erneut.
- validation_failed wegen eventTime
- eventTime liegt außerhalb des erlaubten Fensters [now-86400, now+300] Sekunden. Sende Events innerhalb von 24h nach dem tatsächlichen Auftreten — historische Backfills werden nicht unterstützt.