Skip to main content

Webhooki

Webhooki to Twoje źródło prawdy o zdarzeniach w dpay Connect. Zamiast odpytywać API w pętli, otrzymujesz push na swój endpoint zawsze, gdy zmieni się status onboardingu, płatności, wypłaty lub delegacji.

Konfiguracja

Adres endpointu (webhook_url) i sekret webhooka ustawiasz przy onboardingu partnera lub w portalu partnera. Sekret służy do weryfikacji podpisu - trzymaj go bezpiecznie, możesz go w każdej chwili zrotować.

Katalog zdarzeń

Onboarding merchanta

Zdarzenia onboardingu nazywają się tak samo jak status, który przyjmuje konto (patrz cykl życia):

ZdarzenieKiedy
startedDraft utworzony.
in_progressOnboarding w toku (dane / akty osobiste).
agreement_signedMerchant podpisał umowę.
pending_transferOczekiwanie na przelew weryfikacyjny.
under_reviewWeryfikacja manualna dpay.
completedKonto aktywne.
blockedKonto zablokowane.

Płatności i wypłaty

ZdarzenieKiedy
payment.succeededPłatność opłacona.
payment.failedPłatność nieudana lub anulowana.
payout.paidWypłata zrealizowana na rachunek merchanta.
payout.failedWypłata odrzucona.

Delegacja i reweryfikacja

ZdarzenieKiedy
authorization.revokedMerchant cofnął delegację. Cofnięcie obejmuje wszystkie zakresy naraz i działa natychmiast.
reverification_submittedMerchant złożył dane w reweryfikacji okresowej.

Zaproszenia (onboarding przez API)

ZdarzenieKiedy
invitation.identity_verifiedZaproszona osoba zweryfikowała tożsamość (tylko ścieżka eID - nie polegaj na tym zdarzeniu, ścieżka manualna go nie emituje).
invitation.completedZaproszona osoba ukończyła swój przepływ - jedyny gwarantowany terminal sukcesu zaproszenia.
invitation.signedZaproszony reprezentant podpisał umowę.

Wypłata marży partnera

ZdarzenieKiedy
partner_payout.paiddpay wypłacił Twoją marżę (patrz rozliczenia ISV).
partner_payout.declinedWypłata marży odrzucona.

Zdarzenia partner_payout.* dotyczą Ciebie (partnera), nie merchanta - ich payload nie zawiera pola ref.

Nowe typy zdarzeń

Katalog może się rozszerzać. Zaprojektuj handler tak, aby nieznane event było ignorowane (a nie powodowało błędu). Nie zakładaj zamkniętej listy.

Format ładunku

Każdy webhook to POST z płaskim JSON-em. Typ zdarzenia niesie pole event; pozostałe pola leżą na najwyższym poziomie (nie ma zagnieżdżonego obiektu data).

Zmiana statusu onboardingu:

POST /webhooki/dpay HTTP/1.1
Content-Type: application/json
X-Dpay-Signature: sha256=3a7bd3e2360a...
X-Dpay-Timestamp: 1782561600

{
"event": "agreement_signed",
"ref": "faktura-klient-123",
"status": "agreement_signed",
"channel": "fakturka",
"occurred_at": "2026-07-01T10:00:00+00:00",
"event_id": "12345:agreement_signed"
}

Płatność:

{
"event": "payment.succeeded",
"ref": "faktura-klient-123",
"payment_id": "8F2A11C4-...",
"value": 149.00,
"currency": "PLN",
"status": 1,
"channel": "fakturka",
"occurred_at": "2026-07-01T10:05:00+00:00",
"event_id": "pay:8F2A11C4-...:payment.succeeded"
}

Wypłata:

{
"event": "payout.paid",
"ref": "faktura-klient-123",
"payout_id": 98765,
"net": 3480.50,
"currency": "PLN",
"channel": "fakturka",
"occurred_at": "2026-07-02T09:00:00+00:00",
"event_id": "payout:98765:payout.paid"
}
  • event - typ zdarzenia (dla zdarzeń onboardingu równy nazwie statusu).
  • ref - Twoja referencja merchanta, którego dotyczy zdarzenie (brak w partner_payout.*).
  • status w zdarzeniach płatności to numeryczny status transakcji dpay (patrz statusy transakcji).
  • event_id - unikalny identyfikator zdarzenia. Deduplikuj po nim - to samo zdarzenie może zostać dostarczone więcej niż raz.
  • Zdarzenia zaproszeń niosą dodatkowo invitation_ref, invitation_role, invitation_status (payload bez danych osobowych zaproszonego).

Weryfikacja podpisu (HMAC)

Zawsze weryfikuj podpis, zanim zaufasz treści webhooka. dpay podpisuje każdy webhook nagłówkiem X-Dpay-Signature.

Podpis liczony jest jako HMAC-SHA256 z sekretu webhooka nad ciągiem "{timestamp}.{surowe_body}", gdzie timestamp pochodzi z nagłówka X-Dpay-Timestamp:

signature = HMAC_SHA256(secret, timestamp + "." + raw_body)
X-Dpay-Signature: sha256=<signature_hex>
Podpisuj surowe body

Licz HMAC na surowej treści żądania, dokładnie tak jak przyszła (bajt w bajt). Nie parsuj JSON-a i nie serializuj go ponownie przed weryfikacją - zmiana białych znaków czy kolejności kluczy zepsuje podpis. Timestamp jest częścią podpisu, więc chroni też przed atakiem replay.

Przykład: PHP

function verifyDpayWebhook(string $rawBody, array $headers, string $secret): bool
{
$timestamp = $headers['X-Dpay-Timestamp'] ?? '';
$received = $headers['X-Dpay-Signature'] ?? '';

// odrzuć zbyt stary timestamp (ochrona przed replay)
if (abs(time() - (int) $timestamp) > 300) {
return false;
}

$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);

return hash_equals($expected, $received);
}

Przykład: Node.js

const crypto = require('crypto');

function verifyDpayWebhook(rawBody, headers, secret) {
const timestamp = headers['x-dpay-timestamp'] || '';
const received = headers['x-dpay-signature'] || '';

if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return false; // za stary - możliwy replay
}

const expected =
'sha256=' +
crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');

return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}

Odpowiadaj szybko i idempotentnie

  • Odpowiadaj 2xx od razu. Ciężką pracę (aktualizacja bazy, wysyłka maili) zrób asynchronicznie po zwróceniu odpowiedzi. Zbyt wolna odpowiedź jest traktowana jak błąd i webhook zostanie ponowiony.
  • Bądź idempotentny. Ten sam webhook może przyjść więcej niż raz. Deduplikuj po event_id - nie księguj tej samej płatności dwa razy.

Ponawianie i dead-letter

Jeśli Twój endpoint nie odpowie 2xx (limit czasu odpowiedzi: 10 sekund), dpay ponawia dostarczenie - łącznie do 5 prób, z odstępami 10 s, 60 s, 300 s i 900 s. Po wyczerpaniu prób zdarzenie trafia do dead-letter (kolejki nieudanych dostaw) i możesz je podejrzeć w portalu partnera razem z logiem dostaw.

Portal partnera - lista merchantów i status webhooków
Portal partnera pokazuje m.in. skuteczność dostarczania webhooków. Log dostaw i dead-letter pomaga diagnozować problemy z endpointem.
Rotacja sekretu

Sekret webhooka możesz zrotować w portalu partnera. Podczas rotacji zaktualizuj sekret po swojej stronie. Jeśli podejrzewasz wyciek - zrotuj natychmiast; stary sekret przestanie podpisywać nowe dostawy.

Co dalej