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

# Webhooks

> Set up real-time notifications for events in your Vambe account using webhooks. Get notified instantly when messages are received, tickets are created, or pipeline stages change.

## Overview

Webhooks allow your application to receive real-time notifications when events occur in your Vambe account. Instead of polling for changes, Vambe will send HTTP POST requests to your specified endpoint when events happen.

## Common Use Cases

* **Message Notifications**: Get notified when customers send messages
* **Ticket Updates**: Track when tickets are created, updated, or closed
* **Pipeline Changes**: Monitor when contacts move through pipeline stages
* **Contact Events**: Know when new contacts are created
* **Integration Sync**: Keep external systems in sync with Vambe data

## How Webhooks Work

1. **Configure Webhook**: Set up a webhook URL in your Vambe account
2. **Events Occur**: Customer sends message, ticket created, stage changed, etc.
3. **Vambe Sends Request**: HTTP POST sent to your webhook URL
4. **Your Server Responds**: Process the event and return 200 OK
5. **Retry Logic**: If your server doesn't respond, Vambe retries

## Webhook Management API

Use the webhooks API to manage your webhook configurations programmatically.

### Available Endpoints

Check the **Automation & Pipelines** API tab for webhook management endpoints:

* **Create Webhook**: `POST /api/webhooks`
* **List Webhooks**: `GET /api/webhooks`
* **Get Webhook**: `GET /api/webhooks/{id}`
* **Update Webhook**: `PUT /api/webhooks/{id}`
* **Delete Webhook**: `DELETE /api/webhooks/{id}`
* **Restore Webhook**: `POST /api/webhooks/{id}/restore`
* **Get Topics**: `GET /api/webhooks/topics`
* **Get Webhook Calls**: `GET /api/webhooks/calls`

## Event Types (Topics)

Subscribe to specific events to receive notifications:

### Message Events

**`message.received`** - Customer sends an inbound message

```json theme={null}
{
  "aiContactId": "38f37371-aa05-477a-b6c1-d161417@ed8d",
  "data": {
    "fromNumber": "56998389641",
    "message": "Ok",
    "messageId": "0f475695-ec1b-4ca8-b03d-a1c8d066b999",
    "messageType": "text",
    "name": "Waita",
    "stageId": "3abb9d28-141c-45ab-87f8-52919caf63a7",
    "toNumber": "56961210530"
  },
  "type": "message.received"
}
```

**`message.sent`** - Your team sends an outbound message

```json theme={null}
{
  "aiContactId": "e8e08253-aa12-4f32-b4c9-b1fbd180c26b",
  "data": {
    "error_details": [
      {
        "code": 131026,
        "error_data": {
          "details": "..."
        },
        "message": "Message undeliverable",
        "title": "Message undeliverable"
      }
    ],
    "fromNumber": "56961210530",
    "message": "¡Listo Eduardo! ✨ Aquí tienes tu propuesta definitiva...",
    "messageId": "467ae215-f27a-4c79-ac44-f46fd8a3b28d",
    "messageType": "template",
    "name": "Eduardo Mella Navarrete",
    "status": "failed",
    "toNumber": "56986874408"
  },
  "type": "message.sent"
}
```

**Note**: The `error_details` field in `message.sent` is optional and only present when there are delivery errors.

### Ticket Events

**`ticket.created`** - New support ticket opened

```json theme={null}
{
  "event": "ticket.created",
  "timestamp": "2024-09-30T10:00:00.000Z",
  "data": {
    "ticket_id": "ticket-123",
    "contact_id": "df980fc8-b6db-4820-bf22-2969482d106d",
    "contact_name": "Maria Rodriguez",
    "pipeline_id": "pipeline-456",
    "stage_id": "stage-789",
    "status": "OPEN"
  }
}
```

**`ticket.updated`** - Ticket status or assignment changes
**`ticket.closed`** - Ticket is resolved/closed

### Contact Events

**`contact.created`** - New contact added to system
**`contact.updated`** - Contact information modified

### Pipeline Events

**`stage.changed`** - Contact moves to different pipeline stage

```json theme={null}
{
  "event": "stage.changed",
  "timestamp": "2024-09-30T10:05:00.000Z",
  "data": {
    "contact_id": "df980fc8-b6db-4820-bf22-2969482d106d",
    "previous_stage_id": "stage-789",
    "new_stage_id": "stage-890",
    "pipeline_id": "pipeline-456",
    "changed_by": "agent-123"
  }
}
```

## Webhook Payload Structure

When an event occurs, Vambe sends a POST request to your webhook URL:

```json theme={null}
{
  "aiContactId": "38f37371-aa05-477a-b6c1-d161417@ed8d",
  "data": {
    "fromNumber": "56998389641",
    "message": "Hello, I need help",
    "messageId": "46964423",
    "messageType": "text",
    "name": "Maria Rodriguez",
    "stageId": "3abb9d28-141c-45ab-87f8-52919caf63a7",
    "toNumber": "56961210530"
  },
  "type": "message.received"
}
```

## Setting Up Webhooks

### 1. Create a Webhook Endpoint

Your server must have an endpoint that:

* Accepts POST requests
* Returns 200 OK status
* Responds within 5 seconds
* Processes events asynchronously (don't block the response)

<CodeGroup>
  ```javascript Express.js theme={null}
  // Express.js webhook endpoint
  const express = require('express');
  const app = express();

  app.use(express.json());

  app.post('/webhooks/vambe', async (req, res) => {
  const event = req.body;

  // Immediately return 200 OK
  res.status(200).json({ received: true });

  // Process event asynchronously
  try {
  await processVambeEvent(event);
  console.log(`✅ Processed ${event.type}`);
  } catch (error) {
  console.error(`❌ Error processing ${event.type}:`, error);
  }
  });

  async function processVambeEvent(event) {
  switch (event.type) {
  case 'message.received':
  await handleNewMessage(event.data);
  break;
  case 'message.sent':
  await handleMessageSent(event.data);
  break;
  case 'ticket.created':
  await handleNewTicket(event.data);
  break;
  case 'stage.changed':
  await handleStageChange(event.data);
  break;
  default:
  console.log('Unknown event:', event.type);
  }
  }

  app.listen(3000);

  ```

  ```python FastAPI theme={null}
  # FastAPI webhook endpoint
  from fastapi import FastAPI, BackgroundTasks
  from pydantic import BaseModel

  app = FastAPI()

  class WebhookEvent(BaseModel):
      type: str
      aiContactId: str
      data: dict

  async def process_vambe_event(event: WebhookEvent):
      """Process event asynchronously"""
      if event.type == "message.received":
          await handle_new_message(event.data)
      elif event.type == "message.sent":
          await handle_message_sent(event.data)
      elif event.type == "ticket.created":
          await handle_new_ticket(event.data)
      elif event.type == "stage.changed":
          await handle_stage_change(event.data)

  @app.post("/webhooks/vambe")
  async def vambe_webhook(
      event: WebhookEvent,
      background_tasks: BackgroundTasks
  ):
      # Return immediately
      background_tasks.add_task(process_vambe_event, event)
      return {"received": True}
  ```

  ```typescript NestJS theme={null}
  // NestJS webhook controller
  import { Controller, Post, Body, HttpCode } from '@nestjs/common';

  @Controller('webhooks')
  export class WebhooksController {
    constructor(
      private readonly webhookService: WebhookService,
      private readonly eventEmitter: EventEmitter2,
    ) {}

    @Post('vambe')
    @HttpCode(200)
    async handleVambeWebhook(@Body() event: any) {
      // Emit event for async processing
      this.eventEmitter.emit(`vambe.${event.type}`, event.data);

      // Return immediately
      return { received: true };
    }
  }

  // Event handler
  @OnEvent('vambe.message.received')
  async handleMessageReceived(data: any) {
    console.log('Processing new message:', data.message);
    // Your logic here
  }
  ```
</CodeGroup>

### 2. Configure in Vambe

Use the API to create a webhook configuration:

```bash theme={null}
curl --request POST \
  'https://api.vambe.me/api/webhooks' \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: your_api_key_here' \
  --data-raw '{
    "url": "https://your-domain.com/webhooks/vambe",
    "topics": ["message.received", "ticket.created"],
    "active": true
  }'
```

### 3. Test Your Webhook

* Send a test message to trigger the webhook
* Check your server logs to verify receipt
* Ensure you're returning 200 OK

## Security Best Practices

1. **Validate Requests**: Verify requests are from Vambe
2. **Use HTTPS**: Always use secure endpoints
3. **Verify Signatures**: Check webhook signatures if provided
4. **Rate Limiting**: Implement rate limiting on your endpoint
5. **Idempotency**: Handle duplicate events gracefully

## Error Handling

If your webhook endpoint fails to respond:

* **Retry Logic**: Vambe will retry failed webhooks
* **Backoff Strategy**: Increasing delays between retries
* **Max Retries**: After several failures, webhook may be disabled
* **Monitor Logs**: Check webhook call logs via API

## Real-World Use Cases

### Use Case 1: Auto-Response to High-Priority Messages

```javascript theme={null}
async function handleNewMessage(data) {
  // If message contains "urgent" keyword
  if (data.message.toLowerCase().includes('urgent')) {
    // Tag contact as high priority
    await fetch(
      `https://api.vambe.me/api/public/channels/whatsapp/${data.fromNumber}/tags`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': process.env.VAMBE_API_KEY,
        },
        body: JSON.stringify({ tags: ['urgent', 'high-priority'] }),
      },
    );

    // Send notification to team
    await sendSlackAlert(`🚨 Urgent message from ${data.name}`);
  }
}
```

### Use Case 2: CRM Integration on Ticket Creation

```javascript theme={null}
async function handleNewTicket(data) {
  // Create deal in external CRM
  await externalCRM.createDeal({
    contact_id: data.contact_id,
    contact_name: data.contact_name,
    source: 'Vambe Chat',
    stage: 'New Lead',
    vambe_ticket_id: data.ticket_id,
  });

  console.log('Deal created in CRM for', data.contact_name);
}
```

### Use Case 3: Update Analytics on Stage Change

```javascript theme={null}
async function handleStageChange(data) {
  // Track conversion funnel
  await analytics.track('Stage Changed', {
    contact_id: data.contact_id,
    from_stage: data.previous_stage_id,
    to_stage: data.new_stage_id,
    pipeline: data.pipeline_id,
    timestamp: new Date(),
  });

  // If moved to "Qualified" stage, notify sales team
  if (data.new_stage_id === 'qualified-stage-id') {
    await notifySalesTeam(data.contact_id);
  }
}
```

### Use Case 4: Auto-Assignment Based on Message Content

```javascript theme={null}
async function handleNewMessage(data) {
  const messageText = data.message.toLowerCase();

  // Route based on message content
  let assigneeId;

  if (messageText.includes('billing') || messageText.includes('payment')) {
    assigneeId = 'billing-team-member-id';
  } else if (messageText.includes('technical') || messageText.includes('bug')) {
    assigneeId = 'tech-support-member-id';
  } else {
    assigneeId = 'general-support-member-id';
  }

  // Auto-assign to appropriate team member
  // Note: Use the aiContactId from the parent webhook event
  await fetch(
    `https://api.vambe.me/api/public/contact/{aiContactId}/assign-team-member/${assigneeId}`,
    {
      method: 'POST',
      headers: { 'x-api-key': process.env.VAMBE_API_KEY },
    },
  );
}
```

## Webhook Management with API

### Create a Webhook

```bash theme={null}
curl --request POST \
  'https://api.vambe.me/api/webhooks' \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: your_api_key_here' \
  --data-raw '{
    "url": "https://your-domain.com/webhooks/vambe",
    "topics": ["message.received", "ticket.created", "stage.changed"],
    "active": true,
    "description": "Main webhook for production"
  }'
```

### List All Webhooks

```bash theme={null}
curl --request GET \
  'https://api.vambe.me/api/webhooks' \
  --header 'x-api-key: your_api_key_here'
```

### Update Webhook

```bash theme={null}
curl --request PUT \
  'https://api.vambe.me/api/webhooks/webhook-id-123' \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: your_api_key_here' \
  --data-raw '{
    "active": false
  }'
```

### Delete Webhook

```bash theme={null}
curl --request DELETE \
  'https://api.vambe.me/api/webhooks/webhook-id-123' \
  --header 'x-api-key: your_api_key_here'
```

### Get Available Topics

```bash theme={null}
curl --request GET \
  'https://api.vambe.me/api/webhooks/topics' \
  --header 'x-api-key: your_api_key_here'
```

### View Webhook Call History

```bash theme={null}
curl --request GET \
  'https://api.vambe.me/api/webhooks/calls' \
  --header 'x-api-key: your_api_key_here'
```

## Debugging Webhooks

### Log Everything During Development

```javascript theme={null}
app.post('/webhooks/vambe', (req, res) => {
  console.log('=== Webhook Received ===');
  console.log('Headers:', req.headers);
  console.log('Body:', JSON.stringify(req.body, null, 2));
  console.log('Event Type:', req.body.type);
  console.log('AI Contact ID:', req.body.aiContactId);
  console.log('========================');

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

  // Process after response
  processEvent(req.body);
});
```

### Test Webhook Locally with ngrok

```bash theme={null}
# 1. Start ngrok tunnel
ngrok http 3000

# 2. Use ngrok URL for webhook
# https://abc123.ngrok.io/webhooks/vambe

# 3. Test by triggering events in Vambe
# 4. Watch logs in ngrok dashboard
```

### Common Issues

**Issue**: Webhook not receiving events

* ✅ Verify webhook is active (`GET /api/webhooks`)
* ✅ Check URL is publicly accessible
* ✅ Verify subscribed to correct topics
* ✅ Check firewall/security groups

**Issue**: Receiving duplicate events

* ✅ Implement idempotency with event IDs
* ✅ Track processed events in database
* ✅ Return 200 OK even if duplicate

**Issue**: Webhook timing out

* ✅ Process events asynchronously
* ✅ Return 200 OK immediately
* ✅ Don't wait for external API calls

## Advanced Patterns

### Idempotency Pattern

```javascript theme={null}
const processedEvents = new Set();

app.post('/webhooks/vambe', async (req, res) => {
  const event = req.body;
  const eventId = `${event.type}-${event.aiContactId}-${event.data.messageId}`;

  // Check if already processed
  if (processedEvents.has(eventId)) {
    console.log('Duplicate event, skipping');
    return res.status(200).send('OK');
  }

  // Mark as processed
  processedEvents.add(eventId);

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

  // Process event
  await processVambeEvent(event);
});
```

### Queue-Based Processing

```javascript theme={null}
// Use a queue for reliable processing
const Queue = require('bull');
const webhookQueue = new Queue('vambe-webhooks');

app.post('/webhooks/vambe', async (req, res) => {
  // Add to queue
  await webhookQueue.add(req.body);

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

// Process from queue
webhookQueue.process(async (job) => {
  const event = job.data;
  await processVambeEvent(event);
});
```

### Event Filtering

```javascript theme={null}
// Only process certain events or conditions
app.post('/webhooks/vambe', async (req, res) => {
  const event = req.body;

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

  // Filter: Only process messages with "order" keyword
  if (
    event.type === 'message.received' &&
    event.data.message.toLowerCase().includes('order')
  ) {
    await processOrderInquiry(event.data);
  }

  // Filter: Only process tickets in specific pipeline
  if (
    event.type === 'ticket.created' &&
    event.data.pipeline_id === 'sales-pipeline-id'
  ) {
    await processSalesTicket(event.data);
  }
});
```

## Related Resources

* [Webhook API Reference](#) - Full webhook management endpoints
* [Analytics](/docs/analytics) - Monitor webhook event patterns
* [Integration Guide](/docs/integration-to-the-api) - API integration overview
* [Contact Management](/reference/contact/get-info-of-an-ai-contact) - Webhook data usage
