@coffrify/sdk
v0.14.0
Published
Official TypeScript/JavaScript SDK for Coffrify — encrypted file transfer infrastructure. Transfers, webhooks, API keys, audit, analytics, branding, domains, folders, collections, members, notifications, GDPR, sessions, downloads, alerts, delegated tokens
Maintainers
Readme
@coffrify/sdk
Official TypeScript SDK for Coffrify — encrypted file transfer infrastructure.
Webhooks, API keys, audit logs, teams, white-label, IP allowlist. Everything you need to integrate encrypted file transfer into your product or pipeline.
Install
npm install @coffrify/sdk
# pnpm add @coffrify/sdk
# bun add @coffrify/sdkRequires Node 18+ (or any runtime with fetch and crypto.subtle: Deno, Bun, modern browsers, Cloudflare Workers, Vercel Edge).
Quickstart
import { Coffrify } from "@coffrify/sdk";
const coffrify = new Coffrify({
apiKey: process.env.COFFRIFY_API_KEY!, // cof_live_... or cof_test_...
});
// Create a transfer
const { transfer, upload_urls } = await coffrify.transfers.create([
{ name: "rapport.pdf", size: 1_240_000, mime_type: "application/pdf" },
], {
expires_in_hours: 72,
max_downloads: 10,
password: "s3cret!",
watermark_text: "Confidentiel — Acme Corp",
});
// Now upload each file to its signed URL (PUT)
// ...
// Subscribe to a webhook
const { webhook, secret } = await coffrify.webhooks.create({
name: "Production hook",
url: "https://app.example.com/hooks/coffrify",
events: ["transfer.created", "transfer.downloaded", "transfer.expired"],
});
console.log("Store this secret securely:", secret);End-to-end encryption (E2E v1)
Encrypt files in your process with AES-256-GCM before they ever touch Coffrify.
The 256-bit key is generated locally and lives only in the returned URL fragment
(#k=...) — fragments are never transmitted in HTTP requests, so Coffrify's
servers store opaque ciphertext and have no way to read your data.
import { readFile } from "node:fs/promises";
const pdf = await readFile("./contract.pdf");
const { share_url, transfer } = await coffrify.transfers.createEncrypted([
{ name: "contract.pdf", content: pdf, mime_type: "application/pdf" },
], {
expires_in_hours: 24,
max_downloads: 3,
});
console.log(share_url);
// → https://coffrify.fr/t/abc123#k=Yz9oQ2m... (share this entire URL — including #k=...)What happens under the hood:
- SDK generates a random AES-256 key with WebCrypto.
- Each file is encrypted in chunked AES-256-GCM (4 MiB chunks, per-chunk IV).
- Ciphertext is uploaded to a presigned URL — server only sees opaque bytes.
- The URL fragment carries the key, never sent over the wire.
- The recipient opens the URL: the browser parses
#k=..., fetches ciphertext, decrypts it locally, and saves the plaintext file.
Limits:
- Encryption is performed in memory; suitable for files up to ~1 GB.
- For larger transfers, use
transfers.create()(server-side AES at rest). - The key must be present in the URL on the recipient side. If it's lost
(e.g. via a chat client that strips
#fragments), the file can no longer be decrypted — Coffrify cannot recover it.
If you need to decrypt programmatically (e.g. a recipient bot in Node):
import { importKeyFromFragment, decryptResponseToBlob } from "@coffrify/sdk";
const fragment = new URL(share_url).hash.slice(1);
const k = new URLSearchParams(fragment).get("k")!;
const key = await importKeyFromFragment(k);
// Fetch ciphertext from the presigned URL returned by GET /t/<short_code>
const cipherResp = await fetch(file.url);
const plain = await decryptResponseToBlob(cipherResp, key, file.mime_type);Webhook signature verification
Coffrify signs every webhook with HMAC-SHA256. Verify it on your end before trusting:
import express from "express";
import { verifyWebhook } from "@coffrify/sdk/webhooks";
const app = express();
app.post(
"/hooks/coffrify",
express.raw({ type: "application/json" }),
async (req, res) => {
const result = await verifyWebhook(
req.body.toString("utf8"),
req.headers["x-coffrify-signature"] as string,
process.env.COFFRIFY_WEBHOOK_SECRET!,
);
if (!result.valid) {
console.warn("Invalid webhook", result.reason);
return res.status(400).send(result.reason);
}
switch (result.event!.type) {
case "transfer.created":
// ...
break;
case "transfer.downloaded":
// ...
break;
}
res.sendStatus(200);
}
);The signature header has the form t=<timestamp>,v1=<hmac_hex>. Verification:
- Checks the timestamp is within ±5 minutes (replay protection).
- Recomputes
HMAC-SHA256(secret, "<timestamp>.<raw_body>")in constant time. - Compares against the provided
v1value.
Use raw body (not parsed JSON) — JSON.stringify-then-parse changes whitespace and breaks the signature.
Authentication
Authentication uses Bearer tokens with your API key:
- Live keys:
cof_live_<hex>— use against production data. - Test keys:
cof_test_<hex>— safe sandbox, no real transfers. - Restricted keys:
cof_rk_live_<hex>/cof_rk_test_<hex>— scoped keys for limited automation.
Pro plan: max 5 keys per workspace, no scopes/expiration/IP restrictions. Entreprise plan: max 25 keys, granular scopes, expiration, IP allowlist (CIDR), max-uses.
const key = await coffrify.apiKeys.create({
name: "CI deploy bot",
scopes: ["transfers:write", "webhooks:manage"],
expires_in_days: 90,
allowed_ips: ["1.2.3.4/32", "10.0.0.0/8"],
});
console.log("Save this key:", key.key);Audit log
Entreprise plan: 365 days retention. Pro plan: 30 days.
const { entries } = await coffrify.audit.list({
action: "transfer.downloaded",
since: new Date(Date.now() - 7 * 86400_000),
limit: 100,
});Reference
See the API reference docs (TODO).
License
MIT
