W SimPay Flow działają dwa niezależne rodzaje powiadomień:
| Rodzaj | Czego dotyczy | Domyślny adres i klucz |
|---|---|---|
| Webhooki Flow | Zmiany statusów submerchantów, ich kanałów płatności i wypłat | Adres i klucz ustawione w usłudze Flow w Panelu Partnera SimPay |
| Webhooki płatności (IPN) | Transakcje, zwroty, kody BLIK, subskrypcje i aliasy BLIK submerchantów - standardowe powiadomienia IPN | Adres i klucz IPN usługi nadrzędnej |
Dla każdego submerchanta możesz nadpisać oba adresy - zobacz Własne adresy dla submerchanta.
Webhooki Flow są wysyłane metodą POST jako JSON, z nagłówkiem User-Agent: SimPay-IPN/2.0.
{
"type": "flow_submerchant_withdraw:status_changed",
"webhook_id": "0199a6c2-5f3e-7d21-8b4a-2c3d4e5f6a7b",
"attempt": 0,
"flow_service": {
"id": "flow_aaaabbbb",
"name": "Moja platforma"
},
"date": "2026-10-01T12:00:00+02:00",
"data": {
"id": "0199a5f0-2b8c-7c1e-9e3a-1f2b3c4d5e6f",
"short_id": "1f2b3c4d",
"amount": 150.5,
"created_at": "2026-10-01T10:00:00+02:00",
"updated_at": "2026-10-01T12:00:00+02:00",
"wallet": {
"id": "0199a5f0-1111-7c1e-9e3a-1f2b3c4d5e6f",
"currency": "PLN"
},
"status": {
"previous": "accepted",
"current": "done"
},
"submerchant": {
"id": "flsm_aaaabbbb",
"custom_id": "user-123"
}
},
"signature": "eb4f27ad689b4613fd7cbeff1aa7b130b5cbe00e6fe3bc93772fb72fa037e472"
}| Pole | Opis |
|---|---|
type | Typ zdarzenia (patrz niżej) |
webhook_id | Unikalne ID powiadomienia (UUID). Każda kolejna próba wysyłki ma nowe webhook_id. |
attempt | Numer próby wysyłki, liczony od 0 |
flow_service.id | ID usługi Flow |
flow_service.name | Nazwa usługi Flow |
date | Data wysłania powiadomienia (ISO 8601) |
data | Dane zdarzenia - zależne od type |
signature | Sygnatura SHA-256 - zobacz Weryfikacja sygnatury |
Powiadomienie uznajemy za dostarczone, gdy Twój serwer odpowie:
- kodem
200z treściąOK, lub - kodem
202albo204.
Każda inna odpowiedź, błąd połączenia lub przekroczenie czasu (5 s na połączenie, 15 s na odpowiedź) oznacza nieudaną próbę.
Nieudane powiadomienia są ponawiane - maksymalnie 30 prób:
| Próby | Odstęp między próbami |
|---|---|
| 1-10 | ok. 1 minuty |
| 11-15 | ok. 5 minut |
| 16-20 | ok. 30 minut |
| 21-30 | ok. 1 godziny |
Jeśli przed ponowieniem stan obiektu zmieni się ponownie (np. wypłata przejdzie z accepted na done), nieaktualne powiadomienie nie zostanie już wysłane - otrzymasz tylko powiadomienie o najnowszej zmianie. Dlatego zawsze traktuj current jako aktualny stan, a nie zakładaj, że otrzymasz każdą pośrednią zmianę.
Historię wysłanych webhooków (wraz z odpowiedziami Twojego serwera) znajdziesz w Panelu Partnera SimPay, w ustawieniach usługi Flow. Stamtąd możesz też wysłać powiadomienie testowe.
Zmienił się status submerchanta - np. po przekazaniu danych w onboardingu, po akceptacji przez SimPay lub po zablokowaniu konta.
"data": {
"submerchant": {
"id": "flsm_aaaabbbb",
"custom_id": "user-123"
},
"previous": {
"status": "client_verify",
"aml_level": 1,
"aml_status": "sent",
"fully_active": false
},
"current": {
"status": "client_active",
"aml_level": 2,
"aml_status": "accepted",
"fully_active": true
}
}| Pole | Opis |
|---|---|
status | Status konta submerchanta w SimPay (np. client_new, client_verify, client_active, client_block) |
aml_level | Poziom weryfikacji AML (2 - pełna weryfikacja) |
aml_status | Status weryfikacji AML (np. sent, accepted, declined) |
fully_active | true, gdy submerchant jest w pełni aktywny i może przyjmować płatności |
Do decyzji, czy submerchant może przyjmować płatności, używaj pola current.fully_active. Pozostałe pola mają charakter informacyjny.
Zmienił się status kanału płatności submerchanta - np. kanał wysłany do weryfikacji został zaakceptowany lub odrzucony, zmienił się status BLIK OneClick lub aktywne modele BLIK Płatności Powtarzalnych.
"data": {
"submerchant": {
"id": "flsm_aaaabbbb",
"custom_id": "user-123"
},
"channel": {
"slug": "blik-level0",
"name": "BLIK Level 0"
},
"previous": {
"status": "active",
"blik_one_click_status": "REGISTERED_WAITING_ACTIVATION",
"blik_recurrent_a": null,
"blik_recurrent_m": null,
"blik_recurrent_o": null
},
"current": {
"status": "active",
"blik_one_click_status": "ACTIVE",
"blik_recurrent_a": null,
"blik_recurrent_m": null,
"blik_recurrent_o": null
}
}| Pole | Opis |
|---|---|
channel.slug | ID kanału płatności (to samo co id w GET .../payment-channels) |
status | Status kanału: new, in_progress, active, rejected |
blik_one_click_status | Status BLIK OneClick (tylko blik-level0): PENDING_REGISTRATION, REGISTERED_WAITING_ACTIVATION, ACTIVE, REJECTED lub null |
blik_recurrent_a / _m / _o | Czy model BLIK Płatności Powtarzalnych AUTO / MANUAL / OPEN jest aktywny (tylko blik-recurrent) lub null |
Zmienił się status wypłaty submerchanta. Przykład data - patrz Struktura powiadomienia.
| Pole | Opis |
|---|---|
id / short_id | ID wypłaty (UUID) i krótkie ID wypłaty |
amount | Kwota wypłaty |
wallet.id / wallet.currency | Portfel, z którego realizowana jest wypłata, i jego waluta |
status.previous / status.current | Poprzedni i aktualny status wypłaty: new, accepted, waiting, done, rejected |
Powiadomienie testowe wysyłane z Panelu Partnera. data zawiera tylko losowe pole nonce:
"data": {
"nonce": "01K6FZ8X3Q9V2N7M4T5R1W0YAB"
}Każdy webhook Flow zawiera pole signature - hash SHA-256 (lowercase hex). Zasada jest taka sama jak w powiadomieniach płatności:
- weź wszystkie wartości z powiadomienia (bez pola
signature) w kolejności, w jakiej występują w JSON, spłaszczając zagnieżdżone obiekty, - połącz je separatorem
|, - dołącz na końcu klucz webhooków Flow,
- policz SHA-256 i porównaj z
signaturebezpieczną funkcją porównującą.
type|webhook_id|attempt|flow_service.id|flow_service.name|date|data_value1|...|data_valueN|KLUCZ_WEBHOOKÓW_FLOWW odróżnieniu od powiadomień płatności webhooki Flow zawierają wartości true/false, null i liczby. Przed połączeniem zamień je na tekst tak jak PHP:
true→1,false→ pusty ciąg,null→ pusty ciąg,- liczby → zapis bez zbędnych zer (
0,2,150.5,100dla100.0).
W JavaScript String(true) daje "true", a w Pythonie str(True) daje "True" - bez konwersji sygnatura się nie zgodzi.
Klucz: jeśli submerchant ma własny adres webhooków Flow, powiadomienia o nim są podpisywane jego kluczem. W pozostałych przypadkach - kluczem webhooków ustawionym w usłudze Flow.
Zobacz wyliczenie sygnatury dla przykładu powyżej
Klucz: FLOW_WEBHOOK_KEY_64
flow_submerchant_withdraw:status_changed|0199a6c2-5f3e-7d21-8b4a-2c3d4e5f6a7b|0|flow_aaaabbbb|Moja platforma|2026-10-01T12:00:00+02:00|0199a5f0-2b8c-7c1e-9e3a-1f2b3c4d5e6f|1f2b3c4d|150.5|2026-10-01T10:00:00+02:00|2026-10-01T12:00:00+02:00|0199a5f0-1111-7c1e-9e3a-1f2b3c4d5e6f|PLN|accepted|done|flsm_aaaabbbb|user-123|FLOW_WEBHOOK_KEY_64SHA-256: eb4f27ad689b4613fd7cbeff1aa7b130b5cbe00e6fe3bc93772fb72fa037e472
<?php
function simpayFlowSignatureValid(array $payload, string $key): bool
{
$signature = $payload['signature'] ?? '';
unset($payload['signature']);
$values = [];
array_walk_recursive($payload, function ($value) use (&$values) {
$values[] = $value;
});
$values[] = $key;
// implode() zamienia true na "1", a false i null na pusty ciąg
return hash_equals(hash('sha256', implode('|', $values)), $signature);
}
$payload = json_decode(file_get_contents('php://input'), true);
if (!is_array($payload) || !simpayFlowSignatureValid($payload, 'KLUCZ_WEBHOOKÓW_FLOW')) {
http_response_code(403);
exit('INVALID_SIGNATURE');
}
// obsługa zdarzenia $payload['type']
echo 'OK';Powiadomienia o transakcjach, zwrotach, kodach BLIK, subskrypcjach i aliasach BLIK submerchantów to standardowe powiadomienia IPN - z tą samą strukturą i tym samym sposobem wyliczania sygnatury. W SimPay Flow różnią się kilkoma szczegółami.
| Sytuacja | Adres | Klucz do sygnatury |
|---|---|---|
| Submerchant nie ma własnego adresu IPN | Adres IPN usługi nadrzędnej | Klucz IPN usługi nadrzędnej |
| Submerchant ma własny adres IPN | Własny adres submerchanta | Klucz IPN submerchanta (zwrócony przy ustawianiu adresu) |
Dzięki temu w podstawowej konfiguracji obsługujesz powiadomienia wszystkich submerchantów jednym endpointem i jednym kluczem - tym samym, którego używasz dla usługi nadrzędnej.
We wszystkich powiadomieniach płatności dotyczących submerchantów (transaction:status_changed, transaction_refund:status_changed, transaction_blik_level0:code_status_changed, subscription:status_changed, blik:alias_status_changed) pole service_id zawiera ID usługi nadrzędnej - tej, którą podajesz w ścieżce /payment/{serviceId}/.... Wewnętrzna usługa submerchanta nigdy nie pojawia się w powiadomieniach.
Każde powiadomienie płatności dotyczące submerchanta (wszystkie zdarzenia poza ipn:test) zawiera w data obiekt flow. Dla zwykłych usług Płatności Online obiekt nie występuje.
| Pole | Opis |
|---|---|
flow.submerchant_id | ID submerchanta (flsm_...) |
flow.submerchant_custom_id | customId submerchanta |
flow.flow_service_id | ID usługi Flow |
Ponieważ service_id wskazuje usługę nadrzędną, to właśnie obiekt flow pozwala przypisać transakcję, zwrot, subskrypcję czy alias do właściwego submerchanta w Twoim systemie.
"flow": {
"submerchant_id": "flsm_aaaabbbb",
"submerchant_custom_id": "user-123",
"flow_service_id": "flow_aaaabbbb"
}Dodatkowo w zdarzeniu transaction:status_changed pojawia się pole amount.commission_flow - Twoja prowizja Flow od transakcji (string, format %.2f). Występuje tylko, gdy prowizja jest większa od zera.
{
"type": "transaction:status_changed",
"notification_id": "0199a6d0-1234-7abc-9def-0123456789ab",
"date": "2026-10-01T12:05:00+02:00",
"data": {
"id": "0199a6cf-aaaa-7bbb-8ccc-0123456789ab",
"payer_transaction_id": "Q68KLAKN",
"service_id": "252b85c9",
"status": "transaction_paid",
"amount": {
"final_currency": "PLN",
"final_value": "49.99",
"original_currency": "PLN",
"original_value": "49.99",
"commission_system": "0.60",
"commission_partner": "49.14",
"commission_currency": "PLN",
"commission_flow": "0.25"
},
"control": "order-1001",
"payment": {
"channel": "blik",
"type": "blik"
},
"customer": {
"country_code": "PL"
},
"paid_at": "2026-10-01T12:04:58+02:00",
"created_at": "2026-10-01T12:03:10+02:00",
"flow": {
"submerchant_id": "flsm_aaaabbbb",
"submerchant_custom_id": "user-123",
"flow_service_id": "flow_aaaabbbb"
}
},
"signature": "..."
}Pola commission_flow i flow biorą udział w wyliczaniu sygnatury jak każde inne pole obiektu data. Jeśli Twój walidator dynamicznie spłaszcza cały payload (tak jak w przykładach), nie musisz niczego zmieniać.
Domyślnie wszystkie powiadomienia trafiają na adresy wspólne dla całej usługi. Dla wybranego submerchanta możesz ustawić osobne adresy - np. gdy każdy z Twoich Klientów ma własną instancję aplikacji.
Aktualne ustawienia pobierzesz przez GET .../submerchants/{submerchantId}/webhooks, a zmienisz je przez PATCH .../submerchants/{submerchantId}/webhooks:
curl -X PATCH "https://api.simpay.pl/flow/{flowServiceId}/submerchants/user-123/webhooks" \
-H "Authorization: Bearer TWÓJ_TOKEN_API" \
-H "Content-Type: application/json" \
-d '{
"flow": { "customUrl": "https://klient-123.example.com/webhooks/flow" },
"payment": { "customUrl": "https://klient-123.example.com/webhooks/ipn" }
}'{
"success": true,
"data": {
"paymentNewIpnKey": "aVeryLongAndSecureKeyFor32Chars0",
"flowNewIpnKey": "..."
}
}Przy pierwszym ustawieniu własnego adresu SimPay generuje dla submerchanta osobny klucz do weryfikacji sygnatury powiadomień. Klucz jest zwracany tylko raz - w odpowiedzi na to żądanie. Przy kolejnych zmianach adresu klucz się nie zmienia, a pole w odpowiedzi ma wartość null.
Aby wrócić do adresu domyślnego, wyślij {"flow": {"useDefault": true}} (lub payment). Własny adres i klucz submerchanta zostaną usunięte, a powiadomienia wrócą na adres i klucz usługi.
Jeśli klucz wyciekł lub go zgubiłeś, wygeneruj nowy przez PATCH .../webhooks/key z {"type": "flow"} lub {"type": "payment"}. Działa to tylko dla submerchantów z własnym adresem webhooków danego typu. Po wygenerowaniu nowego klucza poprzedni przestaje być używany.