cli.gs
Sign in

Mağazana gönderilen postback'ler

Durum değişikliklerini al, imzayı doğrula, yeniden denemeleri işle.

cli.gs, bir dönüşümün durumu her değiştiğinde mağazana haber verebilir: kaydedildi, onaylandı, reddedildi veya iptal edildi. Bu sayfada bu durum bildirimini nasıl kuracağını, imzasını nasıl doğrulayacağını ve yeniden denemelerin nasıl çalıştığını öğrenirsin. Böylece mağazanın kayıtları, sorgulama yapmadan bizimkileri izler; örneğin arka ofisinde bir siparişi "komisyon onaylandı" olarak işaretlemek için. Bu isteğe bağlıdır; entegrasyon bunsuz da çalışır.

Kısaca:

  • Durum bildirimi (isteğe bağlı) altında Postback adresi olarak bir HTTPS adresi gir ve Kaydet düğmesine tıkla.
  • Her durum değişikliği için kendi kullandığın üç X-Cligs-* başlığıyla imzalanmış tek bir POST göndeririz.
  • İmzayı ham gövde üzerinden doğrula, sonra 5 saniye içinde 2xx ile yanıt ver.
  • 5xx, 408, 429 veya zaman aşımında yedi defaya kadar yeniden deneriz. Diğer her 4xx teslimatı hemen durdurur.

Kurulum

  1. Entegrasyon sayfasını aç ve Durum bildirimi (isteğe bağlı) bölümüne kaydır.
  2. Postback adresi alanına uç noktanı gir.
  3. Kaydet düğmesine tıkla.

Adres için gereksinimler:

  • HTTPS kullanmalı.
  • Herkese açık bir adrese işaret etmeli. Özel veya dahili ağlardaki adresler hem kaydederken hem her teslimatta reddedilir.
  • Yönlendirmeler izlenmez. Son adresi gir.
  • Teslimatlar programının imza anahtarıyla imzalanır, bu yüzden bir tane gerekir. İlk API anahtarınla birlikte gelir: bkz. İmzalı istekler. İmza anahtarı olmadan hiçbir şey gönderilmez.
  • Yalnızca adresi kaydettikten sonraki durum değişiklikleri gönderilir. Öncekiler geriye dönük olarak teslim edilmez.

Olaylar

OlayNe zaman gönderilir
conversion.recordedBir dönüşüm kaydedildi; kendi postback'inin bildirdikleri ve test dönüşümleri dahil
conversion.approvedBir dönüşüm onaylandı: onay süresinden sonra otomatik olarak veya Dönüşümler altında senin tarafından
conversion.rejectedSen (veya inceleme ekibimiz) bir dönüşümü reddettin
conversion.reversedMağazan bir iptal bildirdi

Her olay dönüşüm başına bir kez gönderilir. Bir sunucu postback'i bekletilen bir piksel dönüşümünü onayladığında yeni bir conversion.recorded gönderilmez. Postback'ine verilen yanıt sonucu zaten söyler.

Gönderdiğimiz istek

text
POST https://shop.example.com/cligs/postback
Content-Type: application/json
User-Agent: cligs-postback/1
X-Cligs-Event: conversion.approved
X-Cligs-Timestamp: 1790000000
X-Cligs-Nonce: 9b1f0c6e2a4d4f8e8c3a7b5d1e2f3a4b
X-Cligs-Signature: 5c0d…e19a
json
{
  "event": "conversion.approved",
  "conversionId": "cmg3x1q2w0001abcd9876efgh",
  "orderId": "100234",
  "programId": "cmf8a7b6c0002wxyz1234lmno",
  "status": "APPROVED",
  "eventType": "SALE",
  "basketValue": "149.99",
  "commission": "12",
  "networkFee": "1.8",
  "currency": "EUR",
  "reason": null,
  "test": false,
  "occurredAt": "2026-09-28T14:05:00.000Z",
  "sentAt": "2026-10-12T03:00:41.512Z"
}
  • status, olay gerçekleştiği andaki dönüşüm durumudur. reason, retler ve iptaller için ayarlanır (ör. RETURN).
  • Yuvarlama hatalarından kaçınmak için tutarlar dizedir.
  • test: true, test dönüşümlerine ait olayları işaretler. Canlı ortamda bunları yok say veya bu uç noktayı test etmek için kullan.
  • occurredAt satışın zamanıdır. sentAt bu teslimat denemesinin yapıldığı zamandır.

İmzayı doğrula

İmza, kendi isteklerindekiyle tamamen aynı şekilde, aynı başlıklar ve aynı imza anahtarıyla oluşturulur. İmzalanan yol, girdiğin adresin yolu ve sorgu dizesidir. Örneğin /cligs/postback, ya da adresinde sorgu varsa /cligs/postback?shop=1.

  1. X-Cligs-Timestamp saatinden beş dakikadan fazla uzaksa isteği reddet.
  2. POST\n<yol>\n<zaman damgası>\n<nonce>\n<ham gövdenin hex SHA-256 özeti> oluştur.
  3. İmza anahtarınla HMAC-SHA256 hesapla. Zamanlama açısından güvenli bir karşılaştırmayla X-Cligs-Signature ile karşılaştır.
  4. İsteğe bağlı olarak nonce'ları beş dakika hatırla ve tekrarları reddet.

Her zaman alındığı haliyle ham gövdeyi özetle. JSON'u ayrıştırıp yeniden serileştirmek baytları değiştirir ve imzayı bozar.

Node.js (Express)

js
import express from "express";
import crypto from "node:crypto";

const SECRET = process.env.CLIGS_SIGNING_SECRET;
// The exact URL you entered as Postback URL. Used for the signed path, so a proxy
// that rewrites paths cannot break verification.
const POSTBACK_URL = new URL("https://shop.example.com/cligs/postback");
const SIGNED_PATH = POSTBACK_URL.pathname + POSTBACK_URL.search;

const app = express();

// express.raw keeps the body as bytes: we need them unchanged for the hash.
app.post(POSTBACK_URL.pathname, express.raw({ type: "application/json" }), (req, res) => {
  const body = req.body.toString("utf8");
  const timestamp = req.get("X-Cligs-Timestamp") ?? "";
  const nonce = req.get("X-Cligs-Nonce") ?? "";
  const signature = req.get("X-Cligs-Signature") ?? "";

  if (!/^\d+$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    return res.status(401).send("stale timestamp");
  }

  const bodyHash = crypto.createHash("sha256").update(body, "utf8").digest("hex");
  const canonical = ["POST", SIGNED_PATH, timestamp, nonce, bodyHash].join("\n");
  const expected = Buffer.from(crypto.createHmac("sha256", SECRET).update(canonical).digest("hex"), "hex");
  const given = Buffer.from(signature, "hex");
  if (given.length !== expected.length || !crypto.timingSafeEqual(given, expected)) {
    return res.status(401).send("bad signature");
  }

  const event = JSON.parse(body);
  res.status(204).end(); // answer first ...
  setImmediate(() => handleCligsEvent(event).catch(console.error)); // ... then do the work
});

async function handleCligsEvent(event) {
  // Deduplicate on conversionId + event: a delivery can arrive twice.
  // e.g. await db.orders.update({ id: event.orderId }, { affiliateStatus: event.status });
}

app.listen(3000);

PHP

php
<?php
// cligs-postback.php — the endpoint behind your Postback URL.
$secret     = getenv('CLIGS_SIGNING_SECRET');
$signedPath = '/cligs/postback'; // path (and query string, if any) of the URL you entered

$body      = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_CLIGS_TIMESTAMP'] ?? '';
$nonce     = $_SERVER['HTTP_X_CLIGS_NONCE'] ?? '';
$signature = strtolower($_SERVER['HTTP_X_CLIGS_SIGNATURE'] ?? '');

if (!ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300) {
    http_response_code(401);
    exit('stale timestamp');
}

$canonical = implode("\n", ['POST', $signedPath, $timestamp, $nonce, hash('sha256', $body)]);
$expected  = hash_hmac('sha256', $canonical, $secret);
if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('bad signature');
}

$event = json_decode($body, true);

// Store the event quickly and process it later (queue, cron job).
// Deduplicate on conversionId + event: a delivery can arrive twice.
store_cligs_event($event['conversionId'], $event['event'], $body);

http_response_code(204);

Hızlı yanıt ver, ve yeniden denemeler

Uç noktandan beklediklerimiz

  • 5 saniye içinde herhangi bir 2xx durumuyla yanıt ver. Olayı sakla ve sonra işle. İstek içinde yavaş iş yapmak zaman aşımı riski taşır ve bu başarısızlık sayılır.
  • Kendi tarafındaki geçici sorunlar için 4xx değil 5xx ile yanıt ver.

Ne zaman yeniden deneriz

  • Zaman aşımı, ağ hatası, HTTP 5xx, 408 veya 429 durumunda 1 dakika, 5 dakika, 30 dakika, 2 saat, 6 saat ve 24 saat sonra yeniden deneriz. Bu, yaklaşık bir buçuk gün boyunca yedi denemedir. Her deneme yeni bir zaman damgası ve nonce ile yeniden imzalanır.
  • Diğer her 4xx (örneğin 400, 401, 404) bilinçli bir "hayır" olarak alınır. Hemen dururuz.
  • Bir teslimat sonunda başarısız olduğunda sana bildiririz. Her teslimatı, denemelerini ve son HTTP durumunu entegrasyon sayfasında Mağazana gönderilen postback'ler altında görebilirsin.

Sonraki adımlar

Uç noktanı uçtan uca kontrol etmek için bir test dönüşümü gönder: bkz. Test ve tanılama.