API bramki SMS
Wysyłaj wiadomości do swoich klientów prosto ze swojego systemu — pojedynczo albo do całej listy, z raportem doręczenia każdej z nich.
Wszystkie żądania wysyłasz w formacie JSON, z nagłówkiem
Content-Type: application/json. Kodowanie to UTF-8.
Odpowiedzi też są w JSON.
Usługę uruchamiasz w panelu klienta, w zakładce Marketing SMS — po akceptacji regulaminu i doładowaniu salda. Klucz do API utworzysz w tej samej zakładce, w sekcji API.
Saldo doładujesz przelewem online albo BLIK-iem — środki są dostępne w kilka sekund po potwierdzeniu płatności, więc integrację możesz uruchomić od razu.
Klucz i limity
Klucz składa się z jawnego prefiksu i sekretu, rozdzielonych kropką. Przekazujesz go w nagłówku każdego żądania:
curl https://paybylink.pl/api/sms/balance/ \
-H 'X-Sms-Key: sms_a1b2c3d4.TWOJ_SEKRET'Możesz też użyć nagłówka Authorization: Bearer prefiks.sekret.
Sekret przechowujemy wyłącznie jako skrót — pokazujemy go raz, przy tworzeniu klucza.
Jeśli go zgubisz, utwórz nowy i unieważnij stary.
Klucz trzymaj na swoim serwerze. Nie umieszczaj go w kodzie strony ani w aplikacji mobilnej — każdy, kto go pozna, może wysyłać wiadomości na Twój koszt.
Limit żądań
Domyślnie 120 żądań na minutę. Aktualny stan zwracamy w nagłówkach:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118Po przekroczeniu limitu dostaniesz kod 429
i błąd rate_limited. Limit możemy podnieść —
napisz, jeśli Twój ruch tego wymaga.
Warianty wysyłki
Wariant decyduje o cenie i o tym, co odbiorca zobaczy w polu nadawcy.
Aktualne ceny Twojego konta zwraca zasób /pricing/.
| Wariant | Nazwa | Pole nadawcy | Zastosowanie |
|---|---|---|---|
| ECO | Podstawowy | numer skrócony operatora | Najtańsza wysyłka masowa, gdy nazwa nadawcy nie ma znaczenia. |
| FULL | Dynamiczny | Twoja nazwa | Powiadomienia i informacje, które mają przyjść od Twojej firmy. |
| PRO | Unikalny | Twoja nazwa | Wiadomości transakcyjne — kody jednorazowe, potwierdzenia. Wyższy priorytet. |
| INTER | Międzynarodowy | Twoja nazwa | Numery spoza Polski. |
| VOICE | Głosowy | zawsze VOICE |
Komunikat odczytywany przez telefon zamiast wiadomości tekstowej. |
Długość wiadomości
Jedna część to 160 znaków alfabetu podstawowego albo 70 znaków, jeśli użyjesz polskich liter lub znaków specjalnych. Dłuższa wiadomość dzieli się na części — każda rozliczana osobno.
| Zestaw znaków | Jedna część | Kolejne części | Maksimum |
|---|---|---|---|
| podstawowy | 160 | 153 | 7 części |
| polskie znaki | 70 | 67 | 16 części |
Łącznie najwyżej 1071 znaków. Odpowiedź zawiera pole
parts oraz encoding,
więc zawsze wiesz, za ile części zapłacisz.
Wysłanie jednej wiadomości
Droga dla wiadomości pilnych — kodów jednorazowych, potwierdzeń zamówienia. Wiadomość trafia do operatora natychmiast.
Pola żądania
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
| to | string | wymagane | Numer odbiorcy. Format krajowy (500100200) albo z prefiksem (48500100200). |
| message | string | wymagane | Treść wiadomości. |
| sender | string | opcjonalne | Nazwa nadawcy. Musi być zatwierdzona na Twoim koncie. Pomijana w wariancie ECO. |
| variant | string | opcjonalne | Wariant wysyłki. Domyślnie PRO. |
| reference | string | opcjonalne | Twój własny identyfikator. Chroni przed podwójną wysyłką i pozwala odpytać o status bez zapamiętywania naszego id. |
curl -X POST https://paybylink.pl/api/sms/send/ \
-H 'X-Sms-Key: sms_a1b2c3d4.TWOJ_SEKRET' \
-H 'Content-Type: application/json' \
-d '{
"to": "500100200",
"message": "Twoj kod: 458213.",
"sender": "MOJSKLEP",
"variant": "PRO",
"reference": "zamowienie-9001"
}'<?php
$payload = array(
'to' => '500100200',
'message' => 'Twoj kod: 458213.',
'sender' => 'MOJSKLEP',
'variant' => 'PRO',
'reference' => 'zamowienie-9001',
);
$ch = curl_init('https://paybylink.pl/api/sms/send/');
curl_setopt_array($ch, array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => array(
'Content-Type: application/json',
'X-Sms-Key: ' . SMS_KEY,
),
));
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if (empty($response['ok'])) {
// $response['error'] i $response['message'] mowia, co poszlo nie tak
}const res = await fetch('https://paybylink.pl/api/sms/send/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Sms-Key': process.env.SMS_KEY,
},
body: JSON.stringify({
to: '500100200',
message: 'Twoj kod: 458213.',
sender: 'MOJSKLEP',
variant: 'PRO',
reference: 'zamowienie-9001',
}),
});
const data = await res.json();import requests
res = requests.post(
'https://paybylink.pl/api/sms/send/',
headers={'X-Sms-Key': SMS_KEY},
json={
'to': '500100200',
'message': 'Twoj kod: 458213.',
'sender': 'MOJSKLEP',
'variant': 'PRO',
'reference': 'zamowienie-9001',
},
)
data = res.json()Odpowiedź
{
"ok": true,
"id": 8412,
"reference": "zamowienie-9001",
"to": "48500100200",
"sender": "MOJSKLEP",
"variant": "PRO",
"parts": 1,
"encoding": "GSM",
"price": "0.1400",
"status": "queued"
}reference.
Jeśli Twoje żądanie nie doczeka odpowiedzi i ponowisz je, powtórka z tym samym
reference zwróci istniejące
id z polem duplicate: true —
nie wyśle wiadomości drugi raz ani nie obciąży salda.
Wysyłka do wielu odbiorców
Jedno żądanie z listą numerów. Odpowiedź wraca od razu, wysyłka rusza w tle.
Pola żądania
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
| to | array | wymagane | Lista numerów. Może być też pojedynczym tekstem z numerami rozdzielonymi przecinkiem. |
| message | string | wymagane | Treść wysyłana do wszystkich odbiorców. |
| name | string | opcjonalne | Nazwa wysyłki, widoczna w Twoim panelu i raportach. |
| sender | string | opcjonalne | Nazwa nadawcy. Pomijana w wariancie ECO. |
| variant | string | opcjonalne | Wariant wysyłki. Domyślnie ECO. |
| sendAt | string | opcjonalne | Termin wysyłki w formacie ISO 8601. Pominięty oznacza natychmiast. |
| reference | string | opcjonalne | Twój identyfikator wysyłki, chroni przed podwójnym zleceniem. |
curl -X POST https://paybylink.pl/api/sms/bulk/ \
-H 'X-Sms-Key: sms_a1b2c3d4.TWOJ_SEKRET' \
-H 'Content-Type: application/json' \
-d '{
"name": "Powiadomienia o wysylce",
"to": ["500100200", "501300400"],
"message": "Twoje zamowienie zostalo wyslane.",
"sender": "MOJSKLEP",
"variant": "FULL",
"sendAt": "2026-09-01T10:00:00+02:00"
}'{
"ok": true,
"id": 331,
"name": "Powiadomienia o wysylce",
"sender": "MOJSKLEP",
"variant": "FULL",
"recipients": 2,
"rejected": 0,
"optedOut": 0,
"parts": 1,
"cost": "0.24",
"sendAt": "2026-09-01T10:00:00+02:00",
"status": "queued"
}Pole rejected mówi, ile wpisów nie było poprawnym numerem,
a optedOut — ile pominęliśmy, bo są na Twojej liście
wypisanych. Powtórzone numery odfiltrowujemy automatycznie.
Status wiadomości
Możesz odpytać po reference albo po
id z odpowiedzi na wysyłkę.
{
"ok": true,
"id": 8412,
"reference": "zamowienie-9001",
"to": "48500100200",
"status": "delivered",
"operator": "MESSAGE_DELIVERED",
"parts": 1,
"price": "0.1400",
"createdAt": "2026-09-01T10:00:00+02:00",
"sentAt": "2026-09-01T10:00:02+02:00",
"deliveredAt": "2026-09-01T10:00:09+02:00"
}Możliwe statusy
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
| queued | status | opcjonalne | Przyjęta, czeka na przekazanie operatorowi. |
| sent | status | opcjonalne | Przekazana do sieci operatora. |
| delivered | status | opcjonalne | Potwierdzone doręczenie na telefon odbiorcy. |
| undelivered | status | opcjonalne | Operator zgłosił, że nie udało się doręczyć. |
| rejected | status | opcjonalne | Odrzucona przed wysłaniem. Opłata została zwrócona. |
Status wysyłki do wielu odbiorców
Dodaj &recipients=1, żeby dostać listę odbiorców
ze statusem każdego numeru — po 500 na stronę, kolejne przez
&page=2.
{
"ok": true,
"id": 331,
"name": "Powiadomienia o wysylce",
"status": "done",
"recipients": 2,
"sent": 2,
"delivered": 2,
"failed": 0,
"charged": "0.24",
"finishedAt": "2026-09-01T10:01:11+02:00"
}Saldo i cennik
{
"ok": true,
"balance": "850.00",
"reserved": "120.00",
"available": "730.00",
"currency": "PLN",
"net": true,
"prices": { "ECO": "0.1100", "FULL": "0.1200" },
"limits": { "daily": 0, "monthly": 0, "perMinute": 120 }
}reserved to kwota zablokowana na wysyłki, które jeszcze
trwają. Do dyspozycji masz available.
Wszystkie kwoty są netto.
Zasób /pricing/ zwraca warianty z cenami i informacją,
czy dany wariant honoruje Twoją nazwę nadawcy.
Nazwy nadawcy
{
"ok": true,
"senders": [
{ "name": "INFORMACJA", "type": "shared", "status": "approved" },
{ "name": "MOJSKLEP", "type": "own", "status": "approved" }
]
}Nazwy typu shared są dostępne od razu i dzielisz je
z innymi nadawcami. Własną nazwę firmy zgłaszasz w panelu — sprawdzamy prawo do niej
i zatwierdzamy. Nazw zastrzeżonych dla banków, urzędów i firm kurierskich
nie zatwierdzamy.
Lista wypisanych
Numery, które zgłosiły rezygnację. Filtrujemy przez tę listę każdą wysyłkę tuż przed przekazaniem jej operatorowi — także wtedy, gdy numer zostanie w Twojej bazie.
curl -X POST https://paybylink.pl/api/sms/optout/ \
-H 'X-Sms-Key: sms_a1b2c3d4.TWOJ_SEKRET' \
-H 'Content-Type: application/json' \
-d '{"to": ["500100200", "501300400"]}'{ "ok": true, "added": 2, "rejected": 0 }Sprawdzenie pojedynczego numeru
{ "ok": true, "to": "48500100200", "optedOut": true }Wywołanie bez parametru to zwraca całą listę,
po 500 pozycji na stronę.
Raporty doręczeń
Podaj w panelu adres, pod który mamy wysyłać powiadomienia. Damy znać, gdy wiadomość dotrze do odbiorcy albo gdy wysyłka masowa się zakończy — nie musisz nas odpytywać.
Adres musi zaczynać się od https://.
Powiadomienie o wiadomości
{
"event": "message.status",
"id": 8412,
"reference": "zamowienie-9001",
"to": "48500100200",
"sender": "MOJSKLEP",
"variant": "PRO",
"status": "delivered",
"operator": "MESSAGE_DELIVERED",
"price": "0.1400",
"deliveredAt": "2026-09-01T10:00:09"
}Powiadomienie o wysyłce masowej
{
"event": "campaign.finished",
"id": 331,
"reference": null,
"name": "Powiadomienia o wysylce",
"status": "done",
"recipients": 2,
"sent": 2,
"delivered": 2,
"failed": 0,
"charged": "0.24"
}Odpowiedz kodem z zakresu 2xx, żeby potwierdzić odbiór.
Jeśli nie odpowiesz, ponawiamy z rosnącym odstępem — do ośmiu prób.
Weryfikacja podpisu
Każde powiadomienie ma nagłówek X-Sms-Signature.
To skrót HMAC-SHA256 z surowej treści żądania, podpisany sekretem widocznym
w panelu przy adresie powiadomień.
<?php
$raw = file_get_contents('php://input');
$sent = isset($_SERVER['HTTP_X_SMS_SIGNATURE']) ? $_SERVER['HTTP_X_SMS_SIGNATURE'] : '';
$calc = hash_hmac('sha256', $raw, SMS_WEBHOOK_SECRET);
if (!hash_equals($calc, $sent)) {
http_response_code(403);
exit;
}
$event = json_decode($raw, true);
// tu aktualizujesz status u siebie
http_response_code(200);
echo 'OK';import crypto from 'crypto';
// body musi byc surowym tekstem, nie sparsowanym JSON-em
function verify(rawBody, signature, secret) {
const calc = crypto.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(calc),
Buffer.from(signature)
);
}import hmac, hashlib
def verify(raw_body, signature, secret):
calc = hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(calc, signature)Podpisujemy surową treść żądania, dokładnie taką, jaka przyszła. Jeśli najpierw sparsujesz JSON, a potem zapiszesz go z powrotem, podpis się nie zgodzi.
Kody błędów
Każda odmowa ma ten sam kształt — kod maszynowy i wyjaśnienie:
{
"ok": false,
"error": "no_funds",
"message": "Za malo srodkow na koncie. Doladuj saldo bramki SMS."
}Sprawdzaj pole ok — wartość
false zawsze oznacza, że nic nie zostało wysłane
ani obciążone.
| Kod | HTTP | Znaczenie |
|---|---|---|
| no_key | 401 | Brak klucza w nagłówku. |
| bad_key | 401 | Klucz nieprawidłowy albo unieważniony. |
| account_off | 403 | Bramka nieaktywna na koncie. |
| sender_not_allowed | 403 | Nazwa nadawcy niezatwierdzona. |
| variant_unavailable | 403 | Wariant niedostępny na koncie. |
| no_funds | 402 | Za mało środków na saldzie. |
| bad_number | 400 | Numer odbiorcy nieprawidłowy. |
| message_rejected | 400 | Treść za długa albo za wiele części. |
| bad_json | 400 | Treść żądania nie jest poprawnym JSON-em. |
| opted_out | 409 | Numer na liście wypisanych. |
| forbidden_topic | 422 | Treść z kategorii, której operator nie przyjmuje. |
| rate_limited | 429 | Za dużo żądań na minutę. |
| limit_reached | 429 | Przekroczony limit dzienny albo miesięczny. |
| not_found | 404 | Nie znaleziono wiadomości albo wysyłki. |
| unavailable | 503 | Bramka chwilowo niedostępna. |
Zasady wysyłki
Zgoda odbiorcy
Wiadomość handlową lub marketingową możesz wysłać wyłącznie do osób, które wyraziły na to zgodę. Obowiązek jej uzyskania i udokumentowania spoczywa na Tobie. W treści marketingowej podaj czytelny sposób rezygnacji.
Odpowiedź STOP
Gdy odbiorca odpisze STOP, dopisujemy go do Twojej listy wypisanych i pomijamy w kolejnych wysyłkach. Lista dotyczy wyłącznie Twoich wiadomości — nie przenosi się na innych nadawców.
Czego nie przyjmiemy
Operator sieci nie przepuszcza wysyłek promujących hazard, alkohol, wyroby tytoniowe,
produkty lecznicze i usługi medyczne, broń, treści dla dorosłych ani zbiórek
charytatywnych. Takie żądania odrzucamy kodem
forbidden_topic, zanim cokolwiek wyjdzie.
Odrzucamy też wiadomości podszywające się pod inny podmiot, zmierzające do wyłudzenia danych lub płatności oraz zawierające numery o podwyższonej opłacie.
Rozliczenie
Płacisz za wiadomości przyjęte do wysyłki. Za wiadomości, których operator w ogóle nie przyjął, opłata jest zwracana. Za nieodebrane — na przykład gdy telefon był wyłączony — opłaty nie zwracamy; wynik doręczenia widzisz w raporcie.