Skip to content

Webhooks ​

Webhooks allow your application to receive real-time notifications when events occur in the Essence Platform. Instead of polling the API, your application receives HTTP POST requests when something changes.

Overview ​

Webhooks are powered by the Unified Webhook Management Platform, providing:

  • Real-time Events - Instant notifications when data changes
  • Reliable Delivery - Automatic retries with exponential backoff
  • Secure Verification - HMAC-SHA256 signature verification
  • Event Filtering - Subscribe only to events you care about
  • Delivery Monitoring - Track delivery success and failures
  • Custom Headers - Add your own headers for routing

How Webhooks Work ​

mermaid
sequenceDiagram
    participant Essence as Essence Platform
    participant Webhook as Webhook Service
    participant Your as Your Endpoint

    Essence->>Webhook: Event occurs (e.g., vault created)
    Webhook->>Webhook: Generate signature
    Webhook->>Your: POST /webhooks/essence<br/>X-Essence-Signature: hmac_sha256=...
    Your->>Your: Verify signature
    Your->>Your: Process event
    Your-->>Webhook: 200 OK
    alt If request fails
        Webhook->>Webhook: Wait (exponential backoff)
        Webhook->>Your: Retry (up to 5 times)
    end

Available Events ​

Vault Events ​

Event TypeDescriptionPayload
vault.createdNew vault createdVault object
vault.updatedVault modifiedVault object + changes
vault.deletedVault deletedVault ID
vault.quota.warningStorage quota at 80%Vault object + usage
vault.quota.exceededStorage quota at 100%Vault object + usage

Document Events ​

Event TypeDescriptionPayload
document.uploadedNew document addedDocument object
document.updatedDocument metadata changedDocument object + changes
document.deletedDocument removedDocument ID + vault ID
document.sharedDocument sharedShare object
document.share.accessedShared document accessedAccess log
document.share.expiredShare link expiredShare ID
Event TypeDescriptionPayload
consent.grantedUser granted consentConsent object
consent.revokedUser revoked consentConsent ID + reason
consent.expiredConsent period endedConsent object
consent.renewedConsent renewedConsent object

User Events ​

Event TypeDescriptionPayload
user.createdNew user registeredUser object
user.updatedUser profile changedUser object + changes
user.deletedUser account deletedUser ID
user.loginUser logged inLogin details

Instance Events ​

Event TypeDescriptionPayload
instance.health.degradedInstance health issueHealth status
instance.health.recoveredInstance recoveredHealth status
instance.quota.warningInstance quota warningUsage stats

Webhook Registration ​

Creating a Webhook ​

typescript
import { EssenceClient } from '@essence/sdk';

const client = new EssenceClient({
  apiKey: process.env.ESSENCE_API_KEY
});

// Register webhook
const webhook = await client.webhooks.create({
  url: 'https://your-app.com/webhooks/essence',
  events: [
    'vault.created',
    'document.uploaded',
    'document.shared'
  ],
  description: 'Production webhook for vault and document events',
  secret: 'your_webhook_secret_here', // Used for signature verification
  headers: {
    'X-Custom-Header': 'your-value'
  },
  active: true
});

console.log('Webhook created:', webhook.id);

Using API Directly ​

bash
curl -X POST "https://api.essence.digital/api/v1/webhooks" \
  -H "X-Api-Key: essence_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/essence",
    "events": ["vault.created", "document.uploaded"],
    "description": "Production webhook",
    "secret": "your_webhook_secret_here"
  }'

Using GraphQL ​

graphql
mutation CreateWebhook {
  createWebhook(input: {
    url: "https://your-app.com/webhooks/essence"
    events: ["vault.created", "document.uploaded"]
    description: "Production webhook"
    secret: "your_webhook_secret_here"
  }) {
    success
    webhook {
      id
      url
      events
      active
      createdAt
    }
  }
}

Configuration Options ​

OptionTypeRequiredDescription
urlstringYesHTTPS endpoint to receive events
eventsstring[]YesArray of event types to subscribe to
descriptionstringNoHuman-readable description
secretstringYesSecret key for signature verification (min 32 chars)
headersobjectNoCustom headers to include in requests
activebooleanNoEnable/disable webhook (default: true)
metadataobjectNoCustom metadata for your use

Event Payload Structure ​

All webhook events follow this structure:

typescript
interface WebhookEvent {
  // Event metadata
  id: string;                    // Unique event ID
  type: string;                  // Event type (e.g., "vault.created")
  timestamp: string;             // ISO 8601 timestamp
  apiVersion: string;            // API version (e.g., "v1")

  // Event data
  data: {
    object: any;                 // The resource object
    previous?: any;              // Previous state (for updates)
    changes?: string[];          // Changed fields (for updates)
  };

  // Context
  context: {
    userId: string;              // User who triggered event
    instanceId: string;          // Instance where event occurred
    requestId: string;           // Original request ID
    ipAddress?: string;          // IP address (if applicable)
  };
}

Example: Vault Created Event ​

json
{
  "id": "evt_1234567890abcdef",
  "type": "vault.created",
  "timestamp": "2025-11-06T10:30:00Z",
  "apiVersion": "v1",
  "data": {
    "object": {
      "id": "vault-123",
      "name": "Medical Records",
      "description": "Patient health information",
      "category": "health",
      "userId": "user-456",
      "instanceId": "instance-789",
      "documentCount": 0,
      "totalSize": 0,
      "isActive": true,
      "createdAt": "2025-11-06T10:30:00Z",
      "updatedAt": "2025-11-06T10:30:00Z"
    }
  },
  "context": {
    "userId": "user-456",
    "instanceId": "instance-789",
    "requestId": "req_abc123",
    "ipAddress": "192.168.1.1"
  }
}

Example: Document Uploaded Event ​

json
{
  "id": "evt_9876543210fedcba",
  "type": "document.uploaded",
  "timestamp": "2025-11-06T10:35:00Z",
  "apiVersion": "v1",
  "data": {
    "object": {
      "id": "doc-789",
      "vaultId": "vault-123",
      "fileName": "lab_results.pdf",
      "sizeBytes": 1048576,
      "mimeType": "application/pdf",
      "encryptionMetadata": {
        "algorithm": "AES-256-GCM",
        "keyDerivation": "PBKDF2"
      },
      "uploadedAt": "2025-11-06T10:35:00Z"
    }
  },
  "context": {
    "userId": "user-456",
    "instanceId": "instance-789",
    "requestId": "req_xyz789"
  }
}

Signature Verification ​

All webhook requests include an X-Essence-Signature header for verification.

Signature Format ​

X-Essence-Signature: hmac_sha256=<signature>

Verifying Signatures ​

Node.js/TypeScript ​

typescript
import crypto from 'crypto';

function verifyWebhookSignature(
  payload: string | Buffer,
  signature: string,
  secret: string
): boolean {
  // Extract signature from header (format: "hmac_sha256=<signature>")
  const signatureParts = signature.split('=');
  if (signatureParts[0] !== 'hmac_sha256') {
    return false;
  }

  const receivedSignature = signatureParts[1];

  // Compute expected signature
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  // Use timing-safe comparison
  return crypto.timingSafeEqual(
    Buffer.from(receivedSignature),
    Buffer.from(expectedSignature)
  );
}

// Express.js example
import express from 'express';

const app = express();

app.post(
  '/webhooks/essence',
  express.raw({ type: 'application/json' }), // Get raw body
  (req, res) => {
    const signature = req.headers['x-essence-signature'] as string;
    const secret = process.env.WEBHOOK_SECRET!;

    // Verify signature
    if (!verifyWebhookSignature(req.body, signature, secret)) {
      console.error('Invalid webhook signature');
      return res.status(401).send('Invalid signature');
    }

    // Parse and process event
    const event = JSON.parse(req.body.toString());
    console.log('Received event:', event.type);

    // Handle event
    handleWebhookEvent(event);

    // Respond quickly (process async if needed)
    res.status(200).send('OK');
  }
);

Python ​

python
import hmac
import hashlib

def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
    """Verify webhook signature"""
    # Extract signature
    if not signature.startswith('hmac_sha256='):
        return False

    received_signature = signature.split('=', 1)[1]

    # Compute expected signature
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()

    # Timing-safe comparison
    return hmac.compare_digest(received_signature, expected_signature)

# Flask example
from flask import Flask, request

app = Flask(__name__)

@app.route('/webhooks/essence', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Essence-Signature', '')
    secret = os.environ['WEBHOOK_SECRET']

    # Verify signature
    if not verify_webhook_signature(request.data, signature, secret):
        return 'Invalid signature', 401

    # Parse and process event
    event = request.json
    print(f'Received event: {event["type"]}')

    # Handle event
    handle_webhook_event(event)

    return 'OK', 200

Using SDK Helper ​

typescript
import { verifyWebhookSignature } from '@essence/sdk';

app.post('/webhooks/essence', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-essence-signature'] as string;
  const secret = process.env.WEBHOOK_SECRET!;

  if (!verifyWebhookSignature(req.body, signature, secret)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body.toString());
  handleWebhookEvent(event);

  res.status(200).send('OK');
});

Event Handling ​

Basic Event Handler ​

typescript
async function handleWebhookEvent(event: WebhookEvent) {
  console.log(`Processing event: ${event.type} (${event.id})`);

  switch (event.type) {
    case 'vault.created':
      await handleVaultCreated(event.data.object);
      break;

    case 'document.uploaded':
      await handleDocumentUploaded(event.data.object);
      break;

    case 'document.shared':
      await handleDocumentShared(event.data.object);
      break;

    case 'consent.granted':
      await handleConsentGranted(event.data.object);
      break;

    case 'consent.revoked':
      await handleConsentRevoked(event.data.object);
      break;

    default:
      console.log(`Unhandled event type: ${event.type}`);
  }
}

async function handleVaultCreated(vault: any) {
  console.log(`New vault created: ${vault.name} (${vault.id})`);

  // Example: Send welcome email
  await sendEmail({
    to: vault.userEmail,
    subject: 'Vault Created',
    body: `Your vault "${vault.name}" has been created successfully.`
  });

  // Example: Initialize analytics
  await analytics.track('vault_created', {
    vaultId: vault.id,
    category: vault.category
  });
}

async function handleDocumentUploaded(document: any) {
  console.log(`Document uploaded: ${document.fileName}`);

  // Example: Trigger document processing
  await processDocument(document.id);

  // Example: Update statistics
  await updateStorageStats(document.vaultId);
}

Asynchronous Processing ​

For long-running tasks, respond quickly and process asynchronously:

typescript
import { Queue } from 'bull';

const webhookQueue = new Queue('webhooks', {
  redis: { host: 'localhost', port: 6379 }
});

// Webhook endpoint - respond quickly
app.post('/webhooks/essence', express.raw({ type: 'application/json' }), async (req, res) => {
  const signature = req.headers['x-essence-signature'] as string;

  if (!verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET!)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body.toString());

  // Queue for async processing
  await webhookQueue.add('process-event', event);

  // Respond immediately
  res.status(200).send('OK');
});

// Worker - process events asynchronously
webhookQueue.process('process-event', async (job) => {
  const event = job.data;
  await handleWebhookEvent(event);
});

Testing Webhooks Locally ​

1. Using ngrok ​

bash
# Install ngrok
npm install -g ngrok

# Start your local server
node server.js # Running on http://localhost:3000

# Create tunnel
ngrok http 3000

# Use the HTTPS URL for webhook
# https://abc123.ngrok.io/webhooks/essence

2. Using localhost.run ​

bash
# Create tunnel (no installation needed)
ssh -R 80:localhost:3000 localhost.run

# Use the provided URL for webhook

3. Using webhook.site ​

For testing without code:

  1. Visit webhook.site
  2. Copy your unique URL
  3. Register webhook with that URL
  4. Trigger events and inspect payloads

4. Testing with SDK ​

typescript
// Manually trigger a test webhook
const testResult = await client.webhooks.test(webhookId);
console.log('Test delivery status:', testResult.status);

Retry and Failure Handling ​

Retry Policy ​

The Unified Webhook Management Platform automatically retries failed deliveries:

AttemptDelayTotal Time
1Immediate0s
21 minute1m
35 minutes6m
415 minutes21m
51 hour1h 21m
64 hours5h 21m

Successful Response ​

Return 200 OK within 10 seconds to acknowledge receipt:

typescript
app.post('/webhooks/essence', async (req, res) => {
  // Verify signature...

  // Respond immediately
  res.status(200).send('OK');

  // Process asynchronously
  processEventAsync(event);
});

Handling Failures ​

typescript
// Monitor webhook failures
const webhook = await client.webhooks.get(webhookId);
console.log('Failed deliveries:', webhook.stats.failedDeliveries);
console.log('Last error:', webhook.stats.lastError);

// Get recent delivery attempts
const attempts = await client.webhooks.getDeliveryAttempts(webhookId, {
  page: 1,
  pageSize: 20,
  status: 'failed'
});

attempts.forEach(attempt => {
  console.log(`Attempt ${attempt.attemptNumber}: ${attempt.statusCode} - ${attempt.error}`);
});

Manual Retry ​

typescript
// Retry a specific failed delivery
await client.webhooks.retryDelivery(webhookId, deliveryId);

// Retry all failed deliveries
await client.webhooks.retryAllFailed(webhookId);

Troubleshooting ​

Common Issues ​

1. Signature Verification Fails ​

typescript
// Check you're using the raw body (not parsed JSON)
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
  // req.body is a Buffer - correct!
});

// ❌ Wrong: Using express.json()
app.post('/webhooks', express.json(), (req, res) => {
  // req.body is parsed object - signature will fail!
});

2. Webhook Endpoint Not Accessible ​

bash
# Test your endpoint is publicly accessible
curl -X POST https://your-app.com/webhooks/essence \
  -H "Content-Type: application/json" \
  -d '{"test": true}'

# Check firewall rules allow incoming connections
# Ensure HTTPS is properly configured

3. Request Timeout ​

typescript
// Respond within 10 seconds
app.post('/webhooks/essence', async (req, res) => {
  // ❌ Bad: Long synchronous processing
  await processEventForMinutes(event);
  res.send('OK');

  // ✅ Good: Quick response + async processing
  res.send('OK');
  processEventAsync(event);
});

4. Duplicate Events ​

typescript
// Handle idempotency
const processedEvents = new Set<string>();

async function handleWebhookEvent(event: WebhookEvent) {
  // Check if already processed
  if (processedEvents.has(event.id)) {
    console.log(`Event ${event.id} already processed, skipping`);
    return;
  }

  // Process event
  await processEvent(event);

  // Mark as processed
  processedEvents.add(event.id);

  // Store in database for persistence
  await db.processedEvents.create({ id: event.id, processedAt: new Date() });
}

Debugging Tools ​

typescript
// Enable webhook logging
const webhook = await client.webhooks.update(webhookId, {
  metadata: { debug: true }
});

// View recent deliveries
const deliveries = await client.webhooks.getDeliveries(webhookId, {
  page: 1,
  pageSize: 10
});

deliveries.forEach(delivery => {
  console.log(`Delivery ${delivery.id}:`, {
    status: delivery.status,
    statusCode: delivery.statusCode,
    duration: delivery.durationMs,
    attempt: delivery.attemptNumber,
    error: delivery.error
  });
});

Best Practices ​

1. Verify Signatures Always ​

typescript
// ✅ Always verify signatures
if (!verifyWebhookSignature(payload, signature, secret)) {
  return res.status(401).send('Invalid signature');
}

2. Respond Quickly ​

typescript
// ✅ Respond within 10 seconds
res.status(200).send('OK');
processEventAsync(event);

3. Handle Idempotency ​

typescript
// ✅ Store processed event IDs
await db.processedEvents.create({ id: event.id });

4. Monitor Failures ​

typescript
// ✅ Set up alerts for failures
if (webhook.stats.failureRate > 0.1) {
  await sendAlert('High webhook failure rate');
}

5. Use HTTPS Only ​

typescript
// ✅ Always use HTTPS endpoints
url: 'https://your-app.com/webhooks/essence'

// ❌ Never use HTTP
url: 'http://your-app.com/webhooks/essence'

6. Implement Circuit Breaker ​

typescript
// ✅ Temporarily disable webhooks if endpoint is down
if (consecutiveFailures > 10) {
  await client.webhooks.update(webhookId, { active: false });
  await sendAlert('Webhook disabled due to repeated failures');
}

Managing Webhooks ​

List All Webhooks ​

typescript
const webhooks = await client.webhooks.list();
webhooks.forEach(webhook => {
  console.log(`${webhook.description}: ${webhook.url}`);
});

Update Webhook ​

typescript
await client.webhooks.update(webhookId, {
  events: ['vault.created', 'vault.updated', 'vault.deleted'],
  description: 'Updated webhook configuration'
});

Disable Webhook ​

typescript
await client.webhooks.update(webhookId, { active: false });

Delete Webhook ​

typescript
await client.webhooks.delete(webhookId);

Rotate Secret ​

typescript
// Generate new secret
const newSecret = crypto.randomBytes(32).toString('hex');

// Update webhook
await client.webhooks.update(webhookId, { secret: newSecret });

// Update your environment
process.env.WEBHOOK_SECRET = newSecret;

Next Steps ​


Need help? Join our Discord community or email support.

Built with ❤️ for developers · Service status