Continuous Monitoring & Webhook Signatures
8. Continuous Monitoring & Webhooks
Section titled “8. Continuous Monitoring & Webhooks”Setting Up Customer Monitor Watches
Section titled “Setting Up Customer Monitor Watches”Authenticated project sessions can establish continuous hourly resolution watches:
POST /v1/session/projects/prj_01J9X/watches HTTP/1.1Host: did.isContent-Type: application/json
{ "did": "did:web:identity.foundation"}Configuring Customer Webhook Deliveries
Section titled “Configuring Customer Webhook Deliveries”Register a private HTTPS receiver to receive signed change alerts:
POST /v1/session/projects/prj_01J9X/webhook HTTP/1.1Host: did.isContent-Type: application/json
{ "url": "https://api.yourcompany.com/webhooks/didis"}Response:
{ "config": { "id": "whc_01J9X...", "url": "https://api.yourcompany.com/webhooks/didis", "active": true }, "secret": "whsec_32_byte_random_secret..."}Save secret immediately. It is displayed exactly once and cannot be recovered.
Verifying Customer Webhook Signatures
Section titled “Verifying Customer Webhook Signatures”Signature Specification
Section titled “Signature Specification”- Header:
X-DIDIS-Signature: t=<creationTimestamp>,v1=<64_char_hex> - Header:
X-DIDIS-Event-ID: cevent_<64_char_hex> - Hash algorithm: HMAC-SHA256
- Signed payload:
<creationTimestamp>.<rawBodyBytes>
TypeScript Verification
Section titled “TypeScript Verification”import { verifyCustomerMonitorSignature } from "@didis/client";
// In your Express, Next.js, or Fastify webhook handler:export async function handleWebhook(req: Request) { const rawBody = await req.text(); const signature = req.headers.get("x-didis-signature") ?? ""; const eventId = req.headers.get("x-didis-event-id") ?? "";
const isValid = await verifyCustomerMonitorSignature( process.env.DIDIS_WEBHOOK_SECRET!, rawBody, signature, { tenantId: "tenant_01J9X...", projectId: "project_01J9X...", eventId: eventId }, 300 // Max age in seconds (5 minutes) );
if (!isValid) { return new Response("Invalid signature", { status: 401 }); }
const event = JSON.parse(rawBody); console.log(`Received event: ${event.type} for DID: ${event.did}`); return new Response("OK", { status: 200 });}Python Verification
Section titled “Python Verification”import osfrom didis import verify_customer_monitor_signature
def handle_webhook(request_body_bytes: bytes, headers: dict) -> bool: signature = headers.get("X-DIDIS-Signature", "") event_id = headers.get("X-DIDIS-Event-ID", "")
return verify_customer_monitor_signature( secret=os.environ["DIDIS_WEBHOOK_SECRET"], body=request_body_bytes, signature=signature, tenant_id="tenant_01J9X...", project_id="project_01J9X...", event_id=event_id, max_age_seconds=300 )Replay Tolerance & Deduplication Best Practices
Section titled “Replay Tolerance & Deduplication Best Practices”- Deduplicate
event.id: Delivery is at least once. Store processedevent.idvalues in Redis or your database with a 30-day TTL. If an event ID has already been processed, return200 OKimmediately without re-executing side effects. - Creation Timestamp Immutability: The signed timestamp
tis fixed at event creation time and remains unchanged across retry attempts. - Delivery Retry Schedule: Failed deliveries retry up to 5 times (backoff: 60s, 120s, 240s, 480s) before transitioning to
DEAD_LETTER.