Skip to content

Workspace & API Key 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. │
└────────────────────────────┴─────────────────────────────┴─────────────────────────────┘

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-core via 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.
  • 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}/rotate provisions a replacement key and revokes the predecessor within a single atomic SQLite transaction.

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.

  • 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.
{
"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."
}
}

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");
}
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
  • 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 reject UPDATE and DELETE operations on this table.
  • Idempotency: Repeated requests bearing the same Idempotency-Key header return cached reservation permits without double-counting usage.