Серверные SDK и примеры
Серверный SDK живёт на вашем бэкенде и работает с секретным ключом приложения (pd_…, Приложения → Настройки → API-ключи): отправка уведомлений, устройства, контакты, запуск сценариев, серверные отметки доставки, плюс два помощника для клиентских SDK — хэш идентичности (X-Identity-Hash) и проверка подписи вебхуков. Секретный ключ никогда не попадает в приложение или на страницу.
Пакеты публикуются. Пока серверные SDK не появились в реестрах (npm, PyPI, Packagist, RubyGems, Maven Central, NuGet, Go), возьмите исходники из каталога integrations/sdk/<язык> репозитория PushData — каждый пакет собирается штатным инструментом языка и подключается локально. API тот же; примеры ниже не меняются.Источник правды — спецификация OpenAPI 3.1: api.pushdata.ru/openapi/v1.yaml. Все SDK — тонкие клиенты с одинаковыми именами операций, полями как на проводе (snake_case), типизированной ошибкой с кодом сервиса, повтором с задержкой при 429 / 5xx / сетевом сбое и автоматическим Idempotency-Key на каждую отправку — повтор никогда не доставит дважды.
Пакеты
| Язык | Пакет | Установка |
|---|---|---|
| Node.js / TypeScript | @pushdata/node | npm install @pushdata/node |
| Python 3.9+ | pushdata | pip install pushdata |
| PHP 8.1+ | pushdata/pushdata | composer require pushdata/pushdata |
| Go 1.21+ | github.com/pushdata-ru/pushdata-go | go get github.com/pushdata-ru/pushdata-go |
| Ruby 3.0+ | pushdata | gem install pushdata |
| Java 11+ / Kotlin | ru.pushdata:pushdata-java | Maven / Gradle |
| .NET 8+ / C# | PushData | dotnet add package PushData |
Готовые обвязки для фреймворков и CMS (Laravel, Symfony, Django, Rails, Spring Boot, NestJS, ASP.NET Core, WordPress, 1С-Битрикс, Tilda) — в разделе Интеграции.
Операции (имена одинаковы во всех SDK, в стиле языка): sendNotification, getNotification, cancelNotification, listDevices, registerDevice, unregisterDeviceByToken, updateDeviceTags, deleteDevice, upsertContact, getContact, deleteContact, triggerWorkflow, trackDelivery; помощники identityHash(secret, userId) и verifyWebhookSignature(rawBody, header, secret).
Одна отправка на каждом языке
Push пользователю user-123 с данными и ссылкой. Тело запроса одинаково: title, body, target (ровно один из all, user_ids, tags, device_ids), data, click_action; ответ — { "id", "status" }. Письмо, SMS, сообщение в MAX или in-app — тот же вызов с channel_type (email, sms, max, in_app; telegram — в чат канала) и target из контактов (all, user_ids, contact_ids); необязательные channel_id и category — см. Уведомления.
cURL
curl -X POST https://api.pushdata.ru/api/v1/notifications \
-H "Authorization: ApiKey pd_ВАШ_СЕКРЕТНЫЙ_КЛЮЧ" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-123-placed" \
-d '{"title":"Заказ оформлен","body":"Собираем заказ №123.","target":{"user_ids":["user-123"]},"data":{"orderId":"123"},"click_action":"myapp://orders/123"}'Node.js / TypeScript
import { PushData } from '@pushdata/node'
const pushdata = new PushData({ apiKey: process.env.PUSHDATA_API_KEY! })
const { id } = await pushdata.sendNotification({
title: 'Заказ оформлен',
body: 'Собираем заказ №123.',
target: { user_ids: ['user-123'] },
data: { orderId: '123' },
click_action: 'myapp://orders/123',
}, { idempotencyKey: 'order-123-placed' })
// То же письмом: адрес берётся из контакта user-123
await pushdata.sendNotification({
channel_type: 'email',
category: 'orders',
title: 'Заказ №123 оформлен',
body: 'Собираем заказ, курьер позвонит перед доставкой.',
target: { user_ids: ['user-123'] },
}, { idempotencyKey: 'order-123-placed-email' })Python
from pushdata import PushData
pushdata = PushData(api_key=os.environ["PUSHDATA_API_KEY"])
created = pushdata.send_notification(
title="Заказ оформлен", body="Собираем заказ №123.",
target={"user_ids": ["user-123"]}, data={"orderId": "123"}, click_action="myapp://orders/123",
idempotency_key="order-123-placed",
)PHP
use PushData\PushData;
$pushdata = new PushData($_ENV['PUSHDATA_API_KEY']);
$created = $pushdata->sendNotification([
'title' => 'Заказ оформлен', 'body' => 'Собираем заказ №123.',
'target' => ['user_ids' => ['user-123']], 'data' => ['orderId' => '123'], 'click_action' => 'myapp://orders/123',
], 'order-123-placed');Go
client := pushdata.New(os.Getenv("PUSHDATA_API_KEY"))
created, err := client.SendNotification(ctx, pushdata.SendNotificationRequest{
Title: "Заказ оформлен", Body: "Собираем заказ №123.",
Target: pushdata.Target{UserIDs: []string{"user-123"}},
Data: map[string]any{"orderId": "123"}, ClickAction: "myapp://orders/123",
IdempotencyKey: "order-123-placed",
})Java
PushData pushdata = new PushData(System.getenv("PUSHDATA_API_KEY"));
Map<String, Object> created = pushdata.sendNotification(Map.of(
"title", "Заказ оформлен", "body", "Собираем заказ №123.",
"target", Map.of("user_ids", List.of("user-123")),
"data", Map.of("orderId", "123"), "click_action", "myapp://orders/123"), "order-123-placed");Kotlin
val pushdata = PushData(System.getenv("PUSHDATA_API_KEY"))
val created = pushdata.sendNotification(mapOf(
"title" to "Заказ оформлен", "body" to "Собираем заказ №123.",
"target" to mapOf("user_ids" to listOf("user-123")),
"data" to mapOf("orderId" to "123"), "click_action" to "myapp://orders/123",
), "order-123-placed")C# / .NET
var pushdata = new PushDataClient(Environment.GetEnvironmentVariable("PUSHDATA_API_KEY")!);
var created = await pushdata.SendNotificationAsync(new JsonObject
{
["title"] = "Заказ оформлен", ["body"] = "Собираем заказ №123.",
["target"] = new JsonObject { ["user_ids"] = new JsonArray("user-123") },
["data"] = new JsonObject { ["orderId"] = "123" }, ["click_action"] = "myapp://orders/123",
}, "order-123-placed");Ruby
pushdata = PushData::Client.new(api_key: ENV.fetch("PUSHDATA_API_KEY"))
created = pushdata.send_notification(
title: "Заказ оформлен", body: "Собираем заказ №123.",
target: { user_ids: ["user-123"] }, data: { orderId: "123" }, click_action: "myapp://orders/123",
idempotency_key: "order-123-placed"
)Swift (сервер: Vapor / скрипт)
var request = URLRequest(url: URL(string: "https://api.pushdata.ru/api/v1/notifications")!)
request.httpMethod = "POST"
request.setValue("ApiKey \(apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("order-123-placed", forHTTPHeaderField: "Idempotency-Key")
request.httpBody = try JSONSerialization.data(withJSONObject: [
"title": "Заказ оформлен", "body": "Собираем заказ №123.",
"target": ["user_ids": ["user-123"]], "data": ["orderId": "123"], "click_action": "myapp://orders/123",
])
let (data, _) = try await URLSession.shared.data(for: request)
let created = try JSONSerialization.jsonObject(with: data) as? [String: Any] // ["id": …, "status": …]Dart (сервер: shelf / скрипт)
final response = await http.post(
Uri.parse('https://api.pushdata.ru/api/v1/notifications'),
headers: {'Authorization': 'ApiKey $apiKey', 'Content-Type': 'application/json', 'Idempotency-Key': 'order-123-placed'},
body: jsonEncode({
'title': 'Заказ оформлен', 'body': 'Собираем заказ №123.',
'target': {'user_ids': ['user-123']}, 'data': {'orderId': '123'}, 'click_action': 'myapp://orders/123',
}),
);
final created = jsonDecode(response.body); // {id, status}Контакты и сценарии
Контакт — ваш пользователь в PushData: external_id совпадает с user_id, под которым регистрируются его устройства. Обновление идемпотентно (201 создан / 200 обновлён), сценарий запускается для контакта с полезной нагрузкой для шаблонов:
await pushdata.upsertContact({ external_id: 'user-123', email: 'user@example.com', phone: '+79990000000', name: 'Иван', locale: 'ru' })
await pushdata.triggerWorkflow('WORKFLOW_ID', { contact_external_id: 'user-123', payload: { orderId: '123' }, idempotency_key: 'order-123-welcome' })
const contact = await pushdata.getContact('user-123')
await pushdata.deleteContact('user-123') // стирание вместе с устройствами и inbox (152-ФЗ)pushdata.upsert_contact("user-123", email="user@example.com", name="Иван")
pushdata.trigger_workflow("WORKFLOW_ID", contact_external_id="user-123", payload={"orderId": "123"})Устройства с сервера
Регистрирует устройство обычно само приложение (клиентские SDK, публичный ключ). С сервера — список без токенов, теги, деактивация:
const { data, total } = await pushdata.listDevices({ user_id: 'user-123', status: 'active' })
await pushdata.updateDeviceTags(data[0].id, ['vip'])
await pushdata.unregisterDeviceByToken('fcm-registration-token') // выход пользователя со стороны сервераХэш идентичности и вебхуки
Клиентские SDK при включённой Проверке идентичности просят у вашего бэкенда X-Identity-Hash — HMAC-SHA256 от id пользователя на секрете приложения. Отдавайте его только авторизованному пользователю для его собственного id:
import { identityHash, verifyWebhookSignature } from '@pushdata/node'
app.get('/api/pushdata-identity', auth, (req, res) => res.send(identityHash(process.env.PUSHDATA_IDENTITY_SECRET!, req.user.id)))
app.post('/webhooks/pushdata', express.raw({ type: '*/*' }), (req, res) => {
if (!verifyWebhookSignature(req.body, req.header('X-PushData-Signature'), process.env.PUSHDATA_WEBHOOK_SECRET!))
return res.sendStatus(401)
const event = JSON.parse(req.body.toString()) // { event: 'notification.sent', ... }
res.sendStatus(204)
})Подпись — t=<unix-секунды>, v1=<hex HMAC-SHA256 от "<t>.<сырое тело>">, окно 5 минут; проверяйте сырое тело до разбора JSON (подробно — Вебхуки).
Ошибки и повторы
Каждая ошибка — { "error": { "code", "message", "details"? } } с HTTP-статусом; SDK бросают типизированную ошибку с status, code (unauthorized, forbidden, validation_error, payment_required, not_found, idempotency_conflict, app_inactive, rate_limited, …) и details. 429 и 5xx SDK повторяют сами (2 попытки, задержка 0,5 → 1 с или Retry-After); 4xx не повторяются. Полный список кодов — в Уведомлениях.