@softora/sdk
v1.0.1
Published
Official SDK for integrating products with Softora Central. Handles HMAC-SHA256 request signing, product registration, webhook verification, and SSO.
Readme
@softora/sdk
Official TypeScript SDK for integrating products with Softora Central.
Handles HMAC‑SHA256 request signing, product registration, webhook verification, and SSO authentication — so you can connect a new product to Softora with just your API keys and a few lines of code.
Installation
npm install @softora/sdkQuick Start
import { SoftoraClient } from "@softora/sdk";
const softora = new SoftoraClient({
apiKey: "sk_...", // from Central admin panel
sharedSecret: "sec_...", // from Central admin panel
ssoSecret: "sso_...", // from Central admin panel (optional)
});
// Register your product with Softora Central
const result = await softora.register({
name: "My Awesome Product",
dashboardUrl: "https://my-product.softora.cc",
identifier: "my-product.softora.cc",
webhookUrl: "https://my-product.softora.cc/api/webhooks/central-sync",
endpoints: {
webhooks: "https://my-product.softora.cc/api/webhooks/central-sync",
health: "https://my-product.softora.cc/api/central-integration/health",
users: "https://my-product.softora.cc/api/central-integration/users",
},
});
console.log(`✅ Registered! Product ID: ${result.productId}`);
console.log(`🏢 Central company: ${result.centralBranding.centralCompanyName}`);That's it. The SDK handles HMAC signing, nonce generation, timestamp injection, idempotency keys, and error handling automatically.
API Reference
new SoftoraClient(config)
Create a new SDK instance.
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| apiKey | string | ✅ | — | Your API key from Central (sk_…) |
| sharedSecret | string | ✅ | — | Your shared secret from Central (sec_…) |
| ssoSecret | string | — | null | Your SSO secret from Central (sso_…) |
| centralBaseUrl | string | — | "https://softora.cc" | Central server URL |
| registerPath | string | — | "/api/product-manager/v1/register" | Registration endpoint path |
| productUpdatedWebhookPath | string | — | "/api/webhooks/product-updated" | Webhook push endpoint path |
softora.register(options): Promise<RegisterResult>
Register or re‑register your product with Central.
First call: The API key is consumed and permanently locked to your product. Subsequent calls: Act as an upsert — updating endpoints, name, and logo.
const result = await softora.register({
name: "Schedular",
dashboardUrl: "https://schedular.softora.cc",
identifier: "schedular.softora.cc",
logoUrl: "https://schedular.softora.cc/logo.png", // optional
webhookUrl: "https://schedular.softora.cc/api/webhooks/central-sync", // optional
endpoints: { // optional
webhooks: "https://schedular.softora.cc/api/webhooks/central-sync",
health: "https://schedular.softora.cc/api/central-integration/health",
users: "https://schedular.softora.cc/api/central-integration/users",
},
});Returns:
{
productId: "uuid-...",
centralBranding: {
centralCompanyName: "Softora",
centralCompanyLogoUrl: "/api/public/cms-assets/...",
},
message: "Product successfully registered and API key locked."
}softora.verifyWebhook(rawBody, signature): Promise<VerifiedWebhook>
Verify an incoming webhook from Central. Uses constant‑time comparison to prevent timing attacks.
// Hono example
app.post("/api/webhooks/central-sync", async (c) => {
const rawBody = await c.req.text();
const signature = c.req.header("x-webhook-signature") ?? "";
try {
const event = await softora.verifyWebhook(rawBody, signature);
if (event.event === "central.settings.updated") {
const { companyName, logoUrl } = event.data;
// Update your local branding...
}
return c.json({ success: true });
} catch (err) {
return c.json({ error: "Invalid signature" }, 403);
}
});Returns:
{
event: "central.settings.updated",
timestamp: "2026-06-06T12:00:00.000Z",
data: { companyName: "Softora", logoUrl: "..." },
verified: true // type-level guarantee
}softora.pushSettingsUpdate(productId, data): Promise<void>
Push a settings change to Central so the dashboard stays in sync.
await softora.pushSettingsUpdate("product-uuid", {
productName: "New Product Name",
productLogoUrl: "https://my-product.softora.cc/new-logo.png",
});softora.verifySSOHeader(headerValue): true
Verify the x-central-sso-secret header on Central‑to‑Product integration requests.
// Hono middleware
app.use("/api/central-integration/*", (c, next) => {
try {
softora.verifySSOHeader(c.req.header("x-central-sso-secret"));
return next();
} catch {
return c.json({ error: "Unauthorized" }, 401);
}
});Central Integration Endpoints
When Central connects to your product to manage users, it expects your product to implement specific REST endpoints at /api/central-integration/users.
The SDK provides the precise TypeScript interfaces you need to implement these endpoints properly without guessing the payload shapes:
import type {
CentralGetUsersResponse,
CentralCreateUserPayload,
CentralUpdateUserPayload
} from "@softora/sdk";
// Example: GET /api/central-integration/users
app.get("/api/central-integration/users", async (c) => {
// 1. Fetch users from your database (Postgres, Mongo, D1, etc.)
const dbUsers = await db.getUsers();
// 2. Return them matching CentralGetUsersResponse
const response: CentralGetUsersResponse = {
success: true,
data: {
users: dbUsers.map(u => ({
id: u.id,
name: u.name,
email: u.email,
role: u.role,
createdAt: u.createdAt,
})),
supportedUserTypes: ["ADMIN", "MEMBER"],
userManagement: {
title: "Add Product User",
description: "Create a user directly in this product.",
fields: [
{ name: "name", label: "Full Name", type: "text", required: true },
{ name: "email", label: "Email", type: "email", required: true },
{ name: "role", label: "Role", type: "role", required: true },
]
}
}
};
return c.json(response);
});
// Example: POST /api/central-integration/users
app.post("/api/central-integration/users", async (c) => {
// Since fields are dynamic and controlled by your `userManagement.fields`,
// you pass your expected shape as a Generic to CentralCreateUserPayload:
const payload = await c.req.json<CentralCreateUserPayload<{
name: string;
email: string;
role: string;
}>>();
// Save to your DB!
await db.insertUser(payload.name, payload.email, payload.role);
return c.json({ success: true });
});Error Handling
The SDK uses a typed error hierarchy. Every error has a machine‑readable code:
import { SoftoraError, SoftoraAuthError, SoftoraValidationError, SoftoraNetworkError } from "@softora/sdk";
try {
await softora.register({ ... });
} catch (err) {
if (err instanceof SoftoraAuthError) {
// Invalid API key or signature (err.code, err.statusCode)
} else if (err instanceof SoftoraValidationError) {
// Missing required field (err.field)
} else if (err instanceof SoftoraNetworkError) {
// Central is unreachable (err.cause)
} else if (err instanceof SoftoraError) {
// Other Central API error (err.code, err.statusCode, err.responseBody)
}
}| Error Class | code examples | When |
|-------------|----------------|------|
| SoftoraAuthError | invalid_api_key, invalid_signature, expired_signature | Key/signature rejected |
| SoftoraValidationError | validation_error | Missing or invalid input |
| SoftoraNetworkError | network_error | DNS/connection failure |
| SoftoraError | registration_failed, idempotency_key_conflict | Other Central errors |
Low‑Level Utilities
For advanced use cases, the SDK exports the cryptographic primitives:
import { sha256Hex, hmacSha256Hex, stableStringify, signRequest } from "@softora/sdk";
// Hash a string
const hash = await sha256Hex("hello world");
// Sign a custom request
const headers = await signRequest("POST", "/my/path", body, apiKey, sharedSecret);Security Model
| Feature | Implementation |
|---------|---------------|
| Request signing | HMAC‑SHA256 with canonical string |
| Replay protection | Single‑use nonces + 5‑minute timestamp window |
| Idempotency | UUID‑based idempotency keys |
| Webhook verification | HMAC‑SHA256 + constant‑time comparison |
| SSO authentication | Shared secret header with constant‑time comparison |
| Key storage | Only SHA‑256 hashes stored server‑side; raw keys never persisted |
| Runtime compatibility | Web Crypto API only — no Node‑specific crypto module |
Runtime Compatibility
| Runtime | Version | Status | |---------|---------|--------| | Cloudflare Workers | Any | ✅ Full support | | Node.js | 18+ | ✅ Full support | | Deno | 1.x+ | ✅ Full support | | Bun | 1.x+ | ✅ Full support | | Browsers | Modern | ✅ Full support |
License
MIT © Softora
