Skip to main content

What are Webhooks?

Webhooks allow you to be notified when changes are made to parts of the Mindset AI system—without needing to repeatedly check by calling an API. You can use webhooks to:
  • Be notified of changes made by users to agent configuration
  • Monitor processing status of files you send to the ContextFiles API for ingestion
  • Track when contexts are created, updated, or deleted
  • Receive notifications when agents are modified
When these events occur, you’ll automatically receive notifications at your specified endpoint.

Real-World Example

When a tenant admin creates a new AI agent, you receive a notification allowing you to:

Update Database

Update your database automatically

Send Notifications

Send notifications to other administrators

Trigger Workflows

Trigger custom workflows

Update Analytics

Update analytics dashboards and log audit trails

Why Use Webhooks?

  • With Webhooks
  • Without Webhooks
Automatic notifications → Minimal load, immediate response
Notified within 5 seconds of changes
No need to constantly poll APIs
React immediately to tenant actions
Every change recorded with timestamps

Supported Events

Webhooks fire for 3 resource types:

Agents

Events:
  • agent.created
  • agent.updated
  • agent.deleted
Description: AI agents configured by tenants
Events:
  • context.created
  • context.updated
  • context.deleted
Description: Knowledge bases and document collections
Events:
  • contextfile.created
  • contextfile.updated
  • contextfile.deleted
Description: Files uploaded to contexts
Webhooks fire for all tenants in your app. Use the externalTenantId field in each payload to identify which tenant made the change.

Quick Start Guide

Step 1: Create Your Webhook Endpoint

Create an HTTPS endpoint that accepts POST requests:
// Example: Express.js
app.post('/webhooks/mindset', async (req, res) => {
  // 1. Verify signature (security - see below)
  const isValid = verifyWebhookSignature(req);
  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // 2. Process webhook event
  const event = req.body;
  console.log(`Received ${event.event} for tenant ${event.data.externalTenantId}`);

  // 3. Respond quickly (< 5 seconds)
  res.status(200).json({ received: true });

  // 4. Process heavy work asynchronously
  processWebhookAsync(event);
});
Security: Webhook endpoints MUST use HTTPS in production.

Step 2: Register Your Webhook

API Endpoint:
POST https://{environment}.api.mindset.ai/api/v1/appuid/{appUid}/webhooks
Request:
curl -X POST \
  https://your-environment.api.mindset.ai/api/v1/appuid/your-app-uid/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-platform.com/webhooks/mindset",
    "entities": ["agent", "context", "contextfile"],
    "active": true
  }'
Response:
{
  "uid": "webhook-abc123",
  "url": "https://your-platform.com/webhooks/mindset",
  "secret": "whsec_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6",
  "entities": ["agent", "context", "contextfile"],
  "active": true,
  "createdAt": "2025-01-20T14:30:00Z"
}
Important: Save the secret! You’ll need it to verify webhook signatures. Store it securely (environment variable or secrets manager).

Step 3: Test Your Integration

1

Make a Change

Make a change in the AMS SDK (create an agent, upload a file, etc.)
2

Verify Receipt

Verify your endpoint receives the webhook
3

Check Logs

Check the browser console or server logs

Webhook Payload Structure

HTTP Headers

Content-Type: application/json
X-Webhook-Signature: sha256=<HMAC-SHA256 signature>
X-Webhook-Event-Id: <unique event ID>
X-Webhook-Timestamp: <ISO8601 timestamp>

Payload Example

{
  "eventId": "evt_A1b2C3d4E5f6",
  "event": "agent.created",
  "timestamp": "2025-01-20T14:35:22.123Z",
  "appUid": "your-app-uid",
  "version": 1,
  "data": {
    "uid": "agent-abc123",
    "appUid": "app-123",
    "agentName": "Customer Support Agent",
    "externalTenantId": "tenant-xyz789",
    "live": true,
    "createdAt": "2025-01-20T14:35:22Z"
    // ... complete agent object
  }
}

Key Fields

FieldTypeDescription
eventIdstringUnique event identifier (use for idempotency)
eventstringEvent name (e.g., agent.created)
timestampstringWhen event occurred (ISO8601)
dataobjectComplete resource object
data.externalTenantIdstringIdentifies which tenant made the change

Security: Verify Signatures

Critical: Always verify webhook signatures to prevent unauthorized requests.
  • Node.js Implementation
  • Python Implementation
import crypto from 'crypto';

function verifyWebhookSignature(payload, signature, secret) {
  // Remove 'sha256=' prefix
  const providedSig = signature.startsWith('sha256=')
    ? signature.slice(7)
    : signature;

  // Compute HMAC-SHA256
  const computedSig = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');

  // Constant-time comparison (prevents timing attacks)
  return crypto.timingSafeEqual(
    Buffer.from(providedSig, 'hex'),
    Buffer.from(computedSig, 'hex')
  );
}

// Express.js middleware
app.post('/webhooks/mindset', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const payload = JSON.stringify(req.body);
  const secret = process.env.MINDSET_WEBHOOK_SECRET;

  if (!verifyWebhookSignature(payload, signature, secret)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process webhook...
  res.status(200).json({ received: true });
});

Handle Webhook Events

Basic Event Handling

async function handleWebhookEvent(event) {
  const { event: eventType, data } = event;

  console.log(`Processing ${eventType} for tenant ${data.externalTenantId}`);

  switch (eventType) {
    case 'agent.created':
      await db.agents.create({
        id: data.uid,
        name: data.agentName,
        tenantId: data.externalTenantId
      });
      break;

    case 'agent.updated':
      await db.agents.update(data.uid, {
        name: data.agentName
      });
      break;

    case 'agent.deleted':
      await db.agents.delete(data.uid);
      break;

    // Handle context events...
    case 'context.created':
    case 'context.updated':
    case 'context.deleted':
      // Your logic here
      break;

    // Handle file events...
    case 'contextfile.created':
    case 'contextfile.updated':
    case 'contextfile.deleted':
      // Your logic here
      break;
  }
}

Prevent Duplicate Processing (Idempotency)

Webhooks may be delivered multiple times. Use eventId to process each event only once:
async function handleWebhookEvent(event) {
  // Check if already processed
  const exists = await db.processedEvents.findOne({ eventId: event.eventId });
  if (exists) {
    console.log(`Event ${event.eventId} already processed`);
    return;
  }

  // Process the event
  await processEvent(event);

  // Mark as processed
  await db.processedEvents.create({
    eventId: event.eventId,
    processedAt: new Date(),
    event: event.event
  });
}

Respond Quickly (Async Processing)

Respond within 5 seconds. Move heavy work to background:
app.post('/webhooks/mindset', async (req, res) => {
  // Verify signature...

  // ✅ Respond immediately
  res.status(200).json({ received: true });

  // ✅ Process asynchronously
  processWebhookAsync(req.body).catch(error => {
    console.error('Webhook processing failed:', error);
  });
});
Always respond to webhook requests within 5 seconds to prevent timeouts.

Manage Webhooks

List All Webhooks

GET /api/v1/appuid/{appUid}/webhooks

Update Webhook

PATCH /api/v1/appuid/{appUid}/webhooks/{webhookUid}

{
  "url": "https://your-platform.com/webhooks/mindset-v2",
  "entities": ["agent", "context", "contextfile"],
  "active": true
}

Pause Webhook

PATCH /api/v1/appuid/{appUid}/webhooks/{webhookUid}

{
  "active": false
}

Delete Webhook

DELETE /api/v1/appuid/{appUid}/webhooks/{webhookUid}

Best Practices

  • Always verify signatures
  • Use HTTPS endpoints
  • Store secrets securely
  • Rotate secrets periodically
  • Respond within 5 seconds
  • Implement idempotency using eventId
  • Handle failures gracefully
  • Process heavy work asynchronously
  • Log successful and failed deliveries
  • Monitor lastTriggeredAt timestamps
  • Alert on extended periods without webhooks

Troubleshooting

Webhooks Not Received

1

Check Webhook Status

Webhook is registered and active: true
2

Verify Endpoint URL

Endpoint URL is correct and accessible
3

Check Firewall

Firewall allows HTTPS traffic from Mindset servers

Signature Verification Fails

Check:
Using correct secret from registration response
Using raw request body (not parsed JSON)
UTF-8 encoding is correct

Duplicate Deliveries

Solution: Implement idempotency using eventId (see example above)

Complete Working Example

import express from 'express';
import crypto from 'crypto';

const app = express();
const WEBHOOK_SECRET = process.env.MINDSET_WEBHOOK_SECRET;

app.post('/webhooks/mindset',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    // 1. Verify signature
    const signature = req.headers['x-webhook-signature'];
    const payload = req.body.toString('utf8');

    if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    // 2. Parse and respond quickly
    const event = JSON.parse(payload);
    res.status(200).json({ received: true });

    // 3. Process async
    processWebhookAsync(event);
  }
);

function verifyWebhookSignature(payload, signature, secret) {
  const providedSig = signature.replace('sha256=', '');
  const computedSig = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(providedSig, 'hex'),
    Buffer.from(computedSig, 'hex')
  );
}

async function processWebhookAsync(event) {
  // Check idempotency
  if (await isEventProcessed(event.eventId)) return;

  // Handle event
  switch (event.event) {
    case 'agent.created':
      await db.agents.create(event.data);
      break;
    // ... other events
  }

  // Mark processed
  await markEventProcessed(event.eventId);
}

app.listen(3000, () => console.log('Webhook server running'));