Security best practices
This page covers the security model you get out of the box and the things you should configure before going live: API key hygiene, IP allowlists, webhook verification, and the two integrity layers (PAdES + blockchain) that make Chaindoc's signatures tamper-evident.
Most of the hard parts are built in. The work on your side is small: keep secret keys off the client, verify webhook signatures, rotate keys on a schedule, and review who has access to your workspace. Learn more in the SOC 2 Trust Services Criteria.
Authentication
Chaindoc is passwordless. Users sign in with an email-based one-time code. there are no stored passwords, no TOTP apps, and no FIDO2 keys to manage. If a user's email account is compromised the attacker can impersonate them, so the usual advice applies: use a strong email provider with its own 2FA. Learn more in the ISO/IEC 27001:2022.
Workspace access is controlled through team roles. Three roles ship by default. Owner (full control including billing), Admin (manage members, documents, contracts), and User (use documents and collaborate). The Owner can rename existing roles, change their permissions, and add custom roles with per-action overrides. Learn more in the OWASP Top 10.
Review access quarterly
Set a reminder to audit workspace membership. Remove people who've left. Downgrade roles that grew too broad. Chaindoc doesn't auto-expire access. that part is on you.
API key security
API keys are the single most common source of incidents. A leaked secret key gives full read/write access to your workspace. Treat them like passwords.
Key management rules
Never put secret keys (sk_) in client-side code. use the public key (pk_) for anything that ships to browsers. Store keys in environment variables or a secrets manager, never in your repo. Keep staging and production keys in separate vaults. the two environments are isolated and keys from one don't work in the other. Rotate production keys at least every 90 days (or immediately if one is exposed): create the new key, cut traffic over, then revoke the old one.
IP allowlists
Every API key supports an optional allowlist. When set, requests from any other address are rejected. even with a valid key. It's the cheapest extra layer you can add to a backend key, and it blocks a whole class of leak scenarios.
Supported formats:
- Individual IPs:
203.0.113.42 - CIDR ranges:
203.0.113.0/24 - Wildcards:
203.0.113.*
Actually, configure the allowlist in Dashboard → Settings → Developers → (key) → Edit. The allowlist applies to every request made with that key.
Least privilege
- Use
pk_keys for read-only workloads (public status pages, dashboards). - Use
sk_keys for server-side writes only. - Actually, create one key per service or environment. so revoking one doesn't break every integration you have.
- Give each key a descriptive name so you can identify it later in the dashboard.
Revocation
Revoking a key is a soft operation: the key is deactivated immediately and returns 401 on every subsequent request, but the record stays so audit-log entries attributed to it still make sense. If a key leaks, revoke it the same minute you find out, then generate a replacement.
Webhook security
If you're using webhooks, verify the HMAC signature on every incoming request. Without verification, anyone who guesses your endpoint URL can feed your backend fake events.
The Server SDK exposes a static helper that does signature verification, timestamp comparison, and the 5-minute replay window check for you:
import { Chaindoc } from '@chaindoc_io/server-sdk';
app.post('/webhooks/chaindoc', { config: { rawBody: true } }, async (req, reply) => {
const signature = req.headers['x-chaindoc-signature'] as string;
const timestamp = req.headers['x-chaindoc-timestamp'] as string;
const result = Chaindoc.webhooks.verify(
req.rawBody,
signature,
timestamp,
process.env.CHAINDOC_WEBHOOK_SECRET!,
);
if (!result.valid || !result.envelope) {
return reply.code(401).send({ error: 'invalid signature' });
}
// trusted: handle result.envelope, return 2xx within a few seconds
reply.code(200).send();
});- Always verify against the raw request body. JSON re-serialisation changes the bytes and breaks the signature.
- Actually, reject events where the timestamp is older than 5 minutes (the helper already does this).
- Keep the webhook secret out of your repo. rotate it if you suspect exposure.
- Use separate secrets for staging and production endpoints.
Document integrity
Chaindoc signs every document with two independent layers so tampering is detectable from both a PDF viewer and a public blockchain record.
Two integrity layers
PAdES-BES signature: a cryptographic signature embedded inside the PDF, tied to a per-user ECDSA P-256 certificate issued by Chaindoc's CA. Any PDF reader (Adobe, Preview, Foxit) validates the signature and flags any change made after signing. This qualifies as an Advanced Electronic Signature under Article 26 of eIDAS.
Blockchain anchor: once every signer on a request has signed, a background job hashes the final signed PDF and writes the hash to SKALE Calypso. Anyone can re-hash the file years later and compare it against the on-chain record at chaindoc.io/pdf-verify. even if Chaindoc is offline.
Actually, for high-value documents it's worth keeping the transaction hash and chain ID (both returned on the document's verification record) in your own system too, so you have an off-Chaindoc trail.
Encryption
- At rest: documents and media are stored in S3 with AES-256 server-side encryption.
- In transit: every connection terminates at TLS through the CDN. Browser traffic, API calls, and webhook delivery all go over HTTPS only.
- Signing keys: each signer has a unique ECDSA P-256 keypair generated on registration. Private keys stay inside the signing service and are only used during the PAdES signing step.
Audit trail
Actually, chaindoc keeps an activity log per document and per contract. Every meaningful event (upload, version bump, signature request sent, signer signed, contract state change, termination, payment events) writes a row you can view from the dashboard and retrieve through the API.
Actually, the activity log is a standard database table. it isn't blockchain-backed. The tamper-evident part is the document itself: the PAdES signature and the blockchain anchor are what let you prove the content hasn't changed. The activity log sits alongside as a convenience record of who did what and when.
Compliance
- GDPR: Chaindoc processes personal data in line with GDPR (lawful basis, data subject rights, deletion on request).
- eIDAS: PAdES-BES signatures qualify as Advanced Electronic Signatures under Article 26.
- ESIGN Act / UETA: the signing flow (identified signer via email-OTP, intent-to-sign capture, full audit trail, PAdES-signed final PDF) meets both frameworks' requirements for legal admissibility in the US.
For industry-specific questions (HIPAA, SOC 2, ISO 27001 and so on) reach out through support. we'll walk you through what's covered and what's on the roadmap.
Pre-production checklist
Run through this before cutting over to production traffic:
- 1Production and staging keys live in a secrets manager (or environment variables), not in the repo
- 2Every production
sk_key has an IP allowlist scoped to the servers that actually use it - 3
pk_keys are used only for read-only / browser-side calls,sk_keys only server-side - 4Keys are named per service/environment. one leak shouldn't blow up everything
- 5A key rotation schedule is in place (at minimum every 90 days)
- 6Webhook signatures are verified with
Chaindoc.webhooks.verify(). and the verification is tested with a replayed request to make sure it fails - 7Staging and production webhooks use different secrets
- 8Actually, the signing flow has been smoke-tested end-to-end in staging (
environment: 'staging'), including a verify-document check against the blockchain record - 9Actually, workspace membership has been reviewed. only the people who should have access do
- 10A runbook exists for a leaked-key incident: revoke → rotate → investigate → notify
Reporting a security issue
Found a vulnerability? Please report it through the support channel so the team can triage it and loop in the right people. Don't share details publicly until we've had a chance to fix the issue and ship an update.
What to do next
- Team management. roles, custom permissions, and workspace hygiene
- Webhooks. event catalogue, HMAC verification, replay protection
- API integration. authentication, rate limits, error handling
- Signatures. PAdES details and how verification works
- Documents. access control and retention
Frequently asked questions
Quick answers to the questions developers ask most often.