BLIK Level 0
BLIK Level 0 is a direct integration where the customer enters a 6-digit BLIK code directly on your website, without being redirected to an external payment gateway. This provides a smoother user experience.
How does BLIK Level 0 work?
Unlike the standard integration (Simple Gateway), the customer is not redirected - the entire process takes place on your website.
Requirements
- An active Payment Point with BLIK support enabled
- A form on your website for entering the BLIK code
- Access to the customer's IP address and User-Agent (required by regulations)
Endpoint
POST https://api-payments.dpay.pl/api/v1_0/payments/register
Content-Type: application/json
Request parameters
The request is analogous to the standard payment registration, with additional BLIK-specific fields:
| Field | Type | Required | Description |
|---|---|---|---|
transactionType | string | Yes | "transfers" |
service | string | Yes | Service name from the panel |
value | string | Yes | Amount in PLN |
url_success | string | Yes | URL after successful payment |
url_fail | string | Yes | URL after failed payment |
url_ipn | string | Yes | URL for IPN notifications |
checksum | string | Yes | SHA-256 checksum |
blik_code | string | Yes | 6-digit BLIK code |
user_ip | string | Yes | Customer's IP address |
user_agent | string | Yes | Customer's User-Agent header |
Generating the checksum
The checksum is generated in the same way as for a standard payment:
sha256({service}|{SecretHash}|{value}|{url_success}|{url_fail}|{url_ipn})
Request example
cURL
curl -X POST https://api-payments.dpay.pl/api/v1_0/payments/register \
-H "Content-Type: application/json" \
-d '{
"transactionType": "transfers",
"service": "abc123",
"value": "29.99",
"url_success": "https://myshop.com/success",
"url_fail": "https://myshop.com/error",
"url_ipn": "https://myshop.com/api/ipn",
"checksum": "e3b0c44298fc1c149afb...",
"blik_code": "123456",
"user_ip": "192.168.1.100",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}'
PHP
<?php
$service = getenv('DPAY_SERVICE');
$secretHash = getenv('DPAY_SECRET_HASH');
$value = '29.99';
$urlSuccess = 'https://myshop.com/success';
$urlFail = 'https://myshop.com/error';
$urlIpn = 'https://myshop.com/api/ipn';
$checksum = hash('sha256',
$service . '|' . $secretHash . '|' . $value . '|' .
$urlSuccess . '|' . $urlFail . '|' . $urlIpn
);
$payload = json_encode([
'transactionType' => 'transfers',
'service' => $service,
'value' => $value,
'url_success' => $urlSuccess,
'url_fail' => $urlFail,
'url_ipn' => $urlIpn,
'checksum' => $checksum,
'blik_code' => $_POST['blik_code'], // Code from the form
'user_ip' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
]);
$ch = curl_init('https://api-payments.dpay.pl/api/v1_0/payments/register');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
JavaScript (Node.js / Express)
const crypto = require('crypto');
const axios = require('axios');
async function processBlikPayment(req, res) {
const service = process.env.DPAY_SERVICE;
const secretHash = process.env.DPAY_SECRET_HASH;
const value = '29.99';
const urlSuccess = 'https://myshop.com/success';
const urlFail = 'https://myshop.com/error';
const urlIpn = 'https://myshop.com/api/ipn';
const checksum = crypto
.createHash('sha256')
.update(`${service}|${secretHash}|${value}|${urlSuccess}|${urlFail}|${urlIpn}`)
.digest('hex');
const response = await axios.post(
'https://api-payments.dpay.pl/api/v1_0/payments/register',
{
transactionType: 'transfers',
service,
value,
url_success: urlSuccess,
url_fail: urlFail,
url_ipn: urlIpn,
checksum,
blik_code: req.body.blik_code,
user_ip: req.ip,
user_agent: req.headers['user-agent'],
}
);
return response.data;
}
API response
Success - payment awaiting confirmation
{
"error": false,
"msg": "Waiting for BLIK confirmation",
"status": "PENDING",
"transactionId": "abc-def-123-456"
}
After receiving this status, the customer must confirm the transaction in their banking app. You will receive the payment result via IPN.
Checking transaction status
In addition to waiting for the IPN notification, you can actively poll the dpay API for the current transaction status. This is useful, for example, when you want to update the user interface in real time.
Endpoint
POST https://panel.dpay.pl/api/v1/pbl/details
Content-Type: application/json
Parameters
| Field | Type | Description |
|---|---|---|
service | string | Service name from the panel |
transaction_id | string | Transaction ID received during registration |
checksum | string | SHA-256 checksum |
Generating the checksum
sha256({service}|{transaction_id}|{SecretHash})
Example response
{
"transaction": {
"id": "abc-def-123-456",
"status": "paid",
"value": "29.99",
"created_at": "2026-03-15T12:00:00Z"
}
}
Possible statuses
| Status | Description |
|---|---|
created | Transaction created, awaiting confirmation |
processing | Transaction is being processed |
paid | Payment completed successfully |
canceled | Transaction canceled |
We recommend polling every 2-3 seconds, for a maximum of 2 minutes. After that, the BLIK code expires.
Full endpoint documentation is available in the API Reference - Transaction Details.
Error - invalid BLIK code
{
"error": true,
"msg": "Transaction canceled",
"status": false,
"transactionId": "42191111-A7AE-392E-8C09-7965C1DC6B0B",
"additionalInfo": {
"error": "ER_WRONG_TICKET",
"error_description": null
}
}
Error handling
| Error code | Description | Action |
|---|---|---|
ER_WRONG_TICKET | Invalid or expired BLIK code | Ask the customer for a new code |
ER_USER_DECLINED | Customer rejected the transaction in their app | Ask to try again |
ER_TIMEOUT | Confirmation timed out | Ask for a new code |
ER_INSUFFICIENT_FUNDS | Insufficient funds | Inform the customer |
BLIK form example (frontend)
<form id="blik-form">
<label for="blik-code">BLIK code</label>
<input
type="text"
id="blik-code"
name="blik_code"
maxlength="6"
pattern="[0-9]{6}"
inputmode="numeric"
placeholder="______"
required
/>
<button type="submit">Pay</button>
</form>
<div id="blik-status" style="display: none;">
<p>Confirm the payment in your banking app...</p>
</div>
<script>
document.getElementById('blik-form').addEventListener('submit', async (e) => {
e.preventDefault();
const blikCode = document.getElementById('blik-code').value;
// Show waiting message
document.getElementById('blik-form').style.display = 'none';
document.getElementById('blik-status').style.display = 'block';
const response = await fetch('/api/pay/blik', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ blik_code: blikCode }),
});
const result = await response.json();
if (result.error) {
alert('Error: ' + result.msg);
document.getElementById('blik-form').style.display = 'block';
document.getElementById('blik-status').style.display = 'none';
}
// Wait for IPN - payment status will change after confirmation
});
</script>
BLIK code validation
A BLIK code is always exactly 6 digits. Validate it on both the client and server side:
// Server-side validation
if (!preg_match('/^\d{6}$/', $blikCode)) {
http_response_code(400);
echo json_encode(['error' => 'BLIK code must consist of 6 digits']);
exit;
}
A BLIK code is valid for 2 minutes from the moment it is generated in the banking app. Make sure the form on your website informs the customer about the need to enter the code quickly.