m4Mindset docs

Mindset v3 / Deploy in your app / Webhooks

View as Markdown

Webhooks API - User Guide

Receive notifications when changes are made to your Mindset AI resources

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?

Automatic notifications → Minimal load, immediate response

Poll APIs every few seconds → High server load, delayed response

  • Constant polling increases server load
  • Delayed detection of changes
  • Higher API usage and costs
  • Increased latency in response

Supported Events

Webhooks fire for 3 resource types:

Agents

Events:

  • agent.created
  • agent.updated
  • agent.deleted

Description: AI agents configured by tenants

Contexts

Events:

  • context.created
  • context.updated
  • context.deleted

Description: Knowledge bases and document collections

Context Files

Events:

  • contextfile.created
  • contextfile.updated
  • contextfile.deleted

Description: Files uploaded to contexts

Quick Start Guide

Step 1: Create Your Webhook Endpoint

Create an HTTPS endpoint that accepts POST requests:

javascript
// 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);
});

Step 2: Register Your Webhook

API Endpoint:

text
POST https://{environment}.api.mindset.ai/api/v1/appuid/{appUid}/webhooks

Request:

bash
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:

json
{
  "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"
}

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

text
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

json
{
  "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

javascript
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 });
});
python
import hmac
import hashlib

def verify_webhook_signature(payload: str, signature: str, secret: str) -> bool:
    # Remove 'sha256=' prefix
    if signature.startswith('sha256='):
        signature = signature[7:]

    # Compute HMAC-SHA256
    computed = hmac.new(
        secret.encode('utf-8'),
        payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    # Constant-time comparison
    return hmac.compare_digest(computed, signature)

# Flask example
@app.route('/webhooks/mindset', methods=['POST'])
def webhook_handler():
    signature = request.headers.get('X-Webhook-Signature')
    payload = request.get_data(as_text=True)
    secret = os.environ['MINDSET_WEBHOOK_SECRET']

    if not verify_webhook_signature(payload, signature, secret):
        return jsonify({'error': 'Invalid signature'}), 401

    event = request.json
    handle_webhook_event(event)
    return jsonify({'received': True}), 200

Handle Webhook Events

Basic Event Handling

javascript
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:

javascript
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:

javascript
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);
  });
});

Manage Webhooks

List All Webhooks

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

Update Webhook

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

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

Pause Webhook

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

{
  "active": false
}

Delete Webhook

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

Best Practices

Security ✅
  • Always verify signatures
  • Use HTTPS endpoints
  • Store secrets securely
  • Rotate secrets periodically
Reliability ✅
  • Respond within 5 seconds
  • Implement idempotency using eventId
  • Handle failures gracefully
  • Process heavy work asynchronously
Monitoring ✅
  • 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:

Duplicate Deliveries

Solution: Implement idempotency using eventId (see example above)

Complete Working Example

javascript
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'));