Sign in
Wprowadzenie/Powiadomienia Webhook

Powiadomienia webhook

Otrzymuj w czasie rzeczywistym aktualizacje statusu płatności i wypłat poprzez webhooki podpisane HMAC.

System 2328.io wysyła webhook pod Twój url_callback przy każdej zmianie statusu płatności. To zalecany sposób otrzymywania powiadomień o udanych płatnościach.

Format żądania

  • Method: POST
  • Content-Type: application/json
  • Signature: pole sign w treści żądania

Payload

Treść webhooka zachowuje format odpowiedzi /v1/payment/info i dodaje tx_explorer_url oraz pole sign używane do weryfikacji podpisu.

Płatność udana

JSON
{
  "uuid": "db17d490-15b6-47b9-9015-91d1d8b119f2",
  "order_id": "ORDER-12345",
  "amount": "180.00000000",
  "currency": "RUB",
  "url": "https://go.2328.io/db17d490-15b6-47b9-9015-91d1d8b119f2",
  "expires_at": "2026-05-09T16:56:58+03:00",
  "created_at": "2026-05-09T15:56:58+03:00",
  "payer_currency": "TON",
  "payer_amount": "0.95256917",
  "network": "TON",
  "address": "UQA0RevhkCQx-EltyNgPPeG8dqtnCz7ZslOzMdNQlLxVaNBb",
  "payment_status": "paid",
  "txid": "41c2a327323480af8e705d05deb09c238a41779928832abef4bb77c862357b11",
  "tx_explorer_url": "https://tonviewer.com/transaction/41c2a327323480af8e705d05deb09c238a41779928832abef4bb77c862357b11",
  "payment_amount": "0.95256917",
  "merchant_amount": "0.949711462490000000",
  "amount_usd": "2.41324380",
  "exchange_rate": "0.01340691",
  "sign": "6f8c15b6e53b506d5bfa38ed3fb3b50697af73434262153c02e412541372f04d"
}

Płatność anulowana / nieudana

Gdy płatność nie jest w terminalnym stanie paid, pola txid, payment_amount oraz merchant_amount mają wartość null:

JSON
{
  "uuid": "48edaf2d-2c49-4638-8f86-88636f661c1f",
  "order_id": "ORDER-12345",
  "amount": "2800.00000000",
  "currency": "RUB",
  "url": "https://go.2328.io/48edaf2d-2c49-4638-8f86-88636f661c1f",
  "expires_at": "2026-05-09T06:19:04+03:00",
  "created_at": "2026-05-09T05:19:04+03:00",
  "payer_currency": "ETH",
  "payer_amount": "0.01620968",
  "network": "ETH-ERC20",
  "address": "0x37c20d6d96d130Bc5B33D832e43b8e16aACe0c59",
  "payment_status": "cancel",
  "txid": null,
  "tx_explorer_url": null,
  "payment_amount": null,
  "merchant_amount": null,
  "amount_usd": "37.53934800",
  "exchange_rate": "0.01340691",
  "sign": "40ce68ad9691ad54e684329d75ab5adaf5b01409a2d18d3e0110b8c1be605342"
}

Opis pól

PoleTypOpis
uuidstringUUID płatności
order_idstringTwój identyfikator zamówienia
amountdecimal (8 dp)Kwota fiat w currency
currencystringWaluta fiat zażądana przez sprzedawcę
urlstringURL hostowanego checkoutu
expires_atstring (ISO 8601)Moment wygaśnięcia sesji płatności
created_atstring (ISO 8601)Moment utworzenia sesji płatności
payer_currencystringKryptowaluta, w której płaci płacący
payer_amountdecimal (8 dp)Oczekiwana kwota w kryptowalucie
networkstringSieć blockchain
addressstringAdres depozytowy
payment_statusstringJedna z wartości: pending, check, paid, underpaid_check, underpaid, overpaid, cancel, aml_lock (zobacz References)
txidstring | nullHash transakcji blockchain, obecny tylko po potwierdzonej płatności
tx_explorer_urlstring | nullAdres URL transakcji w eksploratorze blockchain. Wartość null, gdy brakuje txid lub przelew jest wewnętrznym P2P.
payment_amountdecimal | nullFaktycznie zapłacona kwota, obecna tylko po płatności
merchant_amountdecimal (18 dp) | nullKwota zaksięgowana sprzedawcy po opłatach
amount_usddecimal (8 dp)Kwota w USD w chwili utworzenia
exchange_ratedecimalUżyty kurs wymiany krypto / fiat
signstring (hex)Podpis HMAC-SHA256 payloadu

Weryfikacja podpisu

Aby zweryfikować podpis webhooka:

  1. Wyciągnij pole sign z payloadu
  2. Usuń pole sign z obiektu
  3. Zakoduj pozostałe pola jako JSON
  4. Zakoduj JSON w base64
  5. Oblicz HMAC-SHA256 z ciągu base64 przy użyciu swojego API_KEY
  6. Porównaj obliczony podpis z wartością sign, używając porównania w czasie stałym
PHP
<?php
function verifyWebhookSign(array $data, string $apiKey): bool {
    $receivedSign = $data['sign'] ?? '';
    unset($data['sign']);

    $json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    $base64 = base64_encode($json);
    $calculated = hash_hmac('sha256', $base64, $apiKey);

    return hash_equals($calculated, $receivedSign);
}

$apiKey = 'YOUR_API_KEY';
$payload = json_decode(file_get_contents('php://input'), true);

if (!verifyWebhookSign($payload, $apiKey)) {
    http_response_code(401);
    exit;
}

switch ($payload['payment_status']) {
    case 'paid':
    case 'overpaid':
        // Credit the order — check idempotency by order_id first
        break;
    case 'underpaid_check':
    case 'underpaid':
    case 'cancel':
        break;
}

http_response_code(200);

Zawsze weryfikuj podpis przed zaksięgowaniem jakichkolwiek środków użytkownikowi. Niepodpisany lub błędnie podpisany webhook może być spreparowanym żądaniem.

Webhooki wypłat

Gdy zmienia się status wypłaty, system wysyła webhook POST pod adres url_callback przekazany w momencie utworzenia wypłaty. Jeśli url_callback nie został podany, dla tej wypłaty nie są wysyłane żadne webhooki.

Webhooki wypłat muszą być weryfikowane Twoim Payout API key — nie zwykłym kluczem API. Algorytm podpisywania jest identyczny jak dla webhooków płatności (usuń sign, zakoduj jako JSON, base64, HMAC-SHA256), różni się wyłącznie kluczem.

Payload

JSON
{
  "uuid": "019dff1f-0dbd-7277-8d45-271e7775388f",
  "order_id": "4dfdcc84402b1185b71cbe399321533e",
  "status": "completed",
  "currency": "TRX",
  "network": "TRX-TRC20",
  "amount": "3.00",
  "merchant_amount": "3.00",
  "network_amount": "3.00",
  "amount_usd": "1.04",
  "to_address": "THauRv5tcucQRohXg8NiyGTk16DX1XQG5x",
  "memo": null,
  "txid": "9242e533703704ef3eaba840f70b4a26333e72c943377ee375fea17badb53def",
  "tx_explorer_url": "https://tronscan.org/#/transaction/9242e533703704ef3eaba840f70b4a26333e72c943377ee375fea17badb53def",
  "block_number": null,
  "error_type": null,
  "created_at": "2026-05-07T00:08:38+03:00",
  "updated_at": "2026-05-07T00:08:54+03:00",
  "from_currency": "USDT",
  "debited_amount": "1.050735",
  "debited_currency": "USDT",
  "sign": "925ad7bf3d6841864101f7cc2c7e30652e70a06cdb04dbe07a0129480000ce4a"
}

Opis pól

PoleTypOpis
uuidstringUUID wypłaty
order_idstringTwój identyfikator idempotentności / referencyjny, jeśli go podano
statusstringpending, completed, failed, cancelled (zobacz References)
currencystringWaluta wypłaty
networkstringSieć blockchain
amountdecimalKwota wypłaty (w currency)
merchant_amountdecimalKwota pobrana z salda sprzedawcy
network_amountdecimalKwota faktycznie wysłana w sieci
amount_usddecimalWartość w USD w chwili wypłaty
to_addressstringAdres blockchain odbiorcy
memostring | nullMemo / tag docelowy, jeśli użyty
txidstring | nullHash transakcji blockchain, ustawiany przy completed
tx_explorer_urlstring | nullAdres URL transakcji w eksploratorze blockchain. Wartość null, gdy brakuje txid lub przelew jest wewnętrznym P2P.
block_numberinteger | nullWysokość bloku transakcji on-chain
error_typestring | nullPowód, gdy status = failed (np. aml_risk, zobacz References)
created_atstring (ISO 8601)Moment utworzenia wypłaty
updated_atstring (ISO 8601)Moment ostatniej zmiany statusu
from_currencystringSaldo źródłowe, z którego pobrano wypłatę przy użyciu automatycznej konwersji (np. USDT dla wypłaty w BTC)
debited_amountdecimalKwota pobrana z salda from_currency
debited_currencystringWaluta obciążenia
signstring (hex)Podpis HMAC-SHA256 payloadu, podpisany Payout API key

Dobre praktyki

  • Idempotentność — zawsze sprawdzaj, czy płatność nie została już przetworzona (po order_id lub uuid). Webhooki mogą docierać wielokrotnie.
  • Szybka odpowiedź — odpowiadaj HTTP 200 możliwie najszybciej. Cięższe operacje przekaż do kolejki w tle.
  • Ponawianie prób — jeśli system nie otrzyma HTTP 200, webhook jest wysyłany ponownie po 2 minutach. Maksymalnie 5 prób ponawiania.
  • Przetwarzanie asynchroniczne — obsługuj zdarzenia webhook asynchronicznie, aby nie blokować odpowiedzi.
  • Bezpieczeństwo — ZAWSZE weryfikuj podpis sign przed zaufaniem payloadowi.

Webhooki mogą docierać w innej kolejności niż zdarzenia. Nie zakładaj, że pierwszy otrzymany webhook to stan końcowy — jeśli potrzebujesz pewności, zawsze ponownie pobieraj dane przez /v1/payment/info (lub /v1/payout/status/{uuid}).

Umowa dostawy i przetwarzania

Użyj następującej kolejności w swoim punkcie końcowym webhook:

  1. Odczytaj ciało żądania bez rejestrowania sekretów ani pełnego podpisu.
  2. Określ, czy jest to zdarzenie płatności/statycznego portfela, czy zdarzenie wypłaty, aby wybrać właściwy klucz API.
  3. Usuń sign, odtwórz udokumentowane bajty JSON, oblicz HMAC-SHA256 i porównaj w stałym czasie.
  4. Zweryfikuj wymagane identyfikatory, ciągi dziesiętne i wartości statusu.
  5. Atomowo wstaw rekord skrzynki/idempotencji. Jeśli już istnieje, zwróć HTTP 200 bez powtarzania efektów ubocznych.
  6. Zatwierdź mutację zamówienia/księgi i umieść w kolejce mało istotne e-maile, analizy lub powiadomienia.
  7. Szybko zwróć HTTP 200.

Nie wywołuj wolnych usług zewnętrznych podczas trzymania transakcji idempotencji. Przekroczenie limitu czasu po zatwierdzeniu, ale przed zwróceniem odpowiedzi, może spowodować ponowne wywołanie; duplikat musi uwzględniać zatwierdzony klucz skrzynki odbiorczej i stać się operacją neutralną.

WydarzenieGłówna tożsamośćNotatki
Sesja płatnościuuid + dowody statusu/wersjiTa sama faktura może generować wiele prawidłowych zmian statusu.
Częściowa dopłatafaktura uuid + txidWięcej niż jeden przelew może należeć do tej samej niedopłaconej faktury.
Wpłata na portfel statycznysieć + txidorder_id jest używany ponownie przy każdej wpłacie na ten portfel.
Wypłatawypłata uuid + statusNigdy nie twórz drugiej wypłaty w ramach logiki ponawiania webhooka.

Jeśli w Twoim schemacie nie ma identyfikatora zdarzenia, przechowuj zweryfikowany hash payload jako dodatkowy dowód audytu, ale nie zastępuj powyższych tożsamości biznesowych znacznikiem czasowym.

Zamawianie i uzgadnianie

Dostawa odbywa się co najmniej raz, a komunikaty o stanie mogą się ścigać. Wdróż monotoniczne zasady biznesowe zamiast „wygrywa ostatnie żądanie”:

  • nigdy nie przenoś zrealizowanego zamówienia z powrotem do check z powodu opóźnionego przybycia starszego zdarzenia;
  • pozwól underpaid_check otrzymywać dodatkowe txidy bez powtarzania wcześniejszych kredytów;
  • traktuj paid i overpaid jako stany pomyślnego rozliczenia, zachowując przy tym ich różne kwoty;
  • utrzymuj underpaid jako ostateczny wynik częściowej płatności, chyba że autorytatywne API później zgłosi inny stan;
  • przekieruj aml_lock do przeglądu i nie pozwól, aby ogólny pracownik odpowiedzialny za ponawianie wykonał jego realizację;
  • zapytaj o informacje o płatności/wypłacie, gdy przejście jest niemożliwe, brak kontekstu lub jest niejednoznaczne finansowo.

Uruchamiaj zaplanowaną rekonsyliację nawet wtedy, gdy dostarczanie webhooka wydaje się prawidłowe. Porównaj stan lokalnego terminala i zaksięgowaną kwotę z /v1/payment/info, /v1/static-wallet/transactions lub /v1/payout/status/{uuid} i zgłaszaj różnice zamiast cicho nadpisywać historię księgi.

Bezpieczeństwo punktu końcowego webhooka

  • Wymagaj HTTPS i utrzymuj callback publicznie dostępnym; prywatne/loopbackowe cele callback są odrzucane podczas tworzenia płatności.
  • Egzekwuj mały limit rozmiaru ciała żądania i typ treści JSON.
  • Ograniczaj częstość przed kosztownymi operacjami, ale zostaw wystarczający margines na legalne szczyty i ponowne próby.
  • Nigdy nie autoryzuj webhooka wyłącznie na podstawie adresu IP źródła. Listy dostępu sieci to obrona w głąb; weryfikacja HMAC jest obowiązkowa.
  • Redaguj sign, klucze API, adresy, gdy wymaga tego polityka, oraz dane osobowe z logów aplikacji.
  • Utrzymuj zarówno bieżące, jak i wyraźnie zaplanowane klucze zamienne dostępne podczas kontrolowanego okna rotacji; nigdy nie zgaduj, który klucz podpisał zdarzenie.
  • Zwracaj ogólny komunikat o błędzie w przypadku nieprawidłowych podpisów, aby punkt końcowy nie stał się wróżbitą kluczy lub kont.