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/conversionvon deinem Server mit deinem API-Schlüssel. - Sende immer
ref,orderIdund den Nettobetrag inamount. ErgänzecurrencyundcustomerType, wo du kannst. - Meldungen sind idempotent: Dieselbe
orderIdzählt einmal, Wiederholungen sind also sicher. 201heißt erfasst,200heißt schon vorhanden. Bei429und5xxwiederholst du, bei400,401und422korrigierst du die Anfrage.
Die Anfrage
POST https://cli.gs/api/v1/track/conversion
Authorization: Bearer YOUR_API_KEY
Content-Type: application/jsonSende 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.

Felder
| Feld | Pflicht | Bedeutung |
|---|---|---|
ref | ja | Der Code aus ?ref= auf der Landingpage |
orderId | ja | Deine Bestellnummer, bis 190 Zeichen. Wiederholte Meldungen werden dadurch unschädlich |
amount | bei SALE | Netto-Warenwert nach Rabatt, ohne Steuer und Versand. basketValue wird auch akzeptiert |
currency | empfohlen | Dreistelliger Code. Programme rechnen in EUR ab; fehlt das Feld, gilt die Währung des Programms |
eventType | nein | SALE (Standard), LEAD oder SIGNUP |
customerType | nein | NEW oder RETURNING, für Neukunden-Regeln |
matchKey | nein | Produkt- oder Kategorieschlüssel für Regeln mit Produktschlüssel. commissionGroup wird auch akzeptiert |
occurredAt | nein | ISO-8601-Zeitpunkt des Verkaufs mit Zeitzone, z. B. 2026-09-28T14:05:00Z |
test | nein | true 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 auch149,99und1.234,56. Alles Mehrdeutige wie1.234lehnen wir ab, ebenso negative Werte und unplausibel große Warenkörbe. BeiLEADundSIGNUPist 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
truesein, nicht1oder"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
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)
// 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
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
// 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:
{
"ok": true,
"conversionId": "cmg3x1q2w0001abcd9876efgh",
"status": "PENDING",
"commission": 12,
"duplicate": false,
"confirmed": false,
"test": false
}Was die Felder bedeuten
statusistPENDING, wenn die Provision gebucht ist und das Freigabefenster läuft. Er istNEEDS_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 istREJECTEDbei einem Test oder bei einem Publisher, der nicht mehr aktiv ist.commissionist die Provision des Publishers in der Programmwährung.duplicate: trueheißt: Diese Bestellung war schon erfasst. Nichts hat sich geändert.confirmed: trueheißt: Diese Meldung hat eine Conversion bestätigt, die das Tracking-Pixel für dieselbe Bestellung schon gemeldet hatte.
HTTP-Statuscodes
| HTTP | Body | Was tun |
|---|---|---|
| 201 | ok: true | Erfasst. Fertig |
| 200 | ok: true, duplicate oder confirmed | Schon erfasst oder Pixel-Conversion bestätigt. Fertig |
| 400 | error: "invalid payload" mit issues, oder "invalid json body" | Anfrage korrigieren (fehlende orderId, falscher eventType, test kein Boolean, …) |
| 401 | z. B. "invalid api key", "invalid signature" | Schlüssel und Signatur prüfen |
| 403 | error: "REF_PROGRAM_MISMATCH" | Der ref-Code gehört zu einem anderen Programm; nutze dessen Schlüssel |
| 422 | error: "<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 |
| 5xx | Vorü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.