ДокументацияВведение
Коды ошибок

Справочник

SDK и библиотеки

Python, Node.js, PHP, Go — copy-paste примеры

Отдельного SDK-пакета у PayBot нет — API это обычный JSON поверх HTTPS, поэтому интегрируются прямыми вызовами стандартными средствами языка: requests/httpx в Python, fetch в Node.js, curl в PHP, net/http в Go. Ниже — минимальные рабочие сниппеты для каждого языка: создать Kaspi QR через POST /v2/qr с заголовками X-API-Key и Idempotency-Key, проверить статус, а также проверить подпись входящего вебхука (HMAC-SHA256 над "{timestamp}.{body}").

Базовый URL — https://api.paybot.kz. Прежде чем копировать код, получите API-ключ и разберитесь с режимами в Начало работы. Формат события и заголовки доставки описаны в Вебхуки. Полный список путей — в API Reference.

Что нужно для любого языка

Все примеры используют один и тот же контракт, независимо от языка.

  • Заголовок X-API-Key: kp_live_ (или kp_test_... в тестовом режиме).
  • Заголовок Idempotency-Key: на каждом мутирующем POST — генерируйте один UUID на намерение пользователя, повтор той же операции шлите с тем же ключом.
  • Content-Type: application/json.
  • Суммы — целые в тенге (₸): amount: 5000 = 5000 ₸.
  • Ответ создания QR — {operation_id, qr_token, deep_link, expires_at, status}; статус читается через GET /v2/qr/{operation_id}.

Python

Через requests. Создаём QR, затем опрашиваем статус.

python
Скачать
import uuid
import requests

BASE = "https://api.paybot.kz"
API_KEY = "kp_live_0123456789abcdef0123456789abcdef"

def create_qr(amount: int, comment: str) -> dict:
    resp = requests.post(
        f"{BASE}/v2/qr",
        headers={
            "X-API-Key": API_KEY,
            "Idempotency-Key": str(uuid.uuid4()),
            "Content-Type": "application/json",
        },
        json={"amount": amount, "comment": comment},
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()

def get_status(operation_id: str) -> dict:
    resp = requests.get(
        f"{BASE}/v2/qr/{operation_id}",
        headers={"X-API-Key": API_KEY},
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()

qr = create_qr(5000, "Заказ #1042")
print(qr["deep_link"], qr["expires_at"])
print("status:", get_status(qr["operation_id"])["status"])

Проверка подписи вебхука на приёмнике (Flask). Пересобираем "{timestamp}.{raw_body}", считаем HMAC-SHA256 секретом вебхука и сравниваем в constant-time с частью после sha256=.

python
Скачать
import hmac, hashlib
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = b"whsec_your_webhook_secret"

@app.post("/paybot/webhook")
def webhook():
    raw = request.get_data()  # сырое тело, не парсить до проверки
    ts = request.headers.get("X-Webhook-Timestamp", "")
    header_sig = request.headers.get("X-Webhook-Signature", "")  # "sha256=<hex>"
    signed = f"{ts}.".encode() + raw
    expected = hmac.new(WEBHOOK_SECRET, signed, hashlib.sha256).hexdigest()
    received = header_sig.split("=", 1)[-1]
    if not hmac.compare_digest(expected, received):
        abort(400, "bad signature")
    event = request.get_json()
    if event["type"] == "payment.completed":
        pass  # выдать заказ
    return "", 200

Node.js (JavaScript / TypeScript)

Через встроенный fetch (Node 18+) и node:crypto.

javascript
Скачать
import { randomUUID, createHmac, timingSafeEqual } from "node:crypto";

const BASE = "https://api.paybot.kz";
const API_KEY = "kp_live_0123456789abcdef0123456789abcdef";

async function createQr(amount, comment) {
  const resp = await fetch(`${BASE}/v2/qr`, {
    method: "POST",
    headers: {
      "X-API-Key": API_KEY,
      "Idempotency-Key": randomUUID(),
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ amount, comment }),
  });
  if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
  return resp.json();
}

async function getStatus(operationId) {
  const resp = await fetch(`${BASE}/v2/qr/${operationId}`, {
    headers: { "X-API-Key": API_KEY },
  });
  if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
  return resp.json();
}

const qr = await createQr(5000, "Заказ #1042");
console.log(qr.deep_link, qr.expires_at);
console.log("status:", (await getStatus(qr.operation_id)).status);

Проверка подписи вебхука (Express, express.raw — тело нужно сырым).

javascript
Скачать
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

const app = express();
const SECRET = "whsec_your_webhook_secret";

app.post("/paybot/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const ts = req.header("X-Webhook-Timestamp") || "";
  const headerSig = req.header("X-Webhook-Signature") || ""; // "sha256=<hex>"
  const signed = `${ts}.` + req.body.toString("utf8");
  const expected = createHmac("sha256", SECRET).update(signed).digest("hex");
  const received = headerSig.split("=")[1] || "";
  const a = Buffer.from(expected), b = Buffer.from(received);
  if (a.length !== b.length || !timingSafeEqual(a, b)) {
    return res.status(400).send("bad signature");
  }
  const event = JSON.parse(req.body.toString("utf8"));
  if (event.type === "payment.completed") {
    // выдать заказ
  }
  res.sendStatus(200);
});

PHP

Через curl. Создание QR и проверка подписи вебхука.

php
Скачать
<?php
const BASE = "https://api.paybot.kz";
const API_KEY = "kp_live_0123456789abcdef0123456789abcdef";

function create_qr(int $amount, string $comment): array {
    $ch = curl_init(BASE . "/v2/qr");
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => [
            "X-API-Key: " . API_KEY,
            "Idempotency-Key: " . bin2hex(random_bytes(16)),
            "Content-Type: application/json",
        ],
        CURLOPT_POSTFIELDS => json_encode(["amount" => $amount, "comment" => $comment]),
    ]);
    $body = curl_exec($ch);
    curl_close($ch);
    return json_decode($body, true);
}

$qr = create_qr(5000, "Заказ #1042");
echo $qr["deep_link"], " ", $qr["expires_at"], PHP_EOL;

Проверка подписи входящего вебхука:

php
Скачать
<?php
$secret = "whsec_your_webhook_secret";
$raw = file_get_contents("php://input"); // сырое тело
$ts = $_SERVER["HTTP_X_WEBHOOK_TIMESTAMP"] ?? "";
$headerSig = $_SERVER["HTTP_X_WEBHOOK_SIGNATURE"] ?? ""; // "sha256=<hex>"

$expected = hash_hmac("sha256", $ts . "." . $raw, $secret);
$received = explode("=", $headerSig, 2)[1] ?? "";

if (!hash_equals($expected, $received)) {
    http_response_code(400);
    exit("bad signature");
}
$event = json_decode($raw, true);
if ($event["type"] === "payment.completed") {
    // выдать заказ
}
http_response_code(200);

Go

Через net/http и crypto/hmac.

go
Скачать
package main

import (
	"bytes"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"
)

const base = "https://api.paybot.kz"
const apiKey = "kp_live_0123456789abcdef0123456789abcdef"

func idempotencyKey() string {
	b := make([]byte, 16)
	rand.Read(b)
	return hex.EncodeToString(b)
}

func createQR(amount int, comment string) (map[string]any, error) {
	body, _ := json.Marshal(map[string]any{"amount": amount, "comment": comment})
	req, _ := http.NewRequest("POST", base+"/v2/qr", bytes.NewReader(body))
	req.Header.Set("X-API-Key", apiKey)
	req.Header.Set("Idempotency-Key", idempotencyKey())
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	var out map[string]any
	json.NewDecoder(resp.Body).Decode(&out)
	return out, nil
}

func main() {
	qr, _ := createQR(5000, "Заказ #1042")
	fmt.Println(qr["deep_link"], qr["expires_at"])
}

Проверка подписи вебхука в HTTP-хендлере:

go
Скачать
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"io"
	"net/http"
	"strings"
)

var secret = []byte("whsec_your_webhook_secret")

func webhook(w http.ResponseWriter, r *http.Request) {
	raw, _ := io.ReadAll(r.Body) // сырое тело
	ts := r.Header.Get("X-Webhook-Timestamp")
	headerSig := r.Header.Get("X-Webhook-Signature") // "sha256=<hex>"

	mac := hmac.New(sha256.New, secret)
	mac.Write([]byte(ts + "."))
	mac.Write(raw)
	expected := hex.EncodeToString(mac.Sum(nil))
	received := strings.TrimPrefix(headerSig, "sha256=")

	if !hmac.Equal([]byte(expected), []byte(received)) {
		http.Error(w, "bad signature", http.StatusBadRequest)
		return
	}
	w.WriteHeader(http.StatusOK)
}

FAQ

Есть ли официальный пакет `pip install paybot` или `npm i paybot`?

Нет. Официального SDK-пакета PayBot не публикует — API намеренно простой (JSON поверх HTTPS), и мы показываем прямые вызовы стандартными библиотеками. Это избавляет от версионных конфликтов и лишних зависимостей. Полный контракт — в API Reference.

Почему в примерах подписи вебхука тело читается «сырым»?

Подпись считается над байтами тела ровно так, как их отправил PayBot (компактный JSON без пробелов). Если сначала распарсить и заново сериализовать тело, байты изменятся и HMAC не совпадёт. Поэтому берите raw body до парсинга — express.raw, php://input, io.ReadAll. Детали — в Вебхуки.

Как переключиться с боевого режима на тестовый?

Замените ключ kp_live_... на kp_test_.... Тестовые ключи не тратят лимит платежей и не действуют на реальные деньги. Больше про режимы — в Начало работы.

Обязателен ли Idempotency-Key в этих сниппетах?

Да, для POST /v2/qr, POST /v2/invoices, POST /v2/refunds и отмены счёта. Без него вернётся 400 idempotency_key_required. Генерируйте один ключ на нажатие кнопки пользователем, а не на каждый HTTP-ретрай.