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

# Webhooks

# Webhooks

> Receba notificações automáticas sobre eventos na sua conta

Os **Webhooks** permitem que você receba notificações automáticas quando eventos importantes acontecem no ConnectVets Notes, como quando uma transcrição é concluída ou falha.

## Como Funcionam

<Steps>
  <Step title="Configuração">
    Nossa equipe configura os webhooks no seu sistema conforme suas necessidades
  </Step>

  <Step title="Eventos">
    Quando um evento ocorre (ex: transcrição concluída), enviamos uma requisição POST
  </Step>

  <Step title="Recebimento">
    Sua aplicação recebe a notificação e pode processar o evento imediatamente
  </Step>

  <Step title="Confirmação">
    Responda com status 200 para confirmar o recebimento
  </Step>
</Steps>

## Eventos Disponíveis

### note.transcription.completed

Disparado quando uma transcrição é finalizada com sucesso:

```json theme={null} theme={null}
{
  "event": "note.transcription.completed",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Rex",
    "transcription_status": "completed",
    "external_id": "CLIENTE_123",
    "created_at": "2024-02-14T18:25:43Z",
    "completed_at": "2024-02-14T18:30:23Z"
  },
  "timestamp": "2024-02-14T18:30:24Z",
  "webhook_id": "wh_abc123def456"
}
```

### note.transcription.failed

Disparado quando uma transcrição falha:

```json theme={null} theme={null}
{
  "event": "note.transcription.failed",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Rex",
    "transcription_status": "failed",
    "error": {
      "code": "AUDIO_QUALITY_LOW",
      "message": "Áudio com qualidade insuficiente para transcrição"
    },
    "external_id": "CLIENTE_123",
    "created_at": "2024-02-14T18:25:43Z",
    "failed_at": "2024-02-14T18:28:15Z"
  },
  "timestamp": "2024-02-14T18:28:16Z",
  "webhook_id": "wh_abc123def456"
}
```

## Implementação

### Endpoint do Webhook

Sua aplicação deve ter um endpoint que aceite requisições POST:

<CodeGroup>
  ```javascript Node.js/Express theme={null} theme={null}
  app.post('/webhooks/connectvets', (req, res) => {
    const { event, data, timestamp } = req.body;
    
    console.log(`Evento recebido: ${event} em ${timestamp}`);
    
    switch (event) {
      case 'note.transcription.completed':
        handleTranscriptionCompleted(data);
        break;
        
      case 'note.transcription.failed':
        handleTranscriptionFailed(data);
        break;
        
      default:
        console.log(`Evento não tratado: ${event}`);
    }
    
    // Resposta de confirmação
    res.status(200).json({ received: true });
  });

  function handleTranscriptionCompleted(noteData) {
    // Lógica para processar transcrição concluída
    console.log(`Nota ${noteData.id} foi transcrita com sucesso`);
    
    // Exemplo: notificar o usuário
    notifyUser(noteData.external_id, 'Sua consulta foi transcrita!');
    
    // Exemplo: atualizar banco local
    updateLocalNote(noteData.id, { status: 'completed' });
  }

  function handleTranscriptionFailed(noteData) {
    // Lógica para processar falha na transcrição
    console.log(`Falha na transcrição da nota ${noteData.id}: ${noteData.error.message}`);
    
    // Exemplo: notificar erro
    notifyUser(noteData.external_id, 'Houve um problema na transcrição. Tente novamente.');
  }
  ```

  ```python Flask theme={null} theme={null}
  from flask import Flask, request, jsonify
  import logging

  app = Flask(__name__)

  @app.route('/webhooks/connectvets', methods=['POST'])
  def handle_webhook():
      data = request.get_json()
      
      event = data.get('event')
      event_data = data.get('data')
      timestamp = data.get('timestamp')
      
      logging.info(f"Evento recebido: {event} em {timestamp}")
      
      if event == 'note.transcription.completed':
          handle_transcription_completed(event_data)
      elif event == 'note.transcription.failed':
          handle_transcription_failed(event_data)
      else:
          logging.warning(f"Evento não tratado: {event}")
      
      return jsonify({"received": True}), 200

  def handle_transcription_completed(note_data):
      """Processa transcrição concluída"""
      note_id = note_data['id']
      logging.info(f"Nota {note_id} foi transcrita com sucesso")
      
      # Exemplo: notificar o usuário
      notify_user(note_data['external_id'], 'Sua consulta foi transcrita!')
      
      # Exemplo: atualizar banco local
      update_local_note(note_id, {'status': 'completed'})

  def handle_transcription_failed(note_data):
      """Processa falha na transcrição"""
      note_id = note_data['id']
      error_msg = note_data['error']['message']
      logging.error(f"Falha na transcrição da nota {note_id}: {error_msg}")
      
      # Exemplo: notificar erro
      notify_user(note_data['external_id'], 'Houve um problema na transcrição.')
  ```

  ```php PHP theme={null} theme={null}
  <?php
  // webhook_handler.php

  header('Content-Type: application/json');

  // Receber dados do webhook
  $input = file_get_contents('php://input');
  $data = json_decode($input, true);

  if (!$data) {
      http_response_code(400);
      echo json_encode(['error' => 'Invalid JSON']);
      exit;
  }

  $event = $data['event'] ?? '';
  $eventData = $data['data'] ?? [];
  $timestamp = $data['timestamp'] ?? '';

  error_log("Evento recebido: $event em $timestamp");

  switch ($event) {
      case 'note.transcription.completed':
          handleTranscriptionCompleted($eventData);
          break;
          
      case 'note.transcription.failed':
          handleTranscriptionFailed($eventData);
          break;
          
      default:
          error_log("Evento não tratado: $event");
  }

  // Resposta de confirmação
  http_response_code(200);
  echo json_encode(['received' => true]);

  function handleTranscriptionCompleted($noteData) {
      $noteId = $noteData['id'];
      error_log("Nota $noteId foi transcrita com sucesso");
      
      // Exemplo: notificar o usuário
      notifyUser($noteData['external_id'], 'Sua consulta foi transcrita!');
      
      // Exemplo: atualizar banco local
      updateLocalNote($noteId, ['status' => 'completed']);
  }

  function handleTranscriptionFailed($noteData) {
      $noteId = $noteData['id'];
      $errorMsg = $noteData['error']['message'];
      error_log("Falha na transcrição da nota $noteId: $errorMsg");
      
      // Exemplo: notificar erro
      notifyUser($noteData['external_id'], 'Houve um problema na transcrição.');
  }
  ?>
  ```
</CodeGroup>

## Segurança

### Validação de Origem

Para garantir que o webhook vem do ConnectVets, valide o header de assinatura:

```javascript theme={null} theme={null}
const crypto = require('crypto');

function validateWebhookSignature(payload, signature, secret) {
  const computedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
    
  const expectedSignature = `sha256=${computedSignature}`;
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

app.post('/webhooks/connectvets', (req, res) => {
  const signature = req.headers['x-connectvets-signature'];
  const payload = JSON.stringify(req.body);
  
  if (!validateWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Processar webhook...
});
```

### HTTPS Obrigatório

* Todos os webhooks **devem** usar HTTPS
* Certificados SSL válidos são obrigatórios
* URLs localhost não são aceitas em produção

## Retry Logic

### Como Funciona

Se seu endpoint não responder com status 2xx, reenviamos automaticamente:

| Tentativa | Delay      |
| --------- | ---------- |
| 1ª        | Imediato   |
| 2ª        | 1 minuto   |
| 3ª        | 5 minutos  |
| 4ª        | 15 minutos |
| 5ª        | 1 hora     |

### Idempotência

Implemente verificação de duplicatas usando o `webhook_id`:

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

app.post('/webhooks/connectvets', (req, res) => {
  const webhookId = req.body.webhook_id;
  
  if (processedWebhooks.has(webhookId)) {
    // Já processado, apenas confirme
    return res.status(200).json({ received: true, duplicate: true });
  }
  
  processedWebhooks.add(webhookId);
  
  // Processar webhook...
});
```

## Monitoramento

### Logs Importantes

Monitore estes aspectos:

* **Latência de resposta**: Tempo para processar webhook
* **Taxa de erro**: Percentual de falhas no processamento
* **Timeouts**: Requisições que demoram muito
* **Duplicatas**: Webhooks reprocessados

### Exemplo de Logging

```javascript theme={null} theme={null}
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'webhooks.log' })
  ]
});

app.post('/webhooks/connectvets', (req, res) => {
  const startTime = Date.now();
  const { event, webhook_id } = req.body;
  
  try {
    // Processar webhook...
    
    const duration = Date.now() - startTime;
    logger.info('webhook_processed', {
      event,
      webhook_id,
      duration_ms: duration,
      status: 'success'
    });
    
    res.status(200).json({ received: true });
  } catch (error) {
    const duration = Date.now() - startTime;
    logger.error('webhook_failed', {
      event,
      webhook_id,
      duration_ms: duration,
      error: error.message,
      status: 'error'
    });
    
    res.status(500).json({ error: 'Processing failed' });
  }
});
```

## Configuração

<Info>
  **Importante**: Webhooks não são gerenciados via API. A configuração é feita através de contato direto com nossa equipe.
</Info>

### Para Configurar Webhooks

1. **Contate nossa equipe** via [WhatsApp (+55 31 8883-5141)](https://wa.me/553185127147)
2. **Forneça informações**:
   * URL do seu endpoint (deve ser HTTPS)
   * Eventos que deseja receber
   * Método de autenticação preferido
   * Ambiente (produção/desenvolvimento)

### Informações Necessárias

* **URL do Endpoint**: `https://seuapp.com/webhooks/connectvets`
* **Eventos**: `["note.transcription.completed", "note.transcription.failed"]`
* **Headers Customizados** (opcional): Para autenticação adicional
* **Secret para Validação**: Para verificar autenticidade

### Teste em Desenvolvimento

Nossa equipe pode configurar webhooks para ambiente de desenvolvimento usando:

* **ngrok** para túneis locais
* **Webhook.site** para testes rápidos
* **RequestBin** para debug

## Troubleshooting

### Webhook Não Recebido

**Possíveis causas:**

* Endpoint inacessível (timeout/erro)
* Certificado SSL inválido
* Firewall bloqueando nossa IP

**Soluções:**

* Verificar se endpoint responde a GET/POST
* Validar certificado SSL
* Liberar IPs do ConnectVets no firewall

### Erro 401/403

**Possíveis causas:**

* Autenticação adicional no endpoint
* Headers de validação incorretos

**Soluções:**

* Revisar lógica de autenticação
* Verificar headers `x-connectvets-signature`

### Webhooks Duplicados

**Possíveis causas:**

* Endpoint demorou para responder (timeout)
* Resposta não 2xx

**Soluções:**

* Implementar verificação de `webhook_id`
* Otimizar tempo de processamento
* Responder rapidamente (mover processamento pesado para background)

## Exemplos de Uso

### Integração com CRM

```javascript theme={null} theme={null}
async function handleTranscriptionCompleted(noteData) {
  // Buscar informações da consulta no sistema local
  const appointment = await getAppointmentByExternalId(noteData.external_id);
  
  if (appointment) {
    // Atualizar status no CRM
    await updateCRMRecord(appointment.crm_id, {
      transcription_status: 'completed',
      transcription_url: `https://notes.connectvets.com.br/notes/${noteData.id}`,
      last_updated: new Date()
    });
    
    // Notificar veterinário
    await sendNotification(appointment.veterinarian_id, {
      type: 'transcription_ready',
      patient: noteData.name,
      note_id: noteData.id
    });
  }
}
```

### Workflow de Aprovação

```javascript theme={null} theme={null}
async function handleTranscriptionCompleted(noteData) {
  // Verificar se nota precisa de aprovação
  const needsApproval = await checkApprovalRules(noteData);
  
  if (needsApproval) {
    // Enviar para fila de aprovação
    await addToApprovalQueue(noteData.id, {
      priority: noteData.urgent ? 'high' : 'normal',
      assigned_to: await getReviewerForNote(noteData)
    });
    
    // Notificar revisor
    await notifyReviewer(noteData);
  } else {
    // Aprovação automática
    await autoApproveNote(noteData.id);
  }
}
```

***

<Info>
  **Próximo passo**: Veja [exemplos práticos de webhooks](/notes/examples/webhook-handling) ou explore os [endpoints de Notes](/notes/api-reference/notes/create).
</Info>
