@signedsh/sdk
v0.1.0
Published
Official TypeScript/JavaScript SDK for Signed — the e-signature API that generates the document for you.
Maintainers
Readme
Signed — TypeScript / JavaScript SDK
The official SDK for Signed — the e-signature API that generates the document for you. Generate a PDF from a template or HTML, send it for signature, collect a legally binding e-signature with a court-grade audit trail, get a webhook when it's done, and store the sealed PDF. One API, one bill, ~$0.10 per envelope. The developer-first alternative to DocuSign.
- Zero dependencies. Built on the platform
fetch— works on Node 18+, Bun, Deno, Cloudflare Workers, and the edge. - Fully typed. Every request and response is described in TypeScript.
- Safe by default. Automatic idempotency keys, retries with jitter, and a typed error for every failure mode.
- Webhook verification included. Standard Webhooks HMAC verification, no extra package.
Install
npm install @signedsh/sdkQuickstart
import { Signed } from "@signedsh/sdk";
const signed = new Signed("sk_live_..."); // or set SIGNED_API_KEY
const envelope = await signed.envelopes.create({
document: {
template_id: "tmpl_employment",
data: { employee: "Jane Doe", salary: "$120,000" },
},
signers: [{ name: "Jane Doe", email: "[email protected]", role: "employee", order: 1 }],
fields: [{ type: "signature", signer: "employee", anchor: "{{sig_employee}}" }],
options: { expires_in_days: 14 },
webhook_url: "https://acme.com/hooks/signed",
});
console.log(envelope.envelope_id, envelope.status, envelope.webhook_secret); // env_8fa2 "sent" whsec_...We generate the PDF, email Jane a signing link, and fire envelope.completed to your webhook when she's done. Store webhook_secret to verify events for this envelope. One call.
Configuration
const signed = new Signed({
apiKey: process.env.SIGNED_API_KEY, // default: process.env.SIGNED_API_KEY
baseUrl: "https://api.signed.sh/v1", // override for self-hosted
maxRetries: 2, // retry 429 / 5xx / network errors
timeout: 30_000, // per-request timeout (ms)
});Every method takes the same per-call options as a final argument:
await signed.envelopes.create(params, {
idempotencyKey: "order_8842", // a key is auto-generated if you omit this
timeout: 10_000,
maxRetries: 5,
signal: AbortSignal.timeout(5_000),
});Resources
Envelopes
const ref = await signed.envelopes.create({ document, signers, fields, metadata: { order_id: "8842" } });
const envelope = await signed.envelopes.retrieve(ref.envelope_id);
await signed.envelopes.update(ref.envelope_id, { metadata: { archived: true } });
await signed.envelopes.void(ref.envelope_id);
// Auto-paginating iterator over every envelope (newest first):
for await (const env of signed.envelopes.list({ status: "completed" })) {
console.log(env.envelope_id, env.metadata);
}
// Poll until terminal, with a timeout:
const sealed = await signed.envelopes.waitUntilCompleted(ref.envelope_id, { timeoutMs: 600_000 });
sealed.signed_document_url; // sealed (PAdES) PDF
sealed.certificate_url; // certificate of completionSigning links
A single reusable link — no API call per signature (like a Stripe Payment Link).
const link = await signed.signingLinks.create({
name: "Mutual NDA",
document: { template_id: "tmpl_nda" },
fields: [{ type: "signature", signer: "counterparty" }],
options: { max_uses: 100, expires_in_days: 30 },
});
console.log(link.url); // share anywhere — whoever opens it signsDocuments
Render a template or HTML to a PDF without sending it for signature.
const { url } = await signed.documents.render({
html: "<h1>Invoice #1024</h1>",
options: { format: "A4" },
});Errors
Every failure throws a typed subclass of SignedError.
import { Signed, RateLimitError, InvalidRequestError, SignedError } from "@signedsh/sdk";
try {
await signed.envelopes.create(params);
} catch (err) {
if (err instanceof RateLimitError) {
// already retried maxRetries times; back off further
} else if (err instanceof InvalidRequestError) {
console.error(err.message, err.type, err.requestId, err.docsUrl);
} else if (err instanceof SignedError) {
console.error(`Signed API error ${err.status}:`, err.message);
}
}| Class | When |
| --- | --- |
| InvalidRequestError | 400 / 422 — malformed request or validation failure |
| AuthenticationError | 401 — missing or invalid API key |
| QuotaExceededError | 402 — quota exhausted (prompt an upgrade) |
| PermissionError | 403 — not allowed (e.g. test key on a live resource) |
| NotFoundError | 404 — no such resource |
| ConflictError | 409 — conflicting state (e.g. voiding a completed envelope) |
| RateLimitError | 429 — rate limited (auto-retried) |
| ServerError | 5xx — our side (auto-retried) |
| APIConnectionError | network failure, timeout, or abort |
Every SignedError carries .status, .type, .requestId, .docsUrl, and .raw.
Verifying webhooks
Verify the raw request body before trusting an event (Standard Webhooks).
import { Webhook, WebhookVerificationError } from "@signedsh/sdk";
const wh = new Webhook(process.env.SIGNED_WEBHOOK_SECRET!); // whsec_...
// Express / Node — make sure you pass the RAW body, not a parsed object.
app.post("/hooks/signed", express.raw({ type: "application/json" }), (req, res) => {
try {
const event = wh.verify(req.body, req.headers);
if (event.type === "envelope.completed") {
// event.data.envelope_id, ...
}
res.sendStatus(200);
} catch (err) {
if (err instanceof WebhookVerificationError) return res.sendStatus(400);
throw err;
}
});Idempotency & retries
- Writes are idempotent. Every
POSTsends anIdempotency-Key; one is generated for you, or pass your own via{ idempotencyKey }. Retrying a key returns the original result and never double-charges. - Automatic retries.
429,5xx, and network errors are retried up tomaxRetrieswith exponential backoff + full jitter, honoringRetry-After.
Links
- Docs: https://docs.signed.sh
- API reference: https://docs.signed.sh/api-reference
- SDKs & AI/LLM access: https://docs.signed.sh/sdks
- OpenAPI spec: https://github.com/trysolodev/signed.sh/blob/main/openapi.yaml
Built on open source
Signed's platform stands on great open source — Gotenberg (document generation) and optional self-hosted DocuSeal for advanced/PDF ceremonies — with our own SES signing page, PAdES sealing, certificate of completion, and hash-chained audit trail layered on top. This SDK is an independent HTTP client and bundles none of their code.
License
MIT © Signed
