Zdarzenia i webhooki
Zdarzenia
Wszystko, co dzieje się na koncie, zostawia zdarzenie evt_: utworzenie i opłacenie płatności, zwrot, zmiana klienta, odnowienie subskrypcji, faktura w KSeF. Ta sama treść idzie na webhook i jest dostępna pod GET /v1/events, więc po awarii odbiorcy nic nie ginie: nadrabiasz historię z listy albo każesz wysłać zdarzenie ponownie.
Klucz testowy w przykładach
Wklej klucz sk_test_ z panelu (zakładka Deweloperzy), a każdy przykład będzie gotowy do uruchomienia. Klucz zostaje w tej karcie przeglądarki, nie wysyłamy go nigdzie.
Przykłady używają Twojego klucza testowego. Produkcyjnego klucza nie przyjmujemy.
Obiekt event#
| Pole | Znaczenie |
|---|---|
id | identyfikator evt_; stały, służy do obsługi idempotentnej |
type | typ zdarzenia, np. payment.succeeded |
api_version | wersja API, w której przygotowano data.object |
livemode | true dla zdarzeń produkcyjnych |
created | chwila zdarzenia (Unix) |
data.object | migawka obiektu z chwili zdarzenia, bez rozwinięć (pola powiązane to identyfikatory) |
request.id, request.idempotency_key | żądanie API, które wywołało zdarzenie, albo null dla zdarzeń z harmonogramu i strony płatności |
pending_webhooks | ile doręczeń jeszcze czeka |
delivery | tylko w GET /v1/events: liczba prób, ostatni kod HTTP, chwila doręczenia, następna próba |
{
"id": "evt_Ab12Cd34Ef56Gh",
"object": "event",
"type": "payment.succeeded",
"api_version": "2026-10-03",
"livemode": false,
"created": 1760000000,
"data": {
"object": {
"id": "pay_Ab12Cd34Ef56Gh",
"object": "payment",
"amount": 12900,
"currency": "PLN",
"status": "succeeded",
"method": "blik",
"metadata": {"order_id": "10482"},
"livemode": false
}
},
"request": {"id": "req_7mK2pQ9rT4vX1bN8cD3fG6hJ", "idempotency_key": "zamowienie-10482"},
"pending_webhooks": 0,
"delivery": {"attempts": 1, "last_status": 200, "delivered_at": 1760000001, "next_attempt": null}
}
Historia zdarzeń: GET /v1/events#
Lista zdarzeń konta w trybie klucza, od najnowszego, ze stronicowaniem kursorem i filtrami type, delivered oraz created[...]. To Twoja siatka bezpieczeństwa: po awarii odbiorcy webhooków pobierz zdarzenia z delivered=false albo z created[gte] od chwili awarii i przetwórz je tą samą funkcją, która obsługuje webhooki. Pojedyncze zdarzenie zwraca GET /v1/events/{id}.
curl "https://oplac.pl/api/v1/events?type=payment.succeeded&created[gte]=1759968000&limit=100" \
-H "Authorization: Bearer sk_test_TWOJ_KLUCZ"
$ch = curl_init('https://oplac.pl/api/v1/events?' . http_build_query([
'type' => 'payment.succeeded', 'created[gte]' => 1759968000, 'limit' => 100,
]));
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['Authorization: Bearer sk_test_TWOJ_KLUCZ'],
CURLOPT_RETURNTRANSFER => true,
]);
$events = json_decode(curl_exec($ch), true);
foreach ($events['data'] as $event) {
obsluzZdarzenie($event); // ta sama funkcja co w odbiorcy webhooka
}
const q = new URLSearchParams({ type: 'payment.succeeded', 'created[gte]': '1759968000', limit: '100' });
const res = await fetch(`https://oplac.pl/api/v1/events?${q}`, {
headers: { Authorization: 'Bearer sk_test_TWOJ_KLUCZ' },
});
const events = await res.json();
for (const event of events.data) {
await obsluzZdarzenie(event); // ta sama funkcja co w odbiorcy webhooka
}
import requests
res = requests.get(
"https://oplac.pl/api/v1/events",
headers={"Authorization": "Bearer sk_test_TWOJ_KLUCZ"},
params={"type": "payment.succeeded", "created[gte]": 1759968000, "limit": 100},
)
events = res.json()
for event in events["data"]:
obsluz_zdarzenie(event) # ta sama funkcja co w odbiorcy webhooka
HTTP/1.1 200 OK
{
"object": "list",
"url": "/v1/events",
"has_more": false,
"data": [
{
"id": "evt_Ab12Cd34Ef56Gh",
"object": "event",
"type": "payment.succeeded",
"created": 1760000000,
"data": {"object": {"id": "pay_Ab12Cd34Ef56Gh", "object": "payment", "status": "succeeded"}},
"pending_webhooks": 0,
"delivery": {"attempts": 3, "last_status": 200, "delivered_at": 1760002100, "next_attempt": null}
}
]
}
Ponowna wysyłka: POST /v1/events/{id}/resend#
Wysyła zdarzenie na adres webhooka trybu jeszcze raz, od razu, bez zmiany harmonogramu ponowień. Przydaje się po naprawie odbiorcy albo przy odtwarzaniu pojedynczego przypadku. To samo zrobisz w panelu, w historii zdarzeń. Odbiorca musi być gotowy na powtórkę: zdarzenie przychodzi z tym samym id.
Kolejność i powtórki#
-
Kolejność doręczeń nie jest gwarantowana:
payment.succeededmoże dotrzeć przedpayment.created, zwłaszcza po ponowieniach. Stan obiektu czytaj zdata.objectalbo potwierdź żądaniemGET, nie wnioskuj go z kolejności zdarzeń. -
To samo zdarzenie może przyjść więcej niż raz (ponowienie po timeout, ręczna wysyłka). Zapisuj obsłużone
idi pomijaj duplikaty. -
Zdarzenia z piaskownicy mają
livemode: falsei idą na adres testowy; produkcyjne na produkcyjny. Odbiorca może być jeden, ale sprawdzajlivemode, zanim zmienisz prawdziwe zamówienie. -
Pole
request.idempotency_keypozwala dopasować zdarzenie do operacji w Twoim systemie bez przeszukiwaniametadata.
Typy zdarzeń#
24 typów. Nazwa to obiekt i kropka i co się stało; data.object zawsze niesie pełną migawkę obiektu z chwili zdarzenia. Nowe typy mogą dojść bez zmiany wersji API, dlatego nieznane typy pomijaj zamiast zgłaszać błąd.
Płatności payment.*
| Typ | Kiedy przychodzi |
|---|---|
payment.created |
Płatność utworzona (API, link, faktura albo okres subskrypcji). Klient jeszcze nie zapłacił. |
payment.succeeded |
Płatność opłacona. Na tym zdarzeniu oznaczaj zamówienie jako opłacone. |
payment.failed |
Płatność odrzucona; powód w polu failure_code (np. card_declined, blik_rejected). |
payment.canceled |
Płatność anulowana przez API albo wygasła po expires_at. |
payment.routed |
Smart routing wybrał konto dostawcy; pole provider_account mówi które. |
Linki do płatności payment_link.*
| Typ | Kiedy przychodzi |
|---|---|
payment_link.expired |
Link do płatności przestał przyjmować zapłatę po expires_at. |
Konto sprzedawcy platformy account.*
| Typ | Kiedy przychodzi |
|---|---|
account.verified |
Sprzedawca platformy (Connect) przeszedł weryfikację u dostawcy i może przyjmować płatności. |
Wypłaty payout.*
| Typ | Kiedy przychodzi |
|---|---|
payout.paid |
Dostawca zrealizował wypłatę sprzedawcy platformy (Connect). |
Zwroty refund.*
| Typ | Kiedy przychodzi |
|---|---|
refund.created |
Zwrot zlecony. W piaskownicy od razu po nim przychodzi refund.succeeded. |
refund.succeeded |
Dostawca wykonał zwrot; amount_refunded płatności jest zaktualizowane. |
refund.failed |
Dostawca odrzucił zwrot. Sprawdź saldo i spróbuj ponownie. |
Klienci customer.*
| Typ | Kiedy przychodzi |
|---|---|
customer.created |
Nowy klient (z API albo razem z płatnością). |
customer.updated |
Zmiana danych klienta albo jego metadata. |
customer.deleted |
Klient usunięty; data.object ma deleted: true. |
Subskrypcje subscription.*
| Typ | Kiedy przychodzi |
|---|---|
subscription.created |
Subskrypcja utworzona w statusie incomplete, czeka na opłacenie pierwszego okresu na checkout_url. |
subscription.updated |
Zmiana planu, kuponu, cancel_at_period_end albo metadata; proracja w polu proration. |
subscription.renewed |
Okres odnowiony i opłacony; current_period_end przesunięty. |
subscription.payment_failed |
Odnowienie nieudane, status past_due; Recover ponawia obciążenie i wysyła e-mail z linkiem do dopłaty. |
subscription.recovered |
Recover odzyskał płatność, subskrypcja wraca do active. |
subscription.canceled |
Subskrypcja zakończona (od razu albo z końcem okresu). |
Faktury invoice.*
| Typ | Kiedy przychodzi |
|---|---|
invoice.issued |
Faktura wystawiona (po opłaceniu albo z terminem płatności). |
invoice.paid |
Faktura z terminem opłacona w całości. |
invoice.ksef_accepted |
KSeF przyjął fakturę; numer w polu ksef_number. |
invoice.ksef_rejected |
KSeF odrzucił fakturę; status ksef_status mówi dlaczego. |
Powiązane
Klucz testowy dostajesz od razu po zapisie
Adres e-mail, kod z maila i piaskownica z kodami BLIK. Produkcję włączasz sam w panelu: plan, dostawca płatności, klucz sk_live_. Wolisz, żeby wdrożenie w sklepie zrobił nasz zespół? To też jest za darmo.