Skip to main content
Vault webhooks let your app subscribe to document lifecycle events for a specific vault. Use webhooks when you want to:
  • update UI state in near real time
  • trigger downstream workflows after ingestion
  • avoid polling document status endpoints

Delivery contract

  • At-least-once delivery: duplicate events are possible
  • No strict ordering guarantee: events can arrive out of order
  • Per-vault subscriptions: subscriptions are scoped to a single vault
  • HTTPS only: callback URLs must use https:// and cannot target localhost/private networks
Use event.id as your idempotency key. Persist processed event IDs and ignore repeats.

Event payload

Every webhook delivery includes a stable envelope:
{
  "id": "evt_abc123",
  "version": "2026-02-12",
  "eventType": "vault.ingest.completed",
  "occurredAt": "2026-02-13T00:31:21.214Z",
  "organizationId": "org_123",
  "vaultId": "vault_123",
  "objectId": "obj_123",
  "resourceType": "vault_object",
  "resourceId": "obj_123",
  "data": {
    "status": "completed"
  }
}

Delivery headers and signing

Case.dev includes these headers on webhook requests:
  • X-Case-Event
  • X-Case-Event-Id
  • X-Case-Delivery-Id
  • X-Case-Timestamp
  • X-Case-Webhook-Version
  • X-Case-Signature (only when signingSecret is configured)
Signature format:
sha256=...
Signed message:
<X-Case-Timestamp>.<raw_request_body>
TypeScript signature verification example
import crypto from 'crypto'

function verifyCaseWebhook(params: {
  rawBody: string
  timestamp: string
  signatureHeader: string
  signingSecret: string
}) {
  const expected = crypto
    .createHmac('sha256', params.signingSecret)
    .update(`${params.timestamp}.${params.rawBody}`)
    .digest('hex')

  const received = params.signatureHeader.replace(/^sha256=/, '')
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))
}

Create a subscription

Endpoint
POST /vault/{id}/events/subscriptions
import Casedev from 'casedev';

const client = new Casedev({ apiKey: 'sk_case_YOUR_API_KEY' });

const subscription = await client.vault.events.subscriptions.create(vaultId, {
  callbackUrl: 'https://your-app.com/webhooks/case-vault',
  eventTypes: ['vault.ingest.completed', 'vault.ingest.failed'],
  signingSecret: 'whsec_your_secret'
});
objectIds is optional. If provided, only events for those vault objects are delivered.

Test your endpoint

Endpoint
POST /vault/{id}/events/subscriptions/{subscriptionId}/test
const testResult = await client.vault.events.subscriptions.test(vaultId, subscriptionId, {
  eventType: 'vault.ingest.completed',
  payload: { source: 'manual-test' }
});

Manage subscriptions

List subscriptions
GET /vault/{id}/events/subscriptions
Update subscription
PATCH /vault/{id}/events/subscriptions/{subscriptionId}
Delete subscription
DELETE /vault/{id}/events/subscriptions/{subscriptionId}
PATCH supports:
  • callbackUrl
  • eventTypes
  • objectIds
  • isActive
  • signingSecret
  • clearSigningSecret

Event types

Vault lifecycle events currently include:
  • vault.upload.initiated
  • vault.upload.completed
  • vault.upload.failed
  • vault.ingest.started
  • vault.ingest.completed
  • vault.ingest.failed
  • vault.ingest.extraction.started
  • vault.ingest.extraction.completed
  • vault.ingest.extraction.failed
  • vault.ingest.chunking.started
  • vault.ingest.chunking.completed
  • vault.ingest.chunking.failed
  • vault.ingest.embedding.started
  • vault.ingest.embedding.completed
  • vault.ingest.embedding.failed
  • vault.ingest.storage.started
  • vault.ingest.storage.completed
  • vault.ingest.storage.failed
For unsupported file types that are stored but not text-processed, completion payloads include a status of stored so clients can distinguish this from fully ingested content.

Retry behavior

If delivery fails, Case.dev retries with a lightweight backoff policy.
  • Max attempts: 3
  • Retryable failures: network errors, 408, 429, and 5xx
You should still treat every delivery as potentially duplicated.

Next step

If you have not integrated upload/ingest yet, start with
Upload & Process →