# Webhooks API - User Guide

> You're reading the Mindset v3 documentation. v3 is no longer sold; this is reference for existing customers. Mindset 4 docs are at /docs.

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?

#### With Webhooks

**Automatic notifications → Minimal load, immediate response**

> **Check:** Notified within 5 seconds of changes

> **Check:** No need to constantly poll APIs

> **Check:** React immediately to tenant actions

> **Check:** Every change recorded with timestamps

#### Without Webhooks

**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

> **Note:** 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:

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

> **Warning:** **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:**

```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"
}
```

> **Warning:** **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

```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

| Field                 | Type   | Description                                   |
| --------------------- | ------ | --------------------------------------------- |
| eventId               | string | Unique event identifier (use for idempotency) |
| event                 | string | Event name (e.g., `agent.created`)            |
| timestamp             | string | When event occurred (ISO8601)                 |
| data                  | object | Complete resource object                      |
| data.externalTenantId | string | Identifies which tenant made the change       |

## Security: Verify Signatures

> **Warning:** **Critical:** Always verify webhook signatures to prevent unauthorized requests.

#### Node.js Implementation

```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 Implementation

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

> **Tip:** Always respond to webhook requests within 5 seconds to prevent timeouts.

## Manage Webhooks

### List All Webhooks

```
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

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

> **Check:** Using correct secret from registration response

> **Check:** Using raw request body (not parsed JSON)

> **Check:** UTF-8 encoding is correct

### 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'));
```

---
