Skip to main content

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):

EventWhen
startedThe draft was created.
in_progressOnboarding is in progress (data / personal actions).
agreement_signedThe merchant signed the agreement.
pending_transferThe account is awaiting a verification transfer.
under_reviewdpay is performing a manual review.
completedThe account is active.
blockedThe account is blocked.

Payments and payouts

EventWhen
payment.succeededThe payment was completed.
payment.failedThe payment failed or was cancelled.
payout.paidThe payout was sent to the merchant's bank account.
payout.failedThe payout was rejected.

Delegation and reverification

EventWhen
authorization.revokedThe merchant revoked delegation. Revocation covers all permissions at once and takes effect immediately.
reverification_submittedThe merchant submitted data for periodic reverification.

Invitations (API onboarding)

EventWhen
invitation.identity_verifiedThe invited person verified their identity (eID path only; do not rely on this event because the manual path does not emit it).
invitation.completedThe invited person completed their flow - the only guaranteed successful terminal event for an invitation.
invitation.signedThe invited representative signed the agreement.

Partner margin payout

EventWhen
partner_payout.paiddpay paid out your margin (see ISV settlements).
partner_payout.declinedThe margin payout was rejected.

partner_payout.* events concern you (the partner), not a merchant, so their payload does not contain a ref field.

New event types

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 from partner_payout.*).
  • status in 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, and invitation_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>
Sign the raw body

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 2xx immediately. 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_id so 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.

Partner portal - merchant list and webhook status
The partner portal shows webhook delivery performance. The delivery log and dead-letter queue help diagnose endpoint problems.
Secret rotation

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