Zum Inhalt springen

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.

Nur für Server-zu-Server

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.

Demo-Werte in den Beispielen

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. 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. 2. Bearer-Header setzen
    Schicke den Key bei jedem Request im Authorization-Header: Authorization: Bearer sp_xxxxxxxxxxxx
  3. 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. 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 kurz darauf 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 zum tagesaktuellen Wechselkurs in die Shop-Currency umgerechnet. 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 — eine kurz darauf erneut gesendete eventId wirkt wie einmal gesendet.
  • Deduplizierung: Events mit identischer eventId werden innerhalb eines kurzen Zeitfensters automatisch 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 — das ist am zuverlässigsten.
  • 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 normalisiert und hasht die Daten automatisch im jeweils von der Plattform geforderten Format. Eigenes Hashing ist nicht nötig und 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.

Siehe auch