> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mindset.ai/llms.txt
> Use this file to discover all available pages before exploring further.

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

<CardGroup cols={2}>
  <Card title="Update Database" icon="database">
    Update your database automatically
  </Card>

  <Card title="Send Notifications" icon="bell">
    Send notifications to other administrators
  </Card>

  <Card title="Trigger Workflows" icon="arrows-spin">
    Trigger custom workflows
  </Card>

  <Card title="Update Analytics" icon="chart-line">
    Update analytics dashboards and log audit trails
  </Card>
</CardGroup>

## Why Use Webhooks?

<Tabs>
  <Tab title="With Webhooks">
    **Automatic notifications → Minimal load, immediate response**

    <Check>
      Notified within 5 seconds of changes
    </Check>

    <Check>
      No need to constantly poll APIs
    </Check>

    <Check>
      React immediately to tenant actions
    </Check>

    <Check>
      Every change recorded with timestamps
    </Check>
  </Tab>

  <Tab title="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
  </Tab>
</Tabs>

## Supported Events

Webhooks fire for 3 resource types:

<AccordionGroup>
  <Accordion title="Agents" defaultOpen icon="robot">
    **Events:**

    * `agent.created`
    * `agent.updated`
    * `agent.deleted`

    **Description:** AI agents configured by tenants
  </Accordion>

  <Accordion title="Contexts" icon="book">
    **Events:**

    * `context.created`
    * `context.updated`
    * `context.deleted`

    **Description:** Knowledge bases and document collections
  </Accordion>

  <Accordion title="Context Files" icon="file">
    **Events:**

    * `contextfile.created`
    * `contextfile.updated`
    * `contextfile.deleted`

    **Description:** Files uploaded to contexts
  </Accordion>
</AccordionGroup>

<Note>
  Webhooks fire for all tenants in your app. Use the `externalTenantId` field in each payload to identify which tenant made the change.
</Note>

## Quick Start Guide

### Step 1: Create Your Webhook Endpoint

Create an HTTPS endpoint that accepts POST requests:

```javascript theme={null}
// 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.
</Warning>

### Step 2: Register Your Webhook

**API Endpoint:**

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

**Request:**

```bash theme={null}
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 theme={null}
{
  "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).
</Warning>

### Step 3: Test Your Integration

<Steps>
  <Step title="Make a Change">
    Make a change in the AMS SDK (create an agent, upload a file, etc.)
  </Step>

  <Step title="Verify Receipt">
    Verify your endpoint receives the webhook
  </Step>

  <Step title="Check Logs">
    Check the browser console or server logs
  </Step>
</Steps>

## 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 theme={null}
{
  "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.
</Warning>

<Tabs>
  <Tab title="Node.js Implementation">
    ```javascript theme={null}
    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 });
    });
    ```
  </Tab>

  <Tab title="Python Implementation">
    ```python theme={null}
    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
    ```
  </Tab>
</Tabs>

## Handle Webhook Events

### Basic Event Handling

```javascript theme={null}
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 theme={null}
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 theme={null}
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.
</Tip>

## Manage Webhooks

### List All Webhooks

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

### Update Webhook

```bash theme={null}
PATCH /api/v1/appuid/{appUid}/webhooks/{webhookUid}

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

### Pause Webhook

```bash theme={null}
PATCH /api/v1/appuid/{appUid}/webhooks/{webhookUid}

{
  "active": false
}
```

### Delete Webhook

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

## Best Practices

<AccordionGroup>
  <Accordion title="Security ✅" icon="shield-check">
    * Always verify signatures
    * Use HTTPS endpoints
    * Store secrets securely
    * Rotate secrets periodically
  </Accordion>

  <Accordion title="Reliability ✅" icon="check-double">
    * Respond within 5 seconds
    * Implement idempotency using `eventId`
    * Handle failures gracefully
    * Process heavy work asynchronously
  </Accordion>

  <Accordion title="Monitoring ✅" icon="chart-line">
    * Log successful and failed deliveries
    * Monitor `lastTriggeredAt` timestamps
    * Alert on extended periods without webhooks
  </Accordion>
</AccordionGroup>

## Troubleshooting

### Webhooks Not Received

<Steps>
  <Step title="Check Webhook Status">
    Webhook is registered and `active: true`
  </Step>

  <Step title="Verify Endpoint URL">
    Endpoint URL is correct and accessible
  </Step>

  <Step title="Check Firewall">
    Firewall allows HTTPS traffic from Mindset servers
  </Step>
</Steps>

### Signature Verification Fails

Check:

<Check>
  Using correct secret from registration response
</Check>

<Check>
  Using raw request body (not parsed JSON)
</Check>

<Check>
  UTF-8 encoding is correct
</Check>

### Duplicate Deliveries

**Solution:** Implement idempotency using `eventId` (see example above)

## Complete Working Example

```javascript theme={null}
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'));
```

***
