cli.gs
Sign in

Conversions melden

Der Server-zu-Server-Postback, seine Felder und Antworten.

Mit dem Server-Postback meldet dein Shop cli.gs einen Verkauf. Auf dieser Seite lernst du, welche Anfrage du sendest, welche Felder sie hat und wie du die Antwort liest. Dein Server sendet pro Bestellung eine HTTPS-Anfrage mit dem gespeicherten ref-Code und den Bestelldaten. Das ist der einzige Kanal, der allein eine Provision bucht, deshalb braucht ihn jede Integration.

Kurz gesagt:

  • Sende POST https://cli.gs/api/v1/track/conversion von deinem Server mit deinem API-Schlüssel.
  • Sende immer ref, orderId und den Nettobetrag in amount. Ergänze currency und customerType, wo du kannst.
  • Meldungen sind idempotent: Dieselbe orderId zählt einmal, Wiederholungen sind also sicher.
  • 201 heißt erfasst, 200 heißt schon vorhanden. Bei 429 und 5xx wiederholst du, bei 400, 401 und 422 korrigierst du die Anfrage.

Die Anfrage

text
POST https://cli.gs/api/v1/track/conversion
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

Sende sie von deinem Server, nie aus dem Browser. Der beste Zeitpunkt ist, wenn die Bestellung bestätigt oder bezahlt ist. Schlägt die Anfrage mit einem Netzwerkfehler, Timeout, HTTP 5xx oder 429 fehl, wiederhole sie später. Eine wiederholte Meldung richtet keinen Schaden an.

Parameter und Codebeispiele auf der Integrationsseite
Der Abschnitt Parameter mit fertigem Code für dein Programm

Felder

FeldPflichtBedeutung
refjaDer Code aus ?ref= auf der Landingpage
orderIdjaDeine Bestellnummer, bis 190 Zeichen. Wiederholte Meldungen werden dadurch unschädlich
amountbei SALENetto-Warenwert nach Rabatt, ohne Steuer und Versand. basketValue wird auch akzeptiert
currencyempfohlenDreistelliger Code. Programme rechnen in EUR ab; fehlt das Feld, gilt die Währung des Programms
eventTypeneinSALE (Standard), LEAD oder SIGNUP
customerTypeneinNEW oder RETURNING, für Neukunden-Regeln
matchKeyneinProdukt- oder Kategorieschlüssel für Regeln mit Produktschlüssel. commissionGroup wird auch akzeptiert
occurredAtneinISO-8601-Zeitpunkt des Verkaufs mit Zeitzone, z. B. 2026-09-28T14:05:00Z
testneintrue erfasst eine Test-Conversion, die kein Geld bewegt

Details, die du kennen solltest

  • amount darf ein String oder eine Zahl sein. Verwende einen Punkt als Dezimaltrennzeichen ("149.99"). Wir lesen auch 149,99 und 1.234,56. Alles Mehrdeutige wie 1.234 lehnen wir ab, ebenso negative Werte und unplausibel große Warenkörbe. Bei LEAD und SIGNUP ist der Betrag optional.
  • currency ungleich EUR wird erfasst, aber zur Prüfung geparkt, weil Programme nur in EUR abrechnen.
  • customerType mit einem anderen Wert wird ignoriert. Er gilt nicht als RETURNING, ein Tippfehler kann die Neukunden-Provision eines Publishers also nicht senken.
  • occurredAt ist wichtig, wenn du spät meldest, etwa erst nach Zahlungseingang. Das Cookie-Fenster wird gegen diesen Zeitpunkt geprüft, nicht gegen den Eingang der Anfrage. Er darf nicht vor dem Klick und nicht in der Zukunft liegen. Fünf Minuten Uhrenabweichung werden toleriert.
  • test muss der JSON-Wert true sein, nicht 1 oder "true".

Idempotenz

Jede Bestellung zählt einmal. Kommt dieselbe orderId für denselben eventType erneut, wird nichts Neues angelegt. Die Antwort ist 200 mit "duplicate": true und der bereits vorhandenen Conversion. Du kannst also nach einem Timeout gefahrlos wiederholen oder die Meldung von zwei Stellen aus senden.

Ein Lead und ein Verkauf dürfen dieselbe Bestellnummer haben, etwa Anmeldung jetzt und Kauf später. Sie werden pro eventType getrennt gezählt.

Beispiele

cURL

bash
curl -X POST https://cli.gs/api/v1/track/conversion \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ref": "01J9Z4K6V0QX8M2N3P5R7S9T1WABCD",
    "orderId": "100234",
    "amount": "149.99",
    "currency": "EUR",
    "customerType": "NEW"
  }'

Node.js (fetch)

js
// Node 18+. Call this from your order-paid handler.
async function reportConversion(order) {
  const res = await fetch("https://cli.gs/api/v1/track/conversion", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.CLIGS_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      ref: order.cligsRef,              // saved with the order at checkout
      orderId: String(order.id),
      amount: order.netSubtotal.toFixed(2),
      currency: "EUR",
      customerType: order.isFirstOrder ? "NEW" : "RETURNING",
      occurredAt: order.createdAt.toISOString(),
    }),
    signal: AbortSignal.timeout(5000),
  });

  if (res.ok) return res.json();                           // 200 or 201
  if (res.status >= 500 || res.status === 429) throw new Error("retry later");
  const err = await res.json().catch(() => ({}));
  console.error("cli.gs refused the conversion", res.status, err.error); // do not retry unchanged
}

PHP (curl)

php
<?php
function cligs_report_conversion(array $data): array {
    $ch = curl_init('https://cli.gs/api/v1/track/conversion');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 5,
        CURLOPT_HTTPHEADER     => [
            'Authorization: Bearer ' . getenv('CLIGS_API_KEY'),
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS     => json_encode($data),
    ]);
    $body   = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    return ['status' => $status, 'body' => json_decode((string) $body, true)];
}

$result = cligs_report_conversion([
    'ref'      => $order['cligs_ref'],
    'orderId'  => (string) $order['id'],
    'amount'   => number_format($order['net_subtotal'], 2, '.', ''),
    'currency' => 'EUR',
]);

WooCommerce

Dieses Beispiel speichert den ref-Code beim Checkout an der Bestellung. Es meldet den Verkauf, sobald die Bestellung auf In Bearbeitung wechselt, also bezahlt ist. Es setzt das Cookie cligs_ref aus ref-Code erfassen voraus.

php
<?php
// 1. At checkout: copy the ref code from the cookie onto the order.
function cligs_save_ref_on_order($order) {
    $ref = $_COOKIE['cligs_ref'] ?? '';
    if ($ref !== '' && preg_match('/^[0-9A-Za-z]{10,64}$/', $ref)) {
        $order->update_meta_data('_cligs_ref', $ref);
    }
}
add_action('woocommerce_checkout_create_order', 'cligs_save_ref_on_order'); // classic checkout
add_action('woocommerce_store_api_checkout_update_order_from_request', function ($order) { // block checkout
    cligs_save_ref_on_order($order);
    $order->save();
});

// 2. When the order is paid: report it. Runs server-side, also for background payment callbacks.
add_action('woocommerce_order_status_processing', function ($order_id) {
    $order = wc_get_order($order_id);
    $ref   = $order ? $order->get_meta('_cligs_ref') : '';
    if (!$ref || $order->get_meta('_cligs_reported')) {
        return;
    }

    // Net subtotal after discount, excluding tax and shipping.
    $amount = (float) $order->get_subtotal() - (float) $order->get_discount_total();

    $response = wp_remote_post('https://cli.gs/api/v1/track/conversion', [
        'headers' => [
            'Authorization' => 'Bearer ' . CLIGS_API_KEY, // define in wp-config.php
            'Content-Type'  => 'application/json',
        ],
        'body'    => wp_json_encode([
            'ref'        => $ref,
            'orderId'    => (string) $order->get_id(),
            'amount'     => number_format(max(0, $amount), 2, '.', ''),
            'currency'   => $order->get_currency(),
            'occurredAt' => $order->get_date_created()->format(DATE_ATOM),
        ]),
        'timeout' => 5,
    ]);

    $code = wp_remote_retrieve_response_code($response);
    if ($code === 200 || $code === 201) {
        $order->update_meta_data('_cligs_reported', 1);
        $order->save();
    }
});

Das Flag _cligs_reported spart nur eine Anfrage; die Meldung selbst ist idempotent. Schlägt eine Anfrage fehl, meldest du die Bestellung später einfach erneut. Gut dafür ist ein geplanter Job, der bezahlte Bestellungen mit _cligs_ref, aber ohne _cligs_reported sucht.

Antworten

Eine erfolgreiche Meldung liefert die Conversion so zurück, wie wir sie erfasst haben:

json
{
  "ok": true,
  "conversionId": "cmg3x1q2w0001abcd9876efgh",
  "status": "PENDING",
  "commission": 12,
  "duplicate": false,
  "confirmed": false,
  "test": false
}

Was die Felder bedeuten

  • status ist PENDING, wenn die Provision gebucht ist und das Freigabefenster läuft. Er ist NEEDS_REVIEW, wenn die Conversion für deine Entscheidung geparkt ist, etwa wegen einer anderen Währung als EUR, ohne passende Provisionsregel, bei zu wenig Guthaben oder wegen Betrugssignalen. Er ist REJECTED bei einem Test oder bei einem Publisher, der nicht mehr aktiv ist.
  • commission ist die Provision des Publishers in der Programmwährung.
  • duplicate: true heißt: Diese Bestellung war schon erfasst. Nichts hat sich geändert.
  • confirmed: true heißt: Diese Meldung hat eine Conversion bestätigt, die das Tracking-Pixel für dieselbe Bestellung schon gemeldet hatte.

HTTP-Statuscodes

HTTPBodyWas tun
201ok: trueErfasst. Fertig
200ok: true, duplicate oder confirmedSchon erfasst oder Pixel-Conversion bestätigt. Fertig
400error: "invalid payload" mit issues, oder "invalid json body"Anfrage korrigieren (fehlende orderId, falscher eventType, test kein Boolean, …)
401z. B. "invalid api key", "invalid signature"Schlüssel und Signatur prüfen
403error: "REF_PROGRAM_MISMATCH"Der ref-Code gehört zu einem anderen Programm; nutze dessen Schlüssel
422error: "<CODE>"Die Meldung wurde verstanden, aber nicht angenommen. Unverändert wiederholen hilft nicht
429"rate limit exceeded"Mehr als 600 Anfragen pro Minute für diesen Schlüssel. Später wiederholen
5xxVorübergehendes Problem bei uns. Später wiederholen

Die 422-Codes (REF_UNKNOWN, REF_EXPIRED, INVALID_BASKET und weitere) sind mit Lösungen unter Testen und Diagnose erklärt.

Weiter

Schütze die Anfrage mit einer Signatur und richte danach Rückgaben und Stornos ein.