API e exemplos de webhook por idioma
URL base de produção: https://api.licensechain.app/v1 (11 de maio de 2026). Autentique com Authorization: Bearer … usando um usuário JWT de /v1/auth/login ou uma chave de aplicativo API de Dashboard.
Para regras em nível de campo (níveis, ID de preço de parceiro de pagamento terceirizado recorrente, sinalizadores de entrega somente Enterprise), consulte Produtos API CRUD e o contrato OpenAPI no Referência da API.
Verificação de integridade (ondulação)
curl -sS "https://api.licensechain.app/v1/health" Listar produtos (curl)
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" Criar produto (curl)
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"}' Atualização parcial — PATCH (curl)
PATCH /v1/products/:id aceita os mesmos campos JSON que PUT; as chaves omitidas permanecem inalteradas.
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 (buscar)
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 (solicitações)
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 (líquido/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 (ondulação)
<?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 (stdlib)
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)
Use dentro de um método async; adicione NuGet System.Text.Json se necessário.
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)
Adicione dependências: reqwest (com recurso 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 webhooks de comerciante (x-lc-pay-*)
Pay assina o corpo JSON bruto junto com x-lc-pay-timestamp usando o callbackSecret do seu produto (retornado na criação do produto/update para chamadores autorizados). Mensagem: string UTF-8 timestamp + "." + rawBody, HMAC-SHA256, resumo hexadecimal em minúsculas, enviado como x-lc-pay-signature. Compare em tempo constante. Veja também Retornos de chamada e webhooks.
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 — verifica a assinatura
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 — verifica a assinatura
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 — verifica a assinatura
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 — verifica a assinatura
<?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 — verifica a assinatura
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