API и примеры вебхуков по языкам

Базовый URL для production: https://api.licensechain.app/v1 (11 мая 2026 г.). Выполните аутентификацию с помощью Authorization: Bearer …, используя пользователя JWT из /v1/auth/login или ключ приложения API из Dashboard.

Правила на уровне поля (уровни, идентификатор повторяющейся цены стороннего партнера по оплате, флаги доставки только Enterprise) см. Продукция API CRUD и контракт OpenAPI в Справочник API.

Проверка работоспособности (завиток)

curl -sS "https://api.licensechain.app/v1/health"

Список продуктов (завиток)

curl -sS "https://api.licensechain.app/v1/products?limit=20&offset=0&active=true" \
  -H "Authorization: Bearer YOUR_JWT_OR_APP_API_KEY" \
  -H "Accept: application/json"

Создать продукт (завиток)

curl -sS -X POST "https://api.licensechain.app/v1/products" \
  -H "Authorization: Bearer YOUR_JWT_OR_APP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Pro Monthly","price":49.99,"currency":"USD","active":true,"productType":"subscription","billingType":"recurring","interval":"month","stripePriceIdRecurring":"price_1234567890","successUrl":"https://merchant.example.com/ok","cancelUrl":"https://merchant.example.com/cancel"}'

Частичное обновление — ПАТЧ (curl)

PATCH /v1/products/:id принимает те же поля JSON, что и PUT; пропущенные ключи остаются без изменений.

curl -sS -X PATCH "https://api.licensechain.app/v1/products/PRODUCT_UUID" \
  -H "Authorization: Bearer YOUR_JWT_OR_APP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Pro Monthly (API)","active":true,"stripeTaxEnabled":true}'

JavaScript (выбрать)

const res = await fetch("https://api.licensechain.app/v1/products", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_JWT_OR_APP_API_KEY",
    "Content-Type": "application/json",
    Accept: "application/json",
  },
  body: JSON.stringify({
    name: "Pro Monthly",
    price: 49.99,
    currency: "USD",
    active: true,
    productType: "subscription",
    billingType: "recurring",
    interval: "month",
    stripePriceIdRecurring: "price_1234567890",
    successUrl: "https://merchant.example.com/ok",
    cancelUrl: "https://merchant.example.com/cancel",
  }),
});
if (!res.ok) throw new Error(await res.text());
const product = await res.json();
console.log(product);

Python (запросы)

import requests

url = "https://api.licensechain.app/v1/products"
headers = {
    "Authorization": "Bearer YOUR_JWT_OR_APP_API_KEY",
    "Content-Type": "application/json",
    "Accept": "application/json",
}
body = {
    "name": "Pro Monthly",
    "price": 49.99,
    "currency": "USD",
    "active": True,
    "productType": "subscription",
    "billingType": "recurring",
    "interval": "month",
    "stripePriceIdRecurring": "price_1234567890",
    "successUrl": "https://merchant.example.com/ok",
    "cancelUrl": "https://merchant.example.com/cancel",
}
r = requests.post(url, json=body, headers=headers, timeout=30)
r.raise_for_status()
print(r.json())

Go (чистый/http)

package main

import (
  "bytes"
  "encoding/json"
  "net/http"
  "time"
)

func main() {
  const base = "https://api.licensechain.app/v1"
  body := map[string]any{
    "name": "Pro Monthly", "price": 49.99, "currency": "USD", "active": true,
    "productType": "subscription", "billingType": "recurring", "interval": "month",
    "stripePriceIdRecurring": "price_1234567890",
    "successUrl": "https://merchant.example.com/ok",
    "cancelUrl": "https://merchant.example.com/cancel",
  }
  b, _ := json.Marshal(body)
  req, _ := http.NewRequest(http.MethodPost, base+"/products", bytes.NewReader(b))
  req.Header.Set("Authorization", "Bearer YOUR_JWT_OR_APP_API_KEY")
  req.Header.Set("Content-Type", "application/json")
  req.Header.Set("Accept", "application/json")
  client := &http.Client{Timeout: 30 * time.Second}
  resp, err := client.Do(req)
  if err != nil { panic(err) }
  defer resp.Body.Close()
}

PHP (завиток)

<?php
$ch = curl_init("https://api.licensechain.app/v1/products");
$payload = json_encode([
  "name" => "Pro Monthly",
  "price" => 49.99,
  "currency" => "USD",
  "active" => true,
  "productType" => "subscription",
  "billingType" => "recurring",
  "interval" => "month",
  "stripePriceIdRecurring" => "price_1234567890",
  "successUrl" => "https://merchant.example.com/ok",
  "cancelUrl" => "https://merchant.example.com/cancel",
]);
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer YOUR_JWT_OR_APP_API_KEY",
    "Content-Type: application/json",
    "Accept: application/json",
  ],
  CURLOPT_POSTFIELDS => $payload,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_TIMEOUT => 30,
]);
$response = curl_exec($ch);
curl_close($ch);

Ruby (стандартная библиотека)

require "net/http"
require "json"
require "uri"

uri = URI("https://api.licensechain.app/v1/products")
body = {
  "name" => "Pro Monthly",
  "price" => 49.99,
  "currency" => "USD",
  "active" => true,
  "productType" => "subscription",
  "billingType" => "recurring",
  "interval" => "month",
  "stripePriceIdRecurring" => "price_1234567890",
  "successUrl" => "https://merchant.example.com/ok",
  "cancelUrl" => "https://merchant.example.com/cancel",
}
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer YOUR_JWT_OR_APP_API_KEY"
req["Content-Type"] = "application/json"
req["Accept"] = "application/json"
req.body = JSON.generate(body)
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.body

Java (HttpClient, Java 11+)

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
var json = "{\"name\":\"Pro Monthly\",\"price\":49.99,\"currency\":\"USD\",\"active\":true,\"productType\":\"subscription\",\"billingType\":\"recurring\",\"interval\":\"month\",\"stripePriceIdRecurring\":\"price_1234567890\",\"successUrl\":\"https://merchant.example.com/ok\",\"cancelUrl\":\"https://merchant.example.com/cancel\"}";
var req = HttpRequest.newBuilder()
    .uri(URI.create("https://api.licensechain.app/v1/products"))
    .timeout(Duration.ofSeconds(30))
    .header("Authorization", "Bearer YOUR_JWT_OR_APP_API_KEY")
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(json))
    .build();
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.statusCode() + " " + res.body());

C# (.NET HttpClient)

Используйте внутри метода async; при необходимости добавьте NuGet System.Text.Json.

using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var json = JsonSerializer.Serialize(new {
  name = "Pro Monthly",
  price = 49.99m,
  currency = "USD",
  active = true,
  productType = "subscription",
  billingType = "recurring",
  interval = "month",
  stripePriceIdRecurring = "price_1234567890",
  successUrl = "https://merchant.example.com/ok",
  cancelUrl = "https://merchant.example.com/cancel"
});
using var client = new HttpClient { BaseAddress = new Uri("https://api.licensechain.app/v1/") };
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", "YOUR_JWT_OR_APP_API_KEY");
var content = new StringContent(json, Encoding.UTF8, "application/json");
var res = await client.PostAsync("products", content);
res.EnsureSuccessStatusCode();
Console.WriteLine(await res.Content.ReadAsStringAsync());

Rust (reqwest + serde_json)

Добавьте зависимости: reqwest (с функцией json), serde_json, tokio.

use reqwest::header::{AUTHORIZATION, ACCEPT, CONTENT_TYPE};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::builder().build()?;
    let body = json!({
        "name": "Pro Monthly",
        "price": 49.99,
        "currency": "USD",
        "active": true,
        "productType": "subscription",
        "billingType": "recurring",
        "interval": "month",
        "stripePriceIdRecurring": "price_1234567890",
        "successUrl": "https://merchant.example.com/ok",
        "cancelUrl": "https://merchant.example.com/cancel",
    });
    let res = client
        .post("https://api.licensechain.app/v1/products")
        .header(AUTHORIZATION, "Bearer YOUR_JWT_OR_APP_API_KEY")
        .header(CONTENT_TYPE, "application/json")
        .header(ACCEPT, "application/json")
        .json(&body)
        .timeout(std::time::Duration::from_secs(30))
        .send()
        .await?;
    res.error_for_status_ref()?;
    println!("{}", res.text().await?);
    Ok(())
}

Payment торговые вебхуки (x-lc-pay-*)

Pay подписывает необработанное тело JSON вместе с x-lc-pay-timestamp, используя callbackSecret вашего продукта (возвращается при создании продукта/update для авторизованных вызывающих абонентов). Сообщение: строка UTF-8 timestamp + "." + rawBody, HMAC-SHA256, шестнадцатеричный дайджест в нижнем регистре, отправленная как x-lc-pay-signature. Сравнить в постоянное время. См. также Обратные вызовы и вебхуки.

Core API registered webhooks (app-level, POST /v1/webhooks) are signed with HMAC-SHA256 over the exact JSON request body; the platform sends header X-Webhook-Signature (hex). Verify with the webhook secret you stored at registration. This differs from Pay merchant callbacks (x-lc-pay-signature), which sign the UTF-8 string (x-lc-pay-timestamp) + "." + (raw request body bytes as UTF-8).

Node.js — проверить подпись

import crypto from "node:crypto";

/** rawBody must be the exact request bytes/string before JSON.parse */
export function verifyLcPaySignature(
  rawBody: string,
  secret: string,
  timestampHeader: string | undefined,
  signatureHex: string | undefined
): boolean {
  if (!timestampHeader || !signatureHex || !secret) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(timestampHeader + "." + rawBody, "utf8")
    .digest("hex");
  try {
    const a = Buffer.from(signatureHex, "hex");
    const b = Buffer.from(expected, "hex");
    if (a.length !== b.length) return false;
    return crypto.timingSafeEqual(a, b);
  } catch {
    return false;
  }
}

Python — проверить подпись

import hmac
import hashlib

def verify_lc_pay_signature(raw_body: str, secret: str, ts: str, sig_hex: str) -> bool:
    msg = f"{ts}.{raw_body}".encode("utf-8")
    expected = hmac.new(secret.encode("utf-8"), msg, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig_hex)

Go — проверить подпись

package payhook

import (
  "crypto/hmac"
  "crypto/sha256"
  "encoding/hex"
  "subtle"
)

func VerifyLcPaySignature(rawBody, secret, ts, sigHex string) bool {
  mac := hmac.New(sha256.New, []byte(secret))
  mac.Write([]byte(ts + "." + rawBody))
  expected := hex.EncodeToString(mac.Sum(nil))
  if len(expected) != len(sigHex) {
    return false
  }
  return subtle.ConstantTimeCompare([]byte(expected), []byte(sigHex)) == 1
}

PHP — проверить подпись

<?php
function verify_lc_pay_signature(string $rawBody, string $secret, string $ts, string $sigHex): bool {
  $expected = hash_hmac('sha256', $ts . '.' . $rawBody, $secret);
  return hash_equals($expected, $sigHex);
}

Ruby — проверить подпись

require "openssl"

def verify_lc_pay_signature(raw_body, secret, ts, sig_hex)
  digest = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{ts}.#{raw_body}")
  return false if digest.bytesize != sig_hex.bytesize
  OpenSSL.fixed_length_secure_compare(digest, sig_hex)
end