@licensechain/mirror-sdk
v1.0.1
Published
LicenseChain Mirror Payload SDK (strict mode)
Maintainers
Readme
LicenseChain Mirror SDK
Official SDK for LicenseChain Pay Mirror Payload strict mode.
Integration guide (read this)
- This repo:
docs/INTEGRATION.md— callback verify, payload fields, env (no seller Stripe keys). - Examples:
docs/examples/nextjs-app-router.md— App Router BFF + button. - Docs site: Mirror SDK guide · Pay Mirror API · Signing models
Rule: Integrators must not mask seller-backend failures. Prefer upstreamMessage + correct 4xx HTTP status in your BFF route.
Capabilities
| Feature | Function |
|---------|----------|
| Mirror payload (AES-256-GCM CLIENT_EMAIL) | buildMirrorPayload() |
| Checkout URL | buildMirrorCheckoutUrl() |
| Redirect callback HMAC verify | verifyCallbackSignature() |
| Pay validate API | verifyPurchase() → POST /api/checkout/mirror/validate |
Supported runtimes: Node.js / TypeScript (primary). Reference examples: JavaScript, PHP, Python, Go, Rust.
Limitations
- No order API — your seller backend must create orders, enforce business rules, and return clear 4xx before redirecting to Pay.
- No subscription renewals — use Pay
POST /api/checkout/mirror/subscription/chargefrom your backend (see Pay Mirror API). - Server-side only — do not call payload builders or verification from untrusted clients.
- Strict mode only — unsigned or legacy checkout flows are not supported.
Security: callbackSecret
The product callbackSecret (Dashboard or Core API POST /v1/products) is a shared secret used for:
- Encrypting Mirror payload fields on your server.
- Verifying redirect
signaturequery parameters (HMAC-SHA256 over canonical query string). - Verifying Pay merchant webhooks (
x-lc-pay-signature— different canonicalization; see signing models doc).
Never expose callbackSecret in:
- Browser JavaScript or mobile app bundles
- Public git repositories or support tickets
- Client-side WooCommerce/WordPress theme files
Store in environment variables or a secrets manager. Rotate via product PATCH/PUT if compromised.
Security best practices
- Fail closed — reject missing or invalid redirect signatures; do not trust
LCGorstatusalone. - Double verification — after signature check, call
verifyPurchase()withlicense+ buyer email before fulfillment. - Constant-time compare —
verifyCallbackSignature()usestimingSafeEqual; keep this for custom implementations. - HTTPS only — thanks/cancel URLs and webhook endpoints must use TLS.
- Separate signing models — Core API webhooks (
X-Webhook-Signature), Pay webhooks (x-lc-pay-signature), and Mirror redirect HMAC use different rules. Do not reuse verification code across them without adaptation. - Propagate upstream errors — when your BFF wraps checkout preparation, surface
upstreamMessageand preserve 4xx status codes.
Install
npm install @licensechain/mirror-sdkSource: github.com/LicenseChain/LicenseChain-Mirror-SDK
Usage (Node.js / TypeScript)
import {
buildMirrorPayload,
buildMirrorCheckoutUrl,
verifyCallbackSignature,
verifyPurchase
} from "@licensechain/mirror-sdk";
const { payloadBase64Url } = buildMirrorPayload({
thanksPage: "https://seller.example.com/thanks",
cancelUrl: "https://seller.example.com/cancel",
callbackSecret: process.env.MIRROR_CALLBACK_SECRET!,
token: "order_8f2a9f0c61",
clientEmail: "[email protected]"
});
const checkoutUrl = buildMirrorCheckoutUrl(
"https://pay.licensechain.app",
"YOUR_PRODUCT_ID",
payloadBase64Url
);
// Redirect customer to checkoutUrl
const callbackUrl = "https://seller.example.com/thanks?status=successful&LCG=LC-XXXX-XXXX&signature=...";
const isValid = verifyCallbackSignature(callbackUrl, process.env.MIRROR_CALLBACK_SECRET!);
if (!isValid) throw new Error("Invalid callback signature");
const validation = await verifyPurchase({
apiBaseUrl: "https://pay.licensechain.app",
license: "LC-XXXX-XXXX",
email: "[email protected]"
});
if (!validation.verified) {
throw new Error("Purchase not verified");
}Strict mode flow
- Seller backend: Create order / validate listing; on failure return 4xx and a clear message (e.g. unverified ad). Surface that message in the UI — see
docs/INTEGRATION.md. - Build payload with
TOKENand encryptedCLIENT_EMAIL. - Redirect buyer to
https://pay.licensechain.app/checkout/{PRODUCT_ID}?payload=.... - On return, verify callback signature server-side.
- Use fail-closed logic: reject missing/invalid signature and do not trust URL data alone.
- Call
POST /api/checkout/mirror/validatewithlicenseandemail. - Fulfill order only when response has
verified: true.
Checkout API response handling (before/after)
Your server route that prepares payload may call your API first. Do not only check data.error.
Before:
if (!res.ok) throw new Error(data.error || "Failed to prepare checkout");After:
const body = await res.json() as { error?: string; upstreamMessage?: string };
if (!res.ok) {
const msg =
typeof body.upstreamMessage === "string" && body.upstreamMessage.trim() !== ""
? body.upstreamMessage.trim()
: (body.error ?? "Failed to prepare checkout");
throw new Error(msg);
}Pay Mirror API endpoints
| Method | Endpoint | Purpose |
|--------|----------|---------|
| POST | /api/checkout/mirror/validate | Confirm license + email after redirect |
| POST | /api/checkout/mirror/subscription/charge | Charge Stripe subscription renewal (seller backend) |
Base URL: https://pay.licensechain.app
Examples
examples/javascript/example.mjsexamples/php/example.phpexamples/python/example.pyexamples/go/main.goexamples/rust/main.rs
LicenseChain API (v1)
This SDK targets LicenseChain Pay for checkout validation. Core license HTTP semantics live in the API service:
- Production base URL: https://api.licensechain.app/v1
- API reference: docs.licensechain.app
- Products CRUD:
GET/POST/PATCH/DELETE /v1/products(includescallbackSecretfor Mirror/Pay webhooks)
License
MIT
