Skip to main content

Premium SMS

Premium SMS is a micropayment method where the customer sends a text message to a special number and the cost is added to their phone bill. dpay.pl handles verification of the return codes.

How it works

GET/api/v1/sms/tariffsFull list of available Premium SMS tariffs.Full contract in the API Reference GET/api/v1/sms/verify/{client}/{service}/{code}SMS code verification contract: parameters and response codes.Full contract in the API Reference

Available endpoints

EndpointMethodDescription
/api/v1/sms/tariffsGETList of all available SMS tariffs
/api/v1/sms/verify/{client}/{service}/{code}GETSMS code verification

Step 1: Retrieve the tariff list

Fetch the available SMS tariffs so you can show the customer the right number and message text:

GET https://panel.dpay.pl/api/v1/sms/tariffs

Example response

The response is a flat array of objects - it is not wrapped in any key:

[
{
"id": 1,
"number": "7043",
"vat": "0.65",
"netto": "0.50",
"public": 1,
"18plus": 0
},
{
"id": 3,
"number": "72240",
"vat": "2.46",
"netto": "2.00",
"public": 1,
"18plus": 0
},
{
"id": 24,
"number": "91986",
"vat": "23.37",
"netto": "19.00",
"public": 1,
"18plus": 1
}
]

Tariff fields

FieldDescription
idTariff identifier. Code verification returns it in the tariff field.
numberSMS number the customer sends the message to
nettoNet amount in PLN
vatGross amount in PLN - the cost paid by the customer
public1 - tariff is publicly available
18plus1 - tariff requires age confirmation
The response is not filtered

The endpoint returns all rows of the tariff table, including adult-content tariffs (18plus: 1) and any tariffs withdrawn from the public offer (public: 0). Filter them out on your side before showing the list to a customer.

vat is an amount, not a rate

Despite its name, vat does not hold a percentage rate (23%) - it holds the gross amount in PLN. Compute the VAT amount as vat - netto.

Step 2: Show the instructions to the customer

Based on the selected tariff, show the customer how to send the message:

<div class="sms-instruction">
<p>To pay, send a text message with the following content:</p>
<p class="sms-text"><strong>DPAY.ABC123</strong></p>
<p>to the number:</p>
<p class="sms-number"><strong>72240</strong></p>
<p>Cost: 2.46 PLN gross</p>
</div>

<form id="sms-verify-form">
<label for="sms-code">Enter the code from the return SMS:</label>
<input type="text" id="sms-code" name="code" placeholder="abcdefgh" required />
<button type="submit">Verify code</button>
</form>
SMS content

The SMS content is the tariff prefix, a dot and your service name: DPAY.{service}. The exact content is configured in the dpay.pl panel.

Step 3: Verify the SMS code

Once you have the code from the customer, verify it through the API:

GET https://panel.dpay.pl/api/v1/sms/verify/{client}/{service}/{code}
Verification consumes the code

A successful verification marks the code as used and records the IP and usage date. Calling it again for the same code returns err3. Treat this call as a write operation rather than a status read - call it exactly once and persist the result on your side.

URL parameters

ParameterDescriptionExample
clientNumeric client identifier from the panel1042
serviceNumeric identifier of the registered SMS service77
codeCode from the return SMS - eight lowercase lettersabcdefgh

Example request

curl -X GET "https://panel.dpay.pl/api/v1/sms/verify/1042/77/abcdefgh"

Response - valid code

HTTP 200:

{
"status": true,
"msisdn": "48601234567",
"code": "abcdefgh",
"tariff": 3,
"number": "72240",
"vat": "2.46",
"net": "2.00",
"net_gross": "1.20",
"revenue": "60.00"
}
FieldDescription
statustrue - the code was valid and has just been consumed
msisdnPhone number the SMS was sent from
codeThe verified code
tariffTariff id (matches id from the tariff list)
numberSMS number of the tariff
vatGross amount of the tariff - the customer's cost
netNet amount of the tariff
net_grossYour revenue: net × revenue / 100
revenueYour percentage share of the net amount, as it stood when the SMS was sent

Responses - errors

Error responses share a common shape with an errorcode field. Branch on status, not on error and not on the HTTP status code - error is false for two of the three errors:

errorcodeHTTPerrormessageCause
err1400trueCould not find required parameters or they are wrongMalformed parameters, or no SMS history at all for the client + service pair
err2200falseCode does not existsThe code does not exist for this client + service pair
err3200falseCode usedThe code has already been consumed
{
"status": false,
"error": false,
"errorcode": "err3",
"message": "Code used"
}

Full example - PHP

<?php
$clientId = getenv('DPAY_SMS_CLIENT_ID'); // numeric client ID
$service = getenv('DPAY_SMS_SERVICE_ID'); // numeric SMS service ID

$code = $_POST['code'] ?? '';

if (!preg_match('/^[a-z]{8}$/', $code)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid code format']);
exit;
}

$url = sprintf(
'https://panel.dpay.pl/api/v1/sms/verify/%s/%s/%s',
urlencode($clientId),
urlencode($service),
urlencode($code)
);

$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
]);

$response = curl_exec($ch);
curl_close($ch);

$result = json_decode($response, true);

// The status field is the only reliable indicator - the HTTP code is 200
// for the err2 and err3 errors as well.
if (($result['status'] ?? false) === true) {
activateService($_POST['user_id'], $result['net_gross']);
echo json_encode(['success' => true, 'msg' => 'Payment verified']);
} elseif (($result['errorcode'] ?? '') === 'err3') {
echo json_encode(['error' => true, 'msg' => 'Code already used']);
} else {
echo json_encode(['error' => true, 'msg' => 'Invalid SMS code']);
}

Full example - Node.js

const axios = require('axios');

app.post('/api/verify-sms', async (req, res) => {
const { code } = req.body;
const clientId = process.env.DPAY_SMS_CLIENT_ID; // numeric client ID
const service = process.env.DPAY_SMS_SERVICE_ID; // numeric SMS service ID

if (!/^[a-z]{8}$/.test(code)) {
return res.status(400).json({ error: 'Invalid code format' });
}

try {
// validateStatus - err1 comes back with HTTP 400, so we do not want a throw
const response = await axios.get(
`https://panel.dpay.pl/api/v1/sms/verify/${clientId}/${service}/${code}`,
{ validateStatus: (s) => s === 200 || s === 400 }
);

const result = response.data;

if (result.status === true) {
await activateService(req.user.id, result.net_gross);
res.json({ success: true, msg: 'Payment verified' });
} else if (result.errorcode === 'err3') {
res.json({ error: true, msg: 'Code already used' });
} else {
res.json({ error: true, msg: 'Invalid SMS code' });
}
} catch (error) {
res.status(500).json({ error: true, msg: 'Verification failed' });
}
});

Best practices

1. One-time codes

Every SMS code can be used only once, and the verification call itself consumes it. Do not call this endpoint in a loop or on page refresh - persist the result of the first call and make your decisions from that.

2. Validate the code format

Validate the code format before calling the API:

if (!preg_match('/^[a-z]{8}$/', $code)) {
// The code does not match the required format
}

3. Store the verifications

Record verified codes in your database so you can resolve any future complaints:

$stmt = $pdo->prepare('INSERT INTO sms_payments (code, net_gross, number, msisdn, user_id, verified_at) VALUES (?, ?, ?, ?, ?, NOW())');
$stmt->execute([$code, $result['net_gross'], $result['number'], $result['msisdn'], $userId]);

Common errors

errorcodeCauseResolution
err1Malformed parameters, or no SMS history for the client + service pairCheck that client and service are the numeric identifiers from the panel, not names
err2The code does not existAsk the customer to re-check the code from the return SMS
err3The code has already been usedExplain that the code is single-use
tip

Premium SMS works best as a micropayment method for digital content, premium access or virtual items in games.