Webhooks
Webhooks are your source of truth for events in dpay Connect. Instead of polling the API, you receive a push request at your endpoint whenever an onboarding, payment, payout, or delegation status changes.
Configuration
Configure the endpoint URL (webhook_url) and webhook secret during partner onboarding or in the partner portal. The secret is used to verify signatures. Store it securely; you can rotate it at any time.
Event catalogue
Merchant onboarding
Onboarding events have the same names as account statuses (see the account lifecycle):
| Event | When |
|---|---|
started | The draft was created. |
in_progress | Onboarding is in progress (data / personal actions). |
agreement_signed | The merchant signed the agreement. |
pending_transfer | The account is awaiting a verification transfer. |
under_review | dpay is performing a manual review. |
completed | The account is active. |
blocked | The account is blocked. |
Payments and payouts
| Event | When |
|---|---|
payment.succeeded | The payment was completed. |
payment.failed | The payment failed or was cancelled. |
payout.paid | The payout was sent to the merchant's bank account. |
payout.failed | The payout was rejected. |
Delegation and reverification
| Event | When |
|---|---|
authorization.revoked | The merchant revoked delegation. Revocation covers all permissions at once and takes effect immediately. |
reverification_submitted | The merchant submitted data for periodic reverification. |
Invitations (API onboarding)
| Event | When |
|---|---|
invitation.identity_verified | The invited person verified their identity (eID path only; do not rely on this event because the manual path does not emit it). |
invitation.completed | The invited person completed their flow - the only guaranteed successful terminal event for an invitation. |
invitation.signed | The invited representative signed the agreement. |
Partner margin payout
| Event | When |
|---|---|
partner_payout.paid | dpay paid out your margin (see ISV settlements). |
partner_payout.declined | The margin payout was rejected. |
partner_payout.* events concern you (the partner), not a merchant, so their payload does not contain a ref field.
The catalogue may grow. Design your handler to ignore an unknown event instead of failing. Do not assume the list is closed.
Payload format
Every webhook is a POST request with a flat JSON body. The event field contains the event type, and all other fields are at the top level; there is no nested data object.
Onboarding status change:
POST /webhooki/dpay HTTP/1.1
Content-Type: application/json
X-Dpay-Signature: sha256=3a7bd3e2360a...
X-Dpay-Timestamp: 1782561600
{
"event": "agreement_signed",
"ref": "invoice-customer-123",
"status": "agreement_signed",
"channel": "fakturka",
"occurred_at": "2026-07-01T10:00:00+00:00",
"event_id": "12345:agreement_signed"
}
Payment:
{
"event": "payment.succeeded",
"ref": "invoice-customer-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"
}
Payout:
{
"event": "payout.paid",
"ref": "invoice-customer-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- event type (for onboarding events, it is the status name).ref- your reference for the merchant that the event concerns (absent frompartner_payout.*).statusin payment events is the numeric dpay transaction status (see transaction statuses).event_id- unique event identifier. Deduplicate by this value, because the same event may be delivered more than once.- Invitation events also include
invitation_ref,invitation_role, andinvitation_status; their payload contains no personal data about the invitee.
Signature verification (HMAC)
Always verify the signature before trusting the webhook payload. dpay signs every webhook with the X-Dpay-Signature header.
The signature is an HMAC-SHA256 calculated with the webhook secret over the "{timestamp}.{raw_body}" string, where timestamp comes from the X-Dpay-Timestamp header:
signature = HMAC_SHA256(secret, timestamp + "." + raw_body)
X-Dpay-Signature: sha256=<signature_hex>
Calculate the HMAC from the raw request body exactly as received, byte for byte. Do not parse and re-serialize the JSON before verification, because any change in whitespace or key order invalidates the signature. The timestamp is part of the signature and also protects against replay attacks.
Example: PHP
function verifyDpayWebhook(string $rawBody, array $headers, string $secret): bool
{
$timestamp = $headers['X-Dpay-Timestamp'] ?? '';
$received = $headers['X-Dpay-Signature'] ?? '';
// Reject an old timestamp (replay protection)
if (abs(time() - (int) $timestamp) > 300) {
return false;
}
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
return hash_equals($expected, $received);
}
Example: 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; // Too old - possible replay
}
const expected =
'sha256=' +
crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}
Respond quickly and idempotently
- Return
2xximmediately. Perform heavy work (database updates or email delivery) asynchronously after returning the response. A slow response is treated as a failure and the webhook is retried. - Be idempotent. The same webhook may arrive more than once. Deduplicate by
event_idso that, for example, you do not post the same payment twice.
Retries and dead-letter queue
If your endpoint does not return 2xx within the 10-second response timeout, dpay retries delivery up to 5 total attempts, with delays of 10 s, 60 s, 300 s, and 900 s. After all attempts are exhausted, the event goes to the dead-letter queue for failed deliveries. You can inspect it and the delivery log in the partner portal.

You can rotate the webhook secret in the partner portal. Update the secret on your side during rotation. If you suspect a leak, rotate it immediately; the old secret will no longer sign new deliveries.
Next steps
- Account lifecycle - the meaning of statuses in onboarding events.
- On-behalf payments -
payment.*events. - On-behalf payouts -
payout.*events.