@chaindoc_io/server-sdk
Official server-side SDK for Chaindoc API - documents, signatures, contracts, invoicing, and webhook-safe integrations.

Features
- Document Management - Create, update, and version documents with blockchain verification
- Digital Signatures - Request and collect legally-binding electronic signatures
- Templates, Contracts, and Billing - Generate documents and contracts from published templates, then manage invoice lifecycle and transactions
- Embedded Signing - Seamless in-app signing experience with frontend SDK integration
- Blockchain Verification - Immutable document verification on blockchain
- Webhook Verification - Verify signed webhook deliveries with replay protection
- Embedded KYC Enforcement - Require Chaindoc-managed KYC inside the signing iframe
- Zero Dependencies - Uses native Node.js 18+ APIs (fetch, FormData)
- TypeScript First - Full type definitions included
- Automatic Retries - Built-in retry logic with exponential backoff
Installation
npm install @chaindoc_io/server-sdk
yarn add @chaindoc_io/server-sdk
pnpm add @chaindoc_io/server-sdk
Quick Start
import { Chaindoc } from "@chaindoc_io/server-sdk";
const chaindoc = new Chaindoc({
secretKey: "sk_your_secret_key",
});
const session = await chaindoc.embedded.createSession({
email: "signer@example.com",
metadata: {
documentId: "doc_xxx",
signatureRequestId: "req_xxx",
},
});
console.log(session.sessionId);
Complete Example: Document Signing Flow
import { Chaindoc } from "@chaindoc_io/server-sdk";
import { readFile } from "fs/promises";
const chaindoc = new Chaindoc({
secretKey: "sk_your_secret_key",
});
const buffer = await readFile("./contract.pdf");
const file = new Blob([buffer], { type: "application/pdf" });
const { media } = await chaindoc.media.upload([file]);
const doc = await chaindoc.documents.create({
name: "Service Agreement",
description: "Contract for services",
media: media[0],
hashtags: ["#contract", "#agreement"],
status: "published",
});
const request = await chaindoc.signatures.createRequest({
versionId: doc.document.currentVersion.id,
recipients: [{ email: "signer@example.com" }],
deadline: new Date("2027-12-31"),
embeddedFlow: true,
isKycRequired: true,
});
const session = await chaindoc.embedded.createSession({
email: "signer@example.com",
metadata: {
documentId: doc.documentId,
signatureRequestId: request.requestId,
returnUrl: "https://yourapp.com/signing-complete",
},
});
Complete Example: Contract Billing Flow
import { Chaindoc } from "@chaindoc_io/server-sdk";
const chaindoc = new Chaindoc({
secretKey: "sk_your_secret_key",
});
const contract = await chaindoc.contracts.get("contract_uuid");
const contractId = contract.contractId;
if (contract.contract.status !== "active") {
throw new Error("Invoices can be created only for active contracts");
}
const invoice = await chaindoc.invoices.create(contractId, {
title: "April Retainer",
amount: "1500.00",
dueDate: "2026-04-30T00:00:00.000Z",
sendImmediately: false,
});
const invoiceId = invoice.invoiceId ?? invoice.invoice.id;
await chaindoc.invoices.send(contractId, invoiceId, {
autoCharge: false,
});
const refreshedInvoice = await chaindoc.invoices.get(contractId, invoiceId);
if (refreshedInvoice.invoice.status === "unpaid") {
await chaindoc.invoices.charge(contractId, invoiceId);
}
const transactions = await chaindoc.transactions.listByContract(contractId);
console.log(transactions.transactions.map((transaction) => transaction.id));
Complete Example: Template Runtime Flow
import { Chaindoc } from "@chaindoc_io/server-sdk";
const chaindoc = new Chaindoc({
secretKey: "sk_your_secret_key",
});
const templateId = "template_uuid";
const draftDocument = await chaindoc.templates.createDocument(templateId, {
documentName: "Generated NDA",
documentDescription: "Rendered from a published Chaindoc template",
variables: {
client_name: "Acme Inc.",
effective_date: "2026-04-10",
},
});
const signatureRequest = await chaindoc.templates.sendForSigning(templateId, {
documentName: "Generated NDA",
documentDescription: "Ready for signing",
variables: {
client_name: "Acme Inc.",
effective_date: "2026-04-10",
},
slotAssignments: [{ signerKey: "party_a", email: "signer@example.com" }],
deadline: new Date("2026-12-31"),
});
const contract = await chaindoc.templates.createContract(templateId, {
title: "Generated Master Services Agreement",
description: "Contract rendered from template",
variables: {
company_name: "Acme Inc.",
effective_date: "2026-04-10",
},
contragent: {
email: "partner@example.com",
name: "Partner Corp",
},
slotAssignments: [
{ signerKey: "party_a", role: "business" },
{ signerKey: "party_b", role: "contragent" },
],
deadline: new Date("2026-12-31"),
});
console.log(draftDocument.documentId);
console.log(signatureRequest.requestId);
console.log(contract.contractId);
Configuration
const chaindoc = new Chaindoc({
secretKey: "sk_xxx",
environment: "production",
timeout: 30000,
retry: {
maxRetries: 3,
initialDelay: 1000,
maxDelay: 10000,
},
});
Environments
production | https://api.chaindoc.io (default) | Live production traffic |
staging | https://api-demo.chaindoc.io | Pre-release testing |
development | https://api-demo.chaindoc.io | Development & debugging |
Email handling
The SDK trims and lowercases every email value before sending it to the API. This applies to top-level fields (email, signerEmail) and to nested email fields inside recipients, fields, slotAssignments, accessEmails, and contragent. Your input objects are not mutated — the SDK copies them shallowly before normalizing. This matches the backend's canonical email format and prevents silent auto-link misses for users who registered with a different case.
API Overview
Documents
await chaindoc.documents.create({
name: string;
description: string;
media: Media;
hashtags: string[];
status: 'draft' | 'published';
accessType?: 'private' | 'public' | 'restricted';
});
await chaindoc.documents.update(documentId, params);
await chaindoc.documents.updateRights(documentId, {
accessType: 'restricted',
accessEmails: [{ email: 'user@example.com', level: 'read' }],
});
await chaindoc.documents.verify({ versionHash: '0x...' });
await chaindoc.documents.getVerificationStatus(versionId);
Signatures
await chaindoc.signatures.createRequest({
versionId: string;
recipients: [{ email: string }];
deadline: Date;
embeddedFlow?: boolean;
isKycRequired?: boolean;
});
await chaindoc.signatures.getRequestStatus(requestId);
await chaindoc.signatures.getMyRequests({ pageNumber: 1, pageSize: 10 });
await chaindoc.signatures.sign({ requestId, signatureId });
Embedded Sessions
const session = await chaindoc.embedded.createSession({
email: 'signer@example.com',
metadata: {
documentId: string;
signatureRequestId?: string;
returnUrl?: string;
},
});
Contracts
const created = await chaindoc.contracts.create({
documentId: "doc_uuid",
title: "Master Services Agreement",
contragent: { email: "partner@example.com", name: "Partner LLC" },
paymentMethodRequired: true,
preferredPaymentMethodType: "card",
});
await chaindoc.contracts.list({ page: 1, limit: 10, status: "active" });
await chaindoc.contracts.get(created.contractId);
await chaindoc.contracts.getStatus(created.contractId);
await chaindoc.contracts.getActivities(created.contractId);
await chaindoc.contracts.send(created.contractId, {
messageToSigners: "Please review and sign this contract",
deadline: new Date("2026-12-31"),
isKycRequired: true,
});
await chaindoc.contracts.cancel(created.contractId);
await chaindoc.contracts.terminate(created.contractId, { reason: "Mutual offboarding" });
Templates
await chaindoc.templates.createDocument(templateId, {
documentName: "Generated NDA",
variables: {
client_name: "Acme Inc.",
},
});
await chaindoc.templates.sendForSigning(templateId, {
documentName: "Generated NDA",
variables: {
client_name: "Acme Inc.",
},
slotAssignments: [{ signerKey: "party_a", email: "signer@example.com" }],
deadline: new Date("2026-12-31"),
});
await chaindoc.templates.createContract(templateId, {
title: "Generated MSA",
variables: {
company_name: "Acme Inc.",
},
contragent: {
email: "partner@example.com",
},
slotAssignments: [
{ signerKey: "party_a", role: "business" },
{ signerKey: "party_b", role: "contragent" },
],
deadline: new Date("2026-12-31"),
});
Invoices
await chaindoc.invoices.create(contractId, {
title: "April Retainer",
amount: "1500.00",
dueDate: "2026-04-30T00:00:00.000Z",
autoCharge: false,
sendImmediately: false,
});
await chaindoc.invoices.list(contractId, { status: "unpaid", page: 1, limit: 20 });
await chaindoc.invoices.get(contractId, invoiceId);
await chaindoc.invoices.send(contractId, invoiceId, { autoCharge: true });
await chaindoc.invoices.charge(contractId, invoiceId);
await chaindoc.invoices.markPaid(contractId, invoiceId, { note: "Wire received" });
Transactions
await chaindoc.transactions.listByContract(contractId);
await chaindoc.transactions.get(transactionId);
Media
const { media } = await chaindoc.media.upload([file1, file2]);
Utility
await chaindoc.getApiKeyInfo();
await chaindoc.healthCheck();
Webhooks
const verification = Chaindoc.webhooks.verify(
rawBody,
req.headers["x-chaindoc-signature"] as string,
req.headers["x-chaindoc-timestamp"] as string,
process.env.CHAINDOC_WEBHOOK_SECRET!
);
if (!verification.valid) {
throw new Error("Invalid webhook signature");
}
switch (verification.envelope?.type) {
case "contract.signed":
case "invoice.created":
case "invoice.sent":
case "invoice.paid":
case "transaction.created":
case "transaction.updated":
console.log(verification.envelope.data);
break;
}
Error Handling
import { Chaindoc, ChaindocError } from '@chaindoc_io/server-sdk';
try {
await chaindoc.documents.create({ ... });
} catch (error) {
if (error instanceof ChaindocError) {
console.error('API Error:', error.message);
console.error('Status:', error.statusCode);
console.error('Response:', error.response);
}
}
Documentation
Requirements
Related Packages
Contributing
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
Security
For security vulnerabilities, please see our Security Policy.
License
MIT License - see LICENSE file for details.
Support
Made with ❤️ by the Chaindoc team