Workspace & API Key Governance
8. Accounts, Projects, & Governance
Section titled “8. Accounts, Projects, & Governance”The Account Console (/account) manages institutional workspaces, API credentials, continuous monitoring targets, and billing quotas.
┌────────────────────────────────────────────────────────────────────────────────────────┐│ ACCOUNT & PROJECT CONSOLE (/account) │├────────────────────────────┬─────────────────────────────┬─────────────────────────────┤│ API Keys │ Continuous Monitor │ Signed Webhooks ││ Issue and atomically │ Schedule hourly checks for │ Configure at-least-once ││ rotate 256-bit project │ DIDs, diffs, and 30-day │ HMAC-signed change alerts ││ resolution tokens. │ audit reports. │ for downstream receivers. │└────────────────────────────┴─────────────────────────────┴─────────────────────────────┘Tenants, Workspaces, & Roles
Section titled “Tenants, Workspaces, & Roles”DID.is isolates data by Tenant and Project:
- Authentication: Powered by Better Auth 1.7.7, backed by an isolated database (
accounts.auth.sqlite). - Session Bridge: Authenticated browser sessions map to an opaque subject. The web tier forwards authenticated requests to
resolver-corevia an internal server bridge token; client role or Authorization headers are stripped. - Team Roles:
- Owner: Full control over tenant lifecycle, account closure, and project ownership transfers.
- Admin: Manages team members, invites, billing subscriptions, and webhooks.
- Developer: Issues and rotates project API keys, configures monitor watches, and verifies claims.
- Viewer: Read-only inspection of project metrics, logs, and evidence.
Scoped Project API Keys & Secret Handling
Section titled “Scoped Project API Keys & Secret Handling”- Format: 256-bit OS-random tokens with the prefix
didis_(e.g.,didis_9a8f2...). - Digest Storage: The raw token is never stored in SQLite. Only a domain-separated SHA-256 hash is retained.
- One-Time Secret Presentation:
When a key is created or rotated, the secret is displayed once in a dedicated interface:
- Secrets are kept exclusively in active component memory.
- 60-Second Auto-Hide: A countdown timer destroys the secret from view after 60 seconds.
- Visibility Change Guard: If the user minimizes the browser or switches tabs (
visibilitychange), the secret is discarded immediately. - No clipboard auto-copying or persistent browser caching is permitted.
- Capacity Limits: Enforces hard caps of 10 active keys and 100 retained records per project (revoked keys remain in retained records to maintain immutable audit trails).
- Atomic Rotation:
/v1/session/projects/{project}/keys/{key}/rotateprovisions a replacement key and revokes the predecessor within a single atomic SQLite transaction.
Continuous Monitoring Watches
Section titled “Continuous Monitoring Watches”Continuous monitoring provides automated change detection for mission-critical identifiers:
- Check Cadence: Hourly resolution executed by the daemon worker.
- Targets: Up to 5 identifiers on Pro, up to 25 identifiers on Max.
- Check Budget: 18,000 scheduled resolution attempts per rolling 30-day window per project (failed or unreachable upstream checks consume budget to prevent unbounded polling loops). Scheduled checks do not consume standard API quota.
- Event Audit Pages: Query private event streams in bounded 50-item pages.
- 30-Day Downloadable Reports: Export a comprehensive, cryptographic 30-day event log as a single JSON file (capped at 1,000 events or 8 MiB; oversized reports must be paged to prevent memory exhaustion).
Customer Webhook Delivery & Signature Verification
Section titled “Customer Webhook Delivery & Signature Verification”When a monitored identifier changes (document update, key rotation, deactivation, or domain linkage lapse), DID.is dispatches an HTTP POST event to your configured endpoint.
Delivery Guarantees & Lifecycle
Section titled “Delivery Guarantees & Lifecycle”- At-Least-Once Delivery: Events are staged in an atomic SQLite outbox (
customer_webhook_outbox). - Recovery Leases: Deliveries acquire a 120-second lease to prevent duplicate concurrent deliveries across workers.
- Retries: Up to 5 delivery attempts with exponential backoff (60s, 120s, 240s, 480s). Unreachable endpoints move to
DEAD_LETTER. - Egress Hardening: Webhook endpoints must use HTTPS on port 443; private IP destinations, redirects, and query strings are rejected.
Webhook Payload Envelope
Section titled “Webhook Payload Envelope”{ "id": "evt_01J9X2M...", "type": "identity.document.changed", "did": "did:web:example.com", "watchId": "wat_01J9X...", "createdAt": "2026-10-01T12:00:00Z", "data": { "fromHash": "e3b0c442...", "toHash": "8f4b2a19...", "diff": { "keysAdded": ["did:web:example.com#key-2"], "keysRemoved": [] }, "headline": "Cryptographically controlled via Ed25519." }}Verifying Customer Webhook Signatures
Section titled “Verifying Customer Webhook Signatures”Every delivery includes signature headers:
X-DIDIS-Event-ID: evt_01J9X2M...X-DIDIS-Signature: t=1790000000,v1=a3b8f2c...The signature is computed as an HMAC-SHA256 digest over the creation timestamp, a period (.), and the exact raw UTF-8 request body:
HMAC-SHA256(webhook_secret, "1790000000.{\"id\":\"evt_01J9X2M...\"...}")Verification code using the official @didis/client TypeScript SDK:
import { verifyCustomerMonitorSignature } from "@didis/client";
const isValid = await verifyCustomerMonitorSignature( process.env.DIDIS_WEBHOOK_SECRET!, rawRequestBodyBytes, req.headers["x-didis-signature"], { tenantId: "ten_01J...", projectId: "prj_01J...", eventId: req.headers["x-didis-event-id"] });
if (!isValid) { throw new Error("Invalid webhook signature or expired timestamp");}Commercial Plans, Quotas, & Atomic Ledger
Section titled “Commercial Plans, Quotas, & Atomic Ledger”| Plan | Price (USD) | Standard API Resolutions | Hourly Monitor Targets | Max 30-Day Scheduled Checks | Support Level |
|---|---|---|---|---|---|
| Free | $0 / mo | 1,000 / month | 0 targets | — | Community / Docs |
| Pro | $49 / mo | 10,000 / month | 5 targets | 18,000 attempts | Developer Email |
| Max | $99 / mo | 50,000 / month | 25 targets | 18,000 attempts | Priority Operations |
Atomic Usage Ledger Architecture
Section titled “Atomic Usage Ledger Architecture”- Calendar Month Reservations: Quotas reset at 00:00:00 UTC on the first of each month.
- Append-Only Usage Ledger: Every metered API operation inserts an immutable record into
usage_ledger. SQLite database triggers explicitly rejectUPDATEandDELETEoperations on this table. - Idempotency: Repeated requests bearing the same
Idempotency-Keyheader return cached reservation permits without double-counting usage.