> ## Documentation Index
> Fetch the complete documentation index at: https://oblodai.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Быстрый старт

> От ключей до первого вебхука за пять шагов.

За пять шагов вы примете первый платёж: получите ключи, подпишете запрос, зарегистрируете URL
для вебхуков, создадите счёт и поймаете вебхук об оплате.

<Warning>
  **Песочницы нет — платежи настоящие.** У Oblodai нет тестового режима: «первый платёж» ниже — это
  **реальная** оплата на маленькую сумму, которую вы отправите сами себе. Что можно проверить без
  реальных переводов — см. [Тестирование](/guides/testing).
</Warning>

Примеры кода даны на Python и Node.js (переключайтесь вкладками), но подпись и вызовы идентичны
на любом языке (см. [Как подписать запрос](/guides/signing-requests) для cURL / PHP).

***

## Подготовка

### Что понадобится

* **Аккаунт и ключи** — зарегистрируйтесь в кабинете и получите `public_id` + `secret`. Подробно —
  [Регистрация и ключи](/guides/get-keys).
* **Сервер/бэкенд**, где будет считаться подпись (секрет не должен попадать в браузер).
* **Публично доступный HTTPS‑URL для вебхуков.** На своём ноутбуке (`localhost`) вебхуки **не дойдут**.
  На время разработки поднимите **туннель**: программу, которая даёт вашему локальному серверу
  временный публичный HTTPS‑адрес. Самый простой — [ngrok](https://ngrok.com/download): установили,
  запустили `ngrok http 8000` — получили адрес вида `https://xxxx.ngrok-free.app`, его и указывайте
  при регистрации вебхука в шаге 3 (поле `url`). Альтернативы: Cloudflare Tunnel, localtunnel.

## Пять шагов

<Steps>
  <Step title="Получите ключи" titleSize="h3">
    Зарегистрируйтесь в кабинете [my.oblodai.com](https://my.oblodai.com) и создайте API‑ключ (пошагово —
    [Регистрация и ключи](/guides/get-keys)). Вы получите:

    * **`public_id`** — несекретный идентификатор. Можно логировать. Уходит в заголовке `X-Public-Id`.
    * **`secret`** — секрет для подписи. **Показывается один раз** — сохраните его в секрет‑хранилище.

    Один ключ работает и для приёма, и для выплат. Подробнее — [Аутентификация](/reference/basics-auth).
  </Step>

  <Step title="Научитесь подписывать запрос" titleSize="h3">
    Каждый запрос подписывается HMAC‑SHA256 по канонической строке из четырёх частей. Вот минимальная
    функция‑обёртка, которую мы будем переиспользовать:

    <CodeGroup>
      ```python Python highlight={9-12} theme={null}
      import hmac, hashlib, time, json, os, requests

      # Ключи читаем из окружения — не хардкодьте их в код.
      SECRET = os.environ["OBLODAI_SECRET"].encode()   # секрет API-ключа
      PUBLIC_ID = os.environ["OBLODAI_PUBLIC_ID"]       # oblodai_… / oblodai_live_…
      BASE = "https://api.oblodai.com"

      def call(path: str, payload: dict, idempotency_key: str | None = None) -> dict:
          body = json.dumps(payload, separators=(",", ":"))  # подписываем ровно эту строку
          ts = str(int(time.time()))
          signing = f"{ts}\nPOST\n{path}\n{body}"
          sig = hmac.new(SECRET, signing.encode(), hashlib.sha256).hexdigest()
          headers = {
              "Content-Type": "application/json",
              "X-Public-Id": PUBLIC_ID,
              "X-Timestamp": ts,
              "X-Signature": sig,
          }
          if idempotency_key:
              headers["Idempotency-Key"] = idempotency_key   # защита от дублей; в подпись НЕ входит
          r = requests.post(BASE + path, data=body, headers=headers)
          return r.json()
      ```

      ```js Node.js highlight={9-12} theme={null}
      import crypto from "node:crypto";

      // Ключи читаем из окружения — не хардкодьте их в код.
      const SECRET = process.env.OBLODAI_SECRET;        // секрет API-ключа
      const PUBLIC_ID = process.env.OBLODAI_PUBLIC_ID;  // oblodai_… / oblodai_live_…
      const BASE = "https://api.oblodai.com";

      async function call(path, payload, idempotencyKey = null) {
        const body = JSON.stringify(payload);           // подписываем ровно эту строку
        const ts = Math.floor(Date.now() / 1000).toString();
        const signing = `${ts}\nPOST\n${path}\n${body}`;
        const sig = crypto.createHmac("sha256", SECRET).update(signing).digest("hex");
        const headers = {
          "Content-Type": "application/json",
          "X-Public-Id": PUBLIC_ID,
          "X-Timestamp": ts,
          "X-Signature": sig,
        };
        if (idempotencyKey) {
          headers["Idempotency-Key"] = idempotencyKey;  // защита от дублей; в подпись НЕ входит
        }
        const res = await fetch(BASE + path, { method: "POST", headers, body });
        return res.json();
      }
      ```
    </CodeGroup>

    Детальный разбор и типичные ошибки — [Как подписать запрос](/guides/signing-requests).
  </Step>

  <Step title="Зарегистрируйте URL для вебхуков" titleSize="h3">
    Один раз на проект зарегистрируйте endpoint и **сохраните `secret`** — им проверяется подпись входящих
    вебхуков (это **другой** секрет, чем ключ API):

    <CodeGroup>
      ```python Python theme={null}
      # Внимание: этот эндпоинт возвращает "голый" объект без конверта state/result
      # "url" — ваш ПУБЛИЧНЫЙ HTTPS-адрес из «Что понадобится» (напр.
      # https://xxxx.ngrok-free.app/oblodai/callback), не localhost
      resp = call("/v1/webhooks", {"url": "https://shop.example/oblodai/callback"})
      webhook_secret = resp["secret"]   # ПОКАЗЫВАЕТСЯ ОДИН РАЗ — сохраните
      ```

      ```js Node.js theme={null}
      // Внимание: этот эндпоинт возвращает "голый" объект без конверта state/result
      // "url" — ваш ПУБЛИЧНЫЙ HTTPS-адрес из «Что понадобится» (напр.
      // https://xxxx.ngrok-free.app/oblodai/callback), не localhost
      const resp = await call("/v1/webhooks", { url: "https://shop.example/oblodai/callback" });
      const webhookSecret = resp.secret;   // ПОКАЗЫВАЕТСЯ ОДИН РАЗ — сохраните
      ```
    </CodeGroup>

    Подробнее — [`POST /v1/webhooks`](/reference/webhooks-register).
  </Step>

  <Step title="Создайте платёж" titleSize="h3">
    <CodeGroup>
      ```python Python theme={null}
      payment = call("/v1/payment", {
          "amount": "10",
          "currency": "USD",
          "order_id": "order-1",
          "to_currency": "USDT",
          "network": "tron",
          "lifetime": 3600,
      })["result"]

      print(payment["address"])   # адрес, на который платит покупатель
      print(payment["url"])       # ссылка на hosted-страницу оплаты
      ```

      ```js Node.js theme={null}
      const payment = (await call("/v1/payment", {
        amount: "10",
        currency: "USD",
        order_id: "order-1",
        to_currency: "USDT",
        network: "tron",
        lifetime: 3600,
      })).result;

      console.log(payment.address);   // адрес, на который платит покупатель
      console.log(payment.url);       // ссылка на hosted-страницу оплаты
      ```
    </CodeGroup>

    Покажите покупателю `address` (или отправьте его на `url`). Полное описание полей ответа —
    [Объект платежа](/reference/payment-object).
  </Step>

  <Step title="Поймайте вебхук об оплате" titleSize="h3">
    Когда покупатель оплатит, на ваш URL придёт вебхук `invoice.paid`. Обработчик должен:

    1. Взять **сырое тело** запроса и заголовки `X-Webhook-Timestamp`, `X-Webhook-Signature`.
    2. Проверить подпись секретом из шага 3 (из ответа `POST /v1/webhooks`, **не** API-secret).
    3. Дедуплицировать по `uuid` + `status` и ответить `2xx` **только после** успешной обработки.

    <CodeGroup>
      ```python Python theme={null}
      import hmac, hashlib
      from flask import Flask, request

      app = Flask(__name__)
      WEBHOOK_SECRET = b"b7c1e9…"   # секрет из ответа POST /v1/webhooks (шаг 3), НЕ API-secret

      @app.post("/oblodai/callback")
      def callback():
          raw = request.get_data()  # сырое тело — ВАЖНО не пересериализовывать
          event = request.get_json(silent=True) or {}

          # Пробный вебхук из /guides/testing приходит с "is_test": true и БЕЗ подписи —
          # пропускаем его ДО проверки подписи, иначе ответим 403
          if event.get("is_test"):
              return "ok", 200

          ts = request.headers.get("X-Webhook-Timestamp", "")
          sig = request.headers.get("X-Webhook-Signature", "")
          expected = hmac.new(WEBHOOK_SECRET, ts.encode() + b"." + raw, hashlib.sha256).hexdigest()
          if not hmac.compare_digest(expected, sig):
              return "bad signature", 403

          if event.get("status") == "paid":
              # ... пометить заказ event["order_id"] оплаченным (идемпотентно)
              pass
          return "ok", 200
      ```

      ```js Node.js theme={null}
      import crypto from "node:crypto";
      import express from "express";

      const app = express();
      const WEBHOOK_SECRET = "b7c1e9…";   // секрет из ответа POST /v1/webhooks (шаг 3), НЕ API-secret

      app.post("/oblodai/callback", express.raw({ type: "*/*" }), (req, res) => {
        const raw = req.body;             // сырое тело (Buffer) — ВАЖНО не пересериализовывать
        let event = {};
        try { event = JSON.parse(raw.toString("utf8")); } catch { /* ignore */ }

        // Пробный вебхук из /guides/testing приходит с "is_test": true и БЕЗ подписи —
        // пропускаем его ДО проверки подписи, иначе ответим 403
        if (event.is_test) return res.send("ok");

        const ts = req.get("X-Webhook-Timestamp") ?? "";
        const sig = req.get("X-Webhook-Signature") ?? "";
        const expected = crypto.createHmac("sha256", WEBHOOK_SECRET)
          .update(ts + ".").update(raw).digest("hex");
        const ok = sig.length === expected.length &&
          crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
        if (!ok) {
          return res.status(403).send("bad signature");
        }

        if (event.status === "paid") {
          // ... пометить заказ event.order_id оплаченным (идемпотентно)
        }
        return res.status(200).send("ok");
      });
      ```
    </CodeGroup>

    Подробно про формат, заголовки и идемпотентность — [Объект вебхука](/reference/webhook-object) и
    [Настройка вебхуков](/guides/webhooks-setup).
  </Step>
</Steps>

## Связанные страницы

<CardGroup cols={2}>
  <Card title="Приём первого платежа" href="/guides/accept-first-payment" icon="arrow-right" horizontal>
    тот же поток, но с разбором каждого статуса.
  </Card>

  <Card title="Три режима создания счёта" href="/guides/payment-modes" icon="arrow-right" horizontal>
    фиксированная валюта, авто‑сеть, агностичный счёт.
  </Card>

  <Card title="Первая выплата" href="/guides/first-payout" icon="arrow-right" horizontal>
    вывести средства с баланса.
  </Card>

  <Card title="Чек‑лист перед запуском" href="/guides/production-checklist" icon="arrow-right" horizontal>
    перед боевым трафиком.
  </Card>

  <Card title="Что делать, если не работает" href="/guides/troubleshooting" icon="arrow-right" horizontal>
    диагностика типичных проблем на старте.
  </Card>
</CardGroup>
