Zum Inhalt springen

Entwickler

Die colly API

Eine REST-API für beide Seiten deines Partnerprogramms: Advertiser geben Conversions frei und lesen Partner, Affiliates erstellen Trackinglinks und lesen Guthaben – mit denselben Rechten wie im Dashboard.

Schnellstart

In unter fünf Minuten zur ersten Antwort.

  1. Schritt 1: Anmelden

    Mit deinem Advertiser- oder Affiliate-Konto. API-Schlüssel gehören immer zu deiner Organisation.

  2. Schritt 2: API-Schlüssel erstellen

    In der App unter Integration → API-Schlüssel. Kopiere das API-Token sofort – es wird nur einmal angezeigt.

  3. Schritt 3: Erste Anfrage senden

    Rufe GET /me mit dem Token auf. Die Antwort zeigt deine Organisation und alle Operationen, die der Schlüssel aufrufen darf.

Anfrage
curl https://joincolly.com/api/v1/me \  -H "Authorization: Bearer pk_….sk_…"
Antwort200 OK
{  "data": {    "object": "api_key",    "organization": {      "id": "cm3org00001",      "name": "Example Media",      "type": "affiliate",      "status": "active"    },    "key": {      "id": "pk_Q2x0b3J5LmV4YW1w",      "label": "Reporting server",      "created_at": "2026-10-30T11:00:00.000Z",      "last_used_at": "2026-10-30T11:05:12.000Z"    },    "rate_limit": {      "limit": 600,      "window_seconds": 60    },    "operations": [      "getMe",      "listOffers",      "getOffer",      "listConversions",      "getConversion",      "listLinks",      "createLink",      "getBalance",      "listPayouts",      "getStats"    ],    "api_version": "v1"  }}

Authentifizierung

Sende das API-Token als Bearer-Token: Authorization: Bearer pk_….sk_…. Das Token besteht aus der öffentlichen Key-ID (pk_…) und dem Secret (sk_…), verbunden durch einen Punkt.

Ein Schlüssel handelt für genau eine Organisation und hat die Rechte ihres Typs: Advertiser-Schlüssel sehen eigene Angebote, Conversions und Partner; Affiliate-Schlüssel sehen Katalog, Links, Conversions, Guthaben und Auszahlungen. Anfragen außerhalb dieses Bereichs liefern 404 – die Existenz wird nie preisgegeben.

Tokens nur auf Servern verwenden, nie im Browser oder in Apps. Widerrufene Schlüssel sind sofort ungültig. Jede Änderung wird mit dem Schlüssel im Audit-Log protokolliert.

Authentifizierte Anfrage
GET /api/v1/conversions?status=pending&limit=50 HTTP/1.1Host: joincolly.comAuthorization: Bearer pk_….sk_…Accept: application/json
Endpunkte je Schlüsseltyp
EndpunktAdvertiserAffiliate
GET /me✓✓
GET /offers✓✓
GET /offers/{id}✓✓
GET /conversions✓✓
GET /conversions/{id}✓✓
POST /conversions/{id}/approve✓–
POST /conversions/{id}/reject✓–
POST /conversions/{id}/reverse✓–
GET /partners✓–
GET /links–✓
POST /links–✓
GET /balance–✓
GET /payouts–✓
GET /stats✓✓

Konventionen

Durchgängig JSON
Antworten sind JSON mit Feldern in snake_case. Einzelobjekte stehen in data, Listen in data plus next_cursor. Enum-Werte sind kleingeschrieben.
Exakte Beträge
Beträge sind Dezimal-Strings mit zwei Nachkommastellen plus ISO-Währung ("49.90", "EUR") – nie Gleitkommazahlen.
Zeitpunkte
ISO 8601 in UTC. Filter akzeptieren YYYY-MM-DD (ganzer UTC-Tag) oder einen Zeitpunkt mit Offset; Statistiken akzeptieren eine IANA-Zeitzone.
Stabile v1
Innerhalb von /api/v1 kommen nur Felder und Endpunkte hinzu. Ignoriere unbekannte Felder; inkompatible Änderungen erhalten eine neue Version.

Paginierung

Listen sind absteigend nach Zeit sortiert und werden per Cursor geblättert: limit (Standard 25, max. 100) und cursor. Gib next_cursor unverändert weiter, bis er null ist. Cursor sind stabil – neue Einträge verschieben keine Seiten.

Zwei Seiten
GET /api/v1/conversions?limit=25→ { "data": [ … ], "next_cursor": "WyIyMDI2LTEwLTI4VDA5OjIwOjAyLjAwMFoiLCJjbTNjb252MDAwMSJd" } GET /api/v1/conversions?limit=25&cursor=WyIyMDI2LTEwLTI4VDA5OjIwOjAyLjAwMFoiLCJjbTNjb252MDAwMSJd→ { "data": [ … ], "next_cursor": null }

Fehler

Fehler haben überall dieselbe Form: ein stabiler code für Programme, eine message für Menschen, optionale details (ungültige Felder, fachlicher Grund) und eine request_id für den Support.

Beispielfehler409
{  "error": {    "code": "invalid_state",    "message": "This status change is not allowed for the conversion.",    "details": {      "from": "APPROVED",      "to": "REJECTED",      "reason": "CONVERSION_TRANSITION_NOT_ALLOWED"    },    "request_id": "req_Jx2v9Qm4b7TnK1aZ"  }}
unauthorized
Token fehlt, ist fehlerhaft, unbekannt oder widerrufen (401).
forbidden
Endpunkt für diesen Schlüsseltyp nicht verfügbar oder Organisation nicht aktiv (403).
not_found
Ressource existiert nicht oder liegt außerhalb deiner Organisation (404).
validation_error
Ungültiger Parameter oder Body; details.fields nennt die Felder (400).
conflict
Die Ressource existiert bereits (409).
invalid_state
Statuswechsel nicht erlaubt, z. B. Storno einer offenen Conversion (409).
insufficient_balance
Das verfügbare Guthaben reicht nicht aus (422).
rate_limited
Zu viele Anfragen – Retry-After abwarten (429).
idempotency_key_reused
Der Idempotency-Key wurde bereits für eine andere Anfrage verwendet (422).
idempotency_in_progress
Eine Anfrage mit diesem Idempotency-Key läuft noch – gleich erneut versuchen (409).
payload_too_large
Request-Body größer als 16 KB (413).
unsupported_media_type
Bodies als application/json senden (415).
internal_error
Unerwarteter Fehler – mit demselben Idempotency-Key erneut senden (500).

Rate Limits

600 Anfragen pro 60 Sekunden je Schlüssel. Jede Antwort enthält RateLimit-*-Header; bei Überschreitung folgt 429 mit Retry-After.

Header
HTTP/1.1 200 OKRateLimit-Limit: 600RateLimit-Remaining: 597RateLimit-Reset: 42RateLimit-Policy: 600;w=60X-Request-Id: req_Jx2v9Qm4b7TnK1aZ HTTP/1.1 429 Too Many RequestsRetry-After: 42

Idempotenz

Schreibende Aufrufe (Freigabe, Ablehnung, Storno, Link erstellen) akzeptieren einen Idempotency-Key-Header. Wiederholst du eine Anfrage mit demselben Schlüssel innerhalb von 24 Stunden, erhältst du das Ergebnis der ersten Ausführung, statt doppelt zu buchen – sichere Wiederholungen nach Timeouts. Derselbe Schlüssel für eine andere Anfrage liefert 422.

Sichere Wiederholung
curl -X POST https://joincolly.com/api/v1/conversions/cm3conv0001/approve \  -H "Authorization: Bearer $API_TOKEN" \  -H "Idempotency-Key: 6f1c2a52-8f2e-4c3b-9b1a-2f0d5e7c9a41" # Wiederholung nach Timeout: gleicher Schlüssel → keine zweite FreigabeHTTP/1.1 200 OKIdempotent-Replayed: true

API-Referenz

Erzeugt aus derselben OpenAPI-3.1-Beschreibung, die du in Postman, Insomnia oder Codegeneratoren importieren kannst: /api/v1/openapi.json

https://joincolly.com/api/v1

Konto

get/meAdvertiserAffiliate

Aktueller API-Schlüssel

Organisation, Typ und Bezeichnung des Schlüssels, sein Rate Limit und die Operationen, die er aufrufen darf. Ideal als erste Anfrage, um ein Token zu prüfen.

Antwort200
{  "data": {    "object": "api_key",    "organization": {      "id": "cm3org00001",      "name": "Example Media",      "type": "affiliate",      "status": "active"    },    "key": {      "id": "pk_Q2x0b3J5LmV4YW1w",      "label": "Reporting server",      "created_at": "2026-10-30T11:00:00.000Z",      "last_used_at": "2026-10-30T11:05:12.000Z"    },    "rate_limit": {      "limit": 600,      "window_seconds": 60    },    "operations": [      "getMe",      "listOffers",      "getOffer",      "listConversions",      "getConversion",      "listLinks",      "createLink",      "getBalance",      "listPayouts",      "getStats"    ],    "api_version": "v1"  }}

Angebote

get/offersAdvertiserAffiliate

Angebote auflisten

Advertiser-Schlüssel: eigene Angebote inkl. Ziele und aktueller Standard-Provisionsversion. Affiliate-Schlüssel: der für dich sichtbare Katalog mit Zugriffsstatus und deiner gültigen Provision (individuell vor Standard).

Parameter
ParameterOrtBeschreibung
limitqueryquerySeitengröße (1–100, Standard 25).
cursorqueryqueryUndurchsichtiger Cursor aus next_cursor der vorherigen Seite.
statusqueryqueryNach Status filtern.
qqueryquerySuche im Namen (bei Affiliates auch in der Beschreibung).
categoryqueryqueryNur Affiliates: Kategorie-Schlüssel.
countryqueryqueryNur Affiliates: ISO-Ländercode (Angebote ohne Länderbeschränkung inklusive).
modelqueryqueryNur Affiliates: Provisionsmodell des Standardziels.
accessqueryqueryNur Affiliates: sofort verfügbar, Bewerbung nötig oder bereits beworben.
Antwort200
{  "data": [    {      "id": "cm3offer001",      "object": "offer",      "name": "Fitness App – Annual plan",      "description": "Annual subscription of a fitness app.",      "category": "apps",      "status": "active",      "visibility": "approval_required",      "currency": "EUR",      "destination_url": "https://shop.example.com/landing?cid={click_id}",      "preview_url": null,      "countries": [        "DE",        "AT"      ],      "allowed_traffic_sources": [        "content",        "social"      ],      "restrictions": null,      "attribution_window_hours": 720,      "daily_cap": null,      "monthly_cap": 500,      "goals": [        {          "id": "cm3goal0001",          "key": "sale",          "name": "Sale",          "is_default": true,          "commission": {            "version_id": "cm3ver00001",            "version": 2,            "model": "cpa",            "payout_amount": "12.00",            "payout_percent": null,            "cost_amount": "15.00",            "cost_percent": null,            "currency": "EUR",            "valid_from": "2026-09-01T00:00:00.000Z"          }        }      ],      "created_at": "2026-08-14T10:00:00.000Z",      "updated_at": "2026-09-01T08:30:00.000Z"    }  ],  "next_cursor": "WyIyMDI2LTA4LTE0VDEwOjAwOjAwLjAwMFoiLCJjbTNvZmZlcjAwMSJd"}
get/offers/{id}AdvertiserAffiliate

Angebot abrufen

Advertiser-Schlüssel erhalten je Ziel zusätzlich individual_commissions (aktuelle Versionen individueller Vereinbarungen). Affiliate-Schlüssel sehen nur Angebote aus ihrem Katalog.

Parameter
ParameterOrtBeschreibung
id*pathpathID der Ressource.
Antwort200
{  "data": {    "id": "cm3offer001",    "object": "offer",    "name": "Fitness App – Annual plan",    "description": "Annual subscription of a fitness app.",    "category": "apps",    "currency": "EUR",    "preview_url": null,    "countries": [      "DE",      "AT"    ],    "allowed_traffic_sources": [      "content",      "social"    ],    "restrictions": null,    "attribution_window_hours": 720,    "access": "available",    "goals": [      {        "key": "sale",        "name": "Sale",        "is_default": true,        "commission": {          "version_id": "cm3ver00001",          "model": "cpa",          "payout_amount": "12.00",          "payout_percent": null,          "currency": "EUR",          "individual": false        }      }    ],    "created_at": "2026-08-14T10:00:00.000Z"  }}

Conversions

get/conversionsAdvertiserAffiliate

Conversions auflisten

Neueste zuerst (nach occurred_at). Advertiser sehen Conversions auf ihre Angebote inkl. Affiliate und cost; Affiliates sehen eigene Conversions mit payout und Sub-IDs.

Parameter
ParameterOrtBeschreibung
limitqueryquerySeitengröße (1–100, Standard 25).
cursorqueryqueryUndurchsichtiger Cursor aus next_cursor der vorherigen Seite.
statusqueryqueryNach Status filtern.
offer_idqueryqueryNur dieses Angebot.
affiliate_idqueryqueryNur Advertiser: nach Affiliate filtern.
transaction_idqueryqueryExakte Transaktions-ID.
sub1queryqueryExakter sub1-Wert.
fromqueryqueryBeginn: YYYY-MM-DD oder ISO-8601-Zeitpunkt.
toqueryqueryEnde: YYYY-MM-DD (Tag inklusive) oder ISO-8601-Zeitpunkt (exklusiv).
Antwort200
{  "data": [    {      "id": "cm3conv0001",      "object": "conversion",      "status": "pending",      "status_reason": null,      "offer": {        "id": "cm3offer001",        "name": "Fitness App – Annual plan"      },      "goal": {        "key": "sale",        "name": "Sale"      },      "affiliate": {        "id": "cm3aff00001",        "name": "Example Media"      },      "transaction_id": "order-1001",      "click_id": "Zk3V2l0a9c1qY0m8xQwP7g",      "clicked_at": "2026-10-28T09:12:44.000Z",      "currency": "EUR",      "sale_amount": "49.90",      "payout": "12.00",      "cost": "15.00",      "commission_version_id": "cm3ver00001",      "country": "DE",      "device": "mobile",      "sub1": "newsletter",      "sub2": null,      "sub3": null,      "sub4": null,      "sub5": null,      "occurred_at": "2026-10-28T09:20:02.000Z",      "created_at": "2026-10-28T09:20:03.114Z",      "approved_at": null,      "rejected_at": null,      "reversed_at": null    }  ],  "next_cursor": null}
get/conversions/{id}AdvertiserAffiliate

Conversion abrufen

Liefert 404 für Conversions außerhalb deiner Organisation.

Parameter
ParameterOrtBeschreibung
id*pathpathID der Ressource.
Antwort200
{  "data": {    "id": "cm3conv0001",    "object": "conversion",    "status": "pending",    "status_reason": null,    "offer": {      "id": "cm3offer001",      "name": "Fitness App – Annual plan"    },    "goal": {      "key": "sale",      "name": "Sale"    },    "affiliate": {      "id": "cm3aff00001",      "name": "Example Media"    },    "transaction_id": "order-1001",    "click_id": "Zk3V2l0a9c1qY0m8xQwP7g",    "clicked_at": "2026-10-28T09:12:44.000Z",    "currency": "EUR",    "sale_amount": "49.90",    "payout": "12.00",    "cost": "15.00",    "commission_version_id": "cm3ver00001",    "country": "DE",    "device": "mobile",    "sub1": "newsletter",    "sub2": null,    "sub3": null,    "sub4": null,    "sub5": null,    "occurred_at": "2026-10-28T09:20:02.000Z",    "created_at": "2026-10-28T09:20:03.114Z",    "approved_at": null,    "rejected_at": null,    "reversed_at": null  }}
post/conversions/{id}/approveAdvertiser

Conversion freigeben

pending → approved. Verschiebt die Provision von offen in das verfügbare Guthaben des Affiliates.

Unterstützt den Idempotency-Key-Header.

Parameter
ParameterOrtBeschreibung
id*pathpathID der Ressource.
Idempotency-KeyheaderheaderEindeutiger Schlüssel je Vorgang (z. B. UUID). Wiederholungen mit demselben Schlüssel innerhalb von 24 h liefern das ursprüngliche Ergebnis, statt doppelt auszuführen.
Request-Body
{}
Antwort200
{  "data": {    "id": "cm3conv0001",    "object": "conversion",    "status": "approved",    "status_reason": null,    "offer": {      "id": "cm3offer001",      "name": "Fitness App – Annual plan"    },    "goal": {      "key": "sale",      "name": "Sale"    },    "affiliate": {      "id": "cm3aff00001",      "name": "Example Media"    },    "transaction_id": "order-1001",    "click_id": "Zk3V2l0a9c1qY0m8xQwP7g",    "clicked_at": "2026-10-28T09:12:44.000Z",    "currency": "EUR",    "sale_amount": "49.90",    "payout": "12.00",    "cost": "15.00",    "commission_version_id": "cm3ver00001",    "country": "DE",    "device": "mobile",    "sub1": "newsletter",    "sub2": null,    "sub3": null,    "sub4": null,    "sub5": null,    "occurred_at": "2026-10-28T09:20:02.000Z",    "created_at": "2026-10-28T09:20:03.114Z",    "approved_at": null,    "rejected_at": null,    "reversed_at": null  }}
post/conversions/{id}/rejectAdvertiser

Conversion ablehnen

pending → rejected. Erfordert reason (für den Affiliate sichtbar). Nicht umkehrbar.

Unterstützt den Idempotency-Key-Header.

Parameter
ParameterOrtBeschreibung
id*pathpathID der Ressource.
Idempotency-KeyheaderheaderEindeutiger Schlüssel je Vorgang (z. B. UUID). Wiederholungen mit demselben Schlüssel innerhalb von 24 h liefern das ursprüngliche Ergebnis, statt doppelt auszuführen.
Request-Body
{  "reason": "Order cancelled"}
Antwort200
{  "data": {    "id": "cm3conv0001",    "object": "conversion",    "status": "rejected",    "status_reason": "Order cancelled",    "offer": {      "id": "cm3offer001",      "name": "Fitness App – Annual plan"    },    "goal": {      "key": "sale",      "name": "Sale"    },    "affiliate": {      "id": "cm3aff00001",      "name": "Example Media"    },    "transaction_id": "order-1001",    "click_id": "Zk3V2l0a9c1qY0m8xQwP7g",    "clicked_at": "2026-10-28T09:12:44.000Z",    "currency": "EUR",    "sale_amount": "49.90",    "payout": "12.00",    "cost": "15.00",    "commission_version_id": "cm3ver00001",    "country": "DE",    "device": "mobile",    "sub1": "newsletter",    "sub2": null,    "sub3": null,    "sub4": null,    "sub5": null,    "occurred_at": "2026-10-28T09:20:02.000Z",    "created_at": "2026-10-28T09:20:03.114Z",    "approved_at": null,    "rejected_at": null,    "reversed_at": null  }}
post/conversions/{id}/reverseAdvertiser

Conversion stornieren

approved → reversed (Rückerstattung/Rückbuchung). Erfordert reason. Erzeugt eine Gegenbuchung; nicht umkehrbar.

Unterstützt den Idempotency-Key-Header.

Parameter
ParameterOrtBeschreibung
id*pathpathID der Ressource.
Idempotency-KeyheaderheaderEindeutiger Schlüssel je Vorgang (z. B. UUID). Wiederholungen mit demselben Schlüssel innerhalb von 24 h liefern das ursprüngliche Ergebnis, statt doppelt auszuführen.
Request-Body
{  "reason": "Refunded"}
Antwort200
{  "data": {    "id": "cm3conv0001",    "object": "conversion",    "status": "reversed",    "status_reason": "Refunded",    "offer": {      "id": "cm3offer001",      "name": "Fitness App – Annual plan"    },    "goal": {      "key": "sale",      "name": "Sale"    },    "affiliate": {      "id": "cm3aff00001",      "name": "Example Media"    },    "transaction_id": "order-1001",    "click_id": "Zk3V2l0a9c1qY0m8xQwP7g",    "clicked_at": "2026-10-28T09:12:44.000Z",    "currency": "EUR",    "sale_amount": "49.90",    "payout": "12.00",    "cost": "15.00",    "commission_version_id": "cm3ver00001",    "country": "DE",    "device": "mobile",    "sub1": "newsletter",    "sub2": null,    "sub3": null,    "sub4": null,    "sub5": null,    "occurred_at": "2026-10-28T09:20:02.000Z",    "created_at": "2026-10-28T09:20:03.114Z",    "approved_at": null,    "rejected_at": null,    "reversed_at": null  }}

Partner

get/partnersAdvertiser

Partner auflisten

Affiliates, die sich auf deine Angebote beworben oder darauf Conversions erzeugt haben, mit ihrem Bewerbungsstatus je Angebot.

Parameter
ParameterOrtBeschreibung
limitqueryquerySeitengröße (1–100, Standard 25).
cursorqueryqueryUndurchsichtiger Cursor aus next_cursor der vorherigen Seite.
offer_idqueryqueryNur dieses Angebot.
application_statusqueryqueryNur Partner mit einer Bewerbung in diesem Status.
Antwort200
{  "data": [    {      "id": "cm3aff00001",      "object": "partner",      "name": "Example Media",      "traffic_sources": [        "content",        "email"      ],      "applications": [        {          "offer": {            "id": "cm3offer001",            "name": "Fitness App – Annual plan"          },          "status": "approved",          "applied_at": "2026-09-02T10:00:00.000Z",          "decided_at": "2026-09-02T12:00:00.000Z"        }      ],      "created_at": "2026-08-20T07:00:00.000Z"    }  ],  "next_cursor": null}

Trackinglinks

Guthaben & Auszahlungen

get/balanceAffiliate

Guthaben

Guthaben je Währung und Topf aus dem unveränderlichen Buchungsjournal: pending (wartet auf Freigabe), available (auszahlbar), reserved (beantragte Auszahlungen), paid.

Antwort200
{  "data": [    {      "object": "balance",      "currency": "EUR",      "pending": "84.00",      "available": "312.50",      "reserved": "0.00",      "paid": "1250.00"    }  ]}
get/payoutsAffiliate

Auszahlungen auflisten

Deine Auszahlungsanträge, neueste zuerst. Zahlungsdetails nur maskiert.

Parameter
ParameterOrtBeschreibung
limitqueryquerySeitengröße (1–100, Standard 25).
cursorqueryqueryUndurchsichtiger Cursor aus next_cursor der vorherigen Seite.
statusqueryqueryNach Status filtern.
Antwort200
{  "data": [    {      "id": "cm3pay00001",      "object": "payout",      "status": "paid",      "amount": "250.00",      "currency": "EUR",      "method": "sepa",      "payout_details_masked": "••••••••4321",      "payment_reference": "TRX-2026-1031",      "note": null,      "requested_at": "2026-10-01T08:00:00.000Z",      "approved_at": "2026-10-02T09:00:00.000Z",      "paid_at": "2026-10-03T10:00:00.000Z",      "rejected_at": null    }  ],  "next_cursor": null}

Statistik

get/statsAdvertiserAffiliate

Statistik

Klicks, Conversions und Beträge für einen Zeitraum (max. 366 Tage, Standard: letzte 30 Tage), gruppiert nach Tag oder Angebot – dieselben Zahlen wie im Dashboard. Affiliates erhalten Provision und EPC, Advertiser ihre Kosten.

Parameter
ParameterOrtBeschreibung
fromqueryqueryBeginn: YYYY-MM-DD oder ISO-8601-Zeitpunkt.
toqueryqueryEnde: YYYY-MM-DD (Tag inklusive) oder ISO-8601-Zeitpunkt (exklusiv).
group_byqueryqueryGruppierung der rows: day oder offer.
timezonequeryqueryIANA-Zeitzone für Tagesgrenzen, z. B. Europe/Berlin.
offer_idqueryqueryNur dieses Angebot.
Antwort200
{  "data": {    "object": "stats",    "from": "2026-10-01",    "to": "2026-10-30",    "timezone": "Europe/Berlin",    "group_by": "day",    "totals": {      "clicks": 1840,      "conversions": 52,      "pending_conversions": 12,      "approved_conversions": 40,      "rejected_conversions": 3,      "reversed_conversions": 1,      "conversion_rate": 0.0283,      "amounts": [        {          "currency": "EUR",          "payout_pending": "144.00",          "payout_approved": "480.00",          "epc_approved": "0.2609"        }      ]    },    "rows": [      {        "date": "2026-10-01",        "clicks": 61,        "conversions": 2      }    ]  }}

Webhooks

Affiliates hinterlegen HTTPS-Endpunkte unter Einstellungen → Webhooks und erhalten Ereignisse in Echtzeit, statt abzufragen.

conversion.created
Dir wurde eine Conversion zugeordnet (Status pending).{"conversion_id":"cm3conv0001","offer_id":"cm3offer001","goal":"sale","status":"pending","payout":"12.00","currency":"EUR","sub1":"newsletter","sub2":null,"sub3":null,"sub4":null,"sub5":null,"occurred_at":"2026-10-28T09:20:02.000Z"}
conversion.approved
Der Advertiser hat eine Conversion freigegeben – die Provision ist verfügbar.{"conversion_id":"cm3conv0001","status":"approved","payout":"12.00","currency":"EUR","reason":null}
conversion.rejected
Eine offene Conversion wurde abgelehnt (mit Begründung).{"conversion_id":"cm3conv0001","status":"rejected","payout":"12.00","currency":"EUR","reason":"Order cancelled"}
conversion.reversed
Eine freigegebene Conversion wurde storniert, z. B. nach einer Rückerstattung.{"conversion_id":"cm3conv0001","status":"reversed","payout":"12.00","currency":"EUR","reason":"Refunded"}
payout.paid
Eine Auszahlung wurde bezahlt, inkl. Zahlungsreferenz.{"payout_id":"cm3pay00001","amount":"250.00","currency":"EUR","payment_reference":"TRX-2026-1031"}

Zustellungen sind signiert, brechen nach 5 Sekunden ab, folgen keinen Weiterleitungen und werden mit exponentiellem Backoff wiederholt (bis zu 8 Versuche). Antworte mit einem beliebigen 2xx. Nutze X-Colly-Delivery zur Deduplizierung und prüfe die Signatur über den unveränderten Rohtext.

Zustellung
POST /your/webhook HTTP/1.1Content-Type: application/jsonUser-Agent: colly-Webhooks/1.0X-Colly-Event: conversion.createdX-Colly-Delivery: cm3dlv00001X-Colly-Timestamp: 1793280480X-Colly-Signature: v1=7c1e…b42d {  "event": "conversion.created",  "created_at": "2026-10-28T09:20:03.200Z",  "data": {    "conversion_id": "cm3conv0001",    "offer_id": "cm3offer001",    "goal": "sale",    "status": "pending",    "payout": "12.00",    "currency": "EUR",    "sub1": "newsletter",    "sub2": null,    "sub3": null,    "sub4": null,    "sub5": null,    "occurred_at": "2026-10-28T09:20:02.000Z"  }}
Prüfen – Node.js
import crypto from "node:crypto"; // rawBody: der ungeparste Request-Body als Stringexport function verifyWebhook(rawBody, headers, secret) {  const ts = headers["x-colly-timestamp"] ?? "";  if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;  const expected = "v1=" + crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");  const got = headers["x-colly-signature"] ?? "";  return got.length === expected.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));}
Prüfen – Python
import hashlib, hmac, time def verify_webhook(raw_body: bytes, headers, secret: str) -> bool:    ts = headers.get("X-Colly-Timestamp", "")    if not ts.isdigit() or abs(time.time() - int(ts)) > 300:        return False    expected = "v1=" + hmac.new(secret.encode(), ts.encode() + b"." + raw_body, hashlib.sha256).hexdigest()    return hmac.compare_digest(expected, headers.get("X-Colly-Signature", ""))

Signierte Postbacks

Advertiser melden Conversions per Server-to-Server-Aufruf. Postbacks werden statt mit einem Bearer-Token mit Key-ID und Secret signiert (HMAC-SHA256) – das schützt jedes einzelne Ereignis vor Manipulation und Wiederholung.

  • X-Colly-Key – deine Key-ID (pk_…)
  • X-Colly-Timestamp – Unix-Zeit in Sekunden, höchstens 5 Minuten Abweichung
  • X-Colly-Signature – v1=hex(HMAC_SHA256(secret, "{timestamp}.{rawBody}"))

Die bisherigen Header-Namen X-PF-Key, X-PF-Timestamp und X-PF-Signature werden weiterhin akzeptiert. Jede Signatur gilt nur einmal; dieselbe event_id bucht nie doppelt.

Feldreferenz, GET-Variante, Testkonsole und Postback-Protokoll: Integrationsbereich in der App

Conversion-Postback – Node.js
import crypto from "node:crypto"; const body = JSON.stringify({  click_id: clickId,            // aus dem click_id-Makro deiner Ziel-URL  event_id: `order-${orderId}`,  // eindeutig je Ereignis (Idempotenz)  transaction_id: orderId,  goal: "sale",  amount: "49.90",  currency: "EUR",});const ts = Math.floor(Date.now() / 1000);const signature = "v1=" + crypto.createHmac("sha256", process.env.SECRET).update(`${ts}.${body}`).digest("hex"); const res = await fetch("https://joincolly.com/api/v1/postback", {  method: "POST",  headers: {    "Content-Type": "application/json",    "X-Colly-Key": process.env.KEY_ID,    "X-Colly-Timestamp": String(ts),    "X-Colly-Signature": signature,  },  body,});console.log(await res.json()); // { status: "accepted", conversion_id: "…", conversion_status: "pending" }
Conversion-Postback – Python
import hashlib, hmac, json, os, time, requests body = json.dumps({"click_id": click_id, "event_id": f"order-{order_id}",                   "transaction_id": order_id, "goal": "sale", "amount": "49.90", "currency": "EUR"},                  separators=(",", ":"))ts = str(int(time.time()))sig = "v1=" + hmac.new(os.environ["SECRET"].encode(), f"{ts}.{body}".encode(), hashlib.sha256).hexdigest() r = requests.post("https://joincolly.com/api/v1/postback", data=body, timeout=10, headers={    "Content-Type": "application/json",    "X-Colly-Key": os.environ["KEY_ID"],    "X-Colly-Timestamp": ts,    "X-Colly-Signature": sig,})print(r.json())

Codebeispiele

Kopierfertige Beispiele inkl. Paginierung, Umgang mit Rate Limits und idempotenten Schreibzugriffen. Hinterlege das Token in der Umgebungsvariable API_TOKEN.

curl
API_TOKEN="pk_….sk_…" # Statistik je Angebot für Oktober in Berliner Zeitcurl -sS "https://joincolly.com/api/v1/stats?from=2026-10-01&to=2026-10-31&group_by=offer&timezone=Europe/Berlin" \  -H "Authorization: Bearer $API_TOKEN" # Conversion mit Begründung ablehnencurl -sS -X POST "https://joincolly.com/api/v1/conversions/cm3conv0001/reject" \  -H "Authorization: Bearer $API_TOKEN" \  -H "Content-Type: application/json" \  -H "Idempotency-Key: $(uuidgen)" \  --data '{"reason":"Order cancelled"}'
Node.js
// Node.js 18+ (fetch)const BASE = "https://joincolly.com/api/v1";const headers = { Authorization: `Bearer ${process.env.API_TOKEN}` }; async function* list(path, params = {}) {  let cursor = null;  do {    const qs = new URLSearchParams({ limit: "100", ...params, ...(cursor ? { cursor } : {}) });    const res = await fetch(`${BASE}${path}?${qs}`, { headers });    if (res.status === 429) {      await new Promise((r) => setTimeout(r, Number(res.headers.get("Retry-After") ?? 1) * 1000));      continue;    }    const body = await res.json();    if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message} (${body.error.request_id})`);    yield* body.data;    cursor = body.next_cursor;  } while (cursor);} for await (const c of list("/conversions", { status: "pending" })) {  console.log(c.id, c.transaction_id, c.payout, c.currency);} // Trackinglink erstellen (wiederholungssicher)const res = await fetch(`${BASE}/links`, {  method: "POST",  headers: { ...headers, "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() },  body: JSON.stringify({ offer_id: "cm3offer001", name: "Newsletter November", sub1: "newsletter" }),});const { data: link } = await res.json();console.log(link.url);
Python
# Python 3 (requests)import os, time, uuid, requests BASE = "https://joincolly.com/api/v1"session = requests.Session()session.headers["Authorization"] = f"Bearer {os.environ['API_TOKEN']}" def paginate(path, **params):    params.setdefault("limit", 100)    while True:        r = session.get(f"{BASE}{path}", params=params, timeout=10)        if r.status_code == 429:            time.sleep(int(r.headers.get("Retry-After", "1")))            continue        r.raise_for_status()        body = r.json()        yield from body["data"]        if not body.get("next_cursor"):            break        params["cursor"] = body["next_cursor"] for c in paginate("/conversions", status="pending"):    print(c["id"], c["transaction_id"], c["payout"], c["currency"]) # Conversion freigeben (wiederholungssicher)r = session.post(    f"{BASE}/conversions/cm3conv0001/approve",    json={},    headers={"Idempotency-Key": str(uuid.uuid4())},    timeout=10,)print(r.json()["data"]["status"])