Webhooki
Selgeo wysyła powiadomienia HTTP POST w czasie rzeczywistym do Twojego serwera, gdy zdarzenia występują na Twoim koncie. Używaj webhooków do automatyzacji przepływów pracy, takich jak przydzielanie dostępu gdy partner zostaje zatwierdzony, synchronizacja prowizji z systemem księgowym lub alertowanie zespołu o oszustwach.
Wszystkie payloady webhooków używają API wersji v1.
Rejestracja punktów końcowych
Zarejestruj punkty końcowe webhooków ze strony Ustawienia > Webhooki w panelu sprzedawcy.
- Kliknij Dodaj punkt końcowy.
- Wprowadź adres URL, pod którym chcesz odbierać zdarzenia. Punkty końcowe trybu live muszą używać HTTPS.
- Wybierz zdarzenia, które chcesz subskrybować.
- Kliknij Utwórz. Twój sekret podpisywania (
whsec_...) jest wyświetlany jednorazowo — skopiuj go i przechowaj bezpiecznie.
Weryfikacja podpisu
Każde żądanie webhooka zawiera nagłówek X-Selgeo-Signature:
t=<unix_timestamp>,v1=<hmac_hex>
HMAC jest obliczany jako HMAC-SHA256(signing_secret_bytes, "<timestamp>.<raw_json_body>").
Przykłady weryfikacji
- Node.js
- Python
- PHP
import crypto from 'node:crypto';
function verifyWebhookSignature(signingSecret, signatureHeader, rawBody) {
if (!signatureHeader) return false;
const rawSecret = signingSecret.replace(/^whsec_/, '');
const secretBytes = Buffer.from(rawSecret, 'hex');
const parts = Object.fromEntries(
signatureHeader.split(',').map((part) => part.split('=', 2))
);
const timestamp = parts.t;
const receivedHmac = parts.v1;
if (!timestamp || !receivedHmac) return false;
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10);
if (age > 300 || age < -30) return false;
const expectedHmac = crypto
.createHmac('sha256', secretBytes)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
const receivedBuf = Buffer.from(receivedHmac, 'hex');
const expectedBuf = Buffer.from(expectedHmac, 'hex');
if (receivedBuf.length !== expectedBuf.length) return false;
return crypto.timingSafeEqual(receivedBuf, expectedBuf);
}
import hashlib
import hmac
import time
def verify_webhook_signature(signing_secret: str, signature_header: str, raw_body: str) -> bool:
if not signature_header:
return False
raw_secret = signing_secret.removeprefix("whsec_")
secret_bytes = bytes.fromhex(raw_secret)
try:
parts = dict(part.split("=", 1) for part in signature_header.split(","))
except ValueError:
return False
timestamp = parts.get("t")
received_hmac = parts.get("v1")
if not timestamp or not received_hmac:
return False
try:
age = int(time.time()) - int(timestamp)
except ValueError:
return False
if age > 300 or age < -30:
return False
message = f"{timestamp}.{raw_body}".encode()
expected_hmac = hmac.new(secret_bytes, message, hashlib.sha256).hexdigest()
return hmac.compare_digest(received_hmac, expected_hmac)
<?php
function verifyWebhookSignature(string $signingSecret, string $signatureHeader, string $rawBody): bool {
if ($signatureHeader === '') return false;
$rawSecret = str_starts_with($signingSecret, 'whsec_') ? substr($signingSecret, 6) : $signingSecret;
$secretBytes = hex2bin($rawSecret);
if ($secretBytes === false) return false;
$parts = [];
foreach (explode(',', $signatureHeader) as $part) {
$segments = explode('=', $part, 2);
if (count($segments) !== 2) continue;
[$key, $value] = $segments;
$parts[$key] = $value;
}
$timestamp = $parts['t'] ?? null;
$receivedHmac = $parts['v1'] ?? null;
if (!$timestamp || !$receivedHmac) return false;
$age = time() - (int) $timestamp;
if ($age > 300 || $age < -30) return false;
$expectedHmac = hash_hmac('sha256', "{$timestamp}.{$rawBody}", $secretBytes);
return hash_equals($expectedHmac, $receivedHmac);
}
Katalog zdarzeń
Zdarzenia partnerów
| Zdarzenie | Opis |
|---|---|
participant.created | Nowy partner złożył wniosek lub został dodany do programu |
participant.approved | Partner został zatwierdzony |
participant.rejected | Wniosek partnera został odrzucony |
participant.suspended | Aktywny partner został zawieszony |
participant.reinstated | Zawieszony partner został przywrócony |
participant.erased | Dane partnera zostały usunięte (RODO) |
Zdarzenia atrybucji
| Zdarzenie | Opis |
|---|---|
attribution.created | Kliknięcie zostało przypisane do partnera |
attribution.converted | Przypisane kliknięcie skutkowało konwersją |
attribution.expired | Okno atrybucji wygasło bez konwersji |
Zdarzenia konwersji
| Zdarzenie | Opis |
|---|---|
conversion.created | Zarejestrowano nową konwersję |
conversion.fraud_detected | Wykryto oszustwo (samopolecenie lub duplikat konwersji) |
Zdarzenia prowizji
| Zdarzenie | Opis |
|---|---|
commission.created | Obliczono nową prowizję |
commission.approved | Prowizja została zatwierdzona do wypłaty |
commission.rejected | Prowizja została odrzucona |
commission.refunded | Prowizja została cofnięta z powodu zwrotu |
Polityka ponownych prób
Jeśli Twój punkt końcowy zwraca kod statusu inny niż 2xx lub żądanie przekroczy limit czasu (30 sekund), Selgeo ponawia próbę z wykładniczym opóźnieniem:
| Próba | Opóźnienie po niepowodzeniu |
|---|---|
| 1. próba | 1 minuta |
| 2. próba | 2 minuty |
| 3. próba | 4 minuty |
| 4. próba | 15 minut |
Po 5 łącznych próbach dostawa przechodzi do stanu dead-letter.
Najlepsze praktyki
- Zwracaj 200 szybko. Przetwarzaj zdarzenie asynchronicznie w tle.
- Obsługuj duplikaty. Używaj pola
event_iddo deduplikacji. - Weryfikuj podpisy. Zawsze weryfikuj nagłówek
X-Selgeo-Signature. - Używaj HTTPS na produkcji. Punkty końcowe trybu live wymagają HTTPS.