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

# Webhooks

> Configure webhooks para receber notificações automáticas sobre atualizações de status de pedidos e transações

## Visão Geral

Webhooks permitem que sua aplicação receba notificações automáticas quando eventos importantes ocorrem na API Uvvipague, como mudanças de status de pagamento, liquidação de débitos e conclusão de consultas.

<Info>
  **Comunicação Assíncrona**: Os webhooks são enviados de forma assíncrona. Configure sua URL para receber as notificações automaticamente.
</Info>

<Note>
  **Correlacionando Requisições e Webhooks**: Todos os webhooks incluem o campo `externalId` que você enviou na requisição original. Use este campo para identificar qual requisição gerou cada webhook. Se você não enviar um `externalId`, a API gerará um automaticamente, mas é **altamente recomendado** que você envie seus próprios IDs únicos.
</Note>

## Cadastro de Webhook

### Endpoint

```http theme={null}
POST /uvvi/v1/webhook-register
```

### Parâmetros

<ParamField body="url" type="string" required>
  URL do endpoint que receberá as notificações webhook. Deve ser uma URL válida e acessível publicamente.

  Exemplo: `https://sua-api.com.br/webhook/uvvipague`
</ParamField>

### Headers Obrigatórios

<ParamField header="x-api-key" type="string" required>
  Sua API Key para autenticação
</ParamField>

### Exemplo de Requisição

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST \
    --url 'https://api.uvvipague.com.br/uvvi/v1/webhook-register' \
    --header 'x-api-key: SUA_API_KEY_AQUI' \
    --header 'Content-Type: application/json' \
    --data '{
      "url": "https://sua-api.com.br/webhook/uvvipague"
    }'
  ```

  ```javascript JavaScript theme={null}
  const requestBody = {
    url: "https://sua-api.com.br/webhook/uvvipague"
  };

  const options = {
    method: 'POST',
    headers: {
      'x-api-key': 'SUA_API_KEY_AQUI',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(requestBody)
  };

  fetch('https://api.uvvipague.com.br/uvvi/v1/webhook-register', options)
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error('Erro:', error));
  ```

  ```python Python theme={null}
  import requests

  url = "https://api.uvvipague.com.br/uvvi/v1/webhook-register"
  headers = {
      "x-api-key": "SUA_API_KEY_AQUI",
      "Content-Type": "application/json"
  }
  payload = {
      "url": "https://sua-api.com.br/webhook/uvvipague"
  }

  response = requests.post(url, headers=headers, json=payload)
  data = response.json()
  print(data)
  ```

  ```php PHP theme={null}
  <?php
  $curl = curl_init();

  $payload = json_encode([
      "url" => "https://sua-api.com.br/webhook/uvvipague"
  ]);

  curl_setopt_array($curl, [
      CURLOPT_URL => "https://api.uvvipague.com.br/uvvi/v1/webhook-register",
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_POST => true,
      CURLOPT_POSTFIELDS => $payload,
      CURLOPT_HTTPHEADER => [
          "x-api-key: SUA_API_KEY_AQUI",
          "Content-Type: application/json"
      ],
  ]);

  $response = curl_exec($curl);
  curl_close($curl);

  $data = json_decode($response, true);
  print_r($data);
  ?>
  ```
</CodeGroup>

### Resposta de Sucesso

```json theme={null}
{
  "request_id": "req_abc123def456",
  "status": "success",
  "message": "Webhook cadastrado com sucesso"
}
```

## Política de Retentativas

A Uvvipague implementa uma política robusta de retentativas para garantir que você receba as notificações:

<Steps>
  <Step title="Tentativa Inicial">
    Enviamos a primeira requisição imediatamente após o evento ocorrer.
  </Step>

  <Step title="1ª Retentativa - 15 minutos">
    Se não recebermos resposta 200, tentamos novamente após **15 minutos**.
  </Step>

  <Step title="2ª Retentativa - 60 minutos">
    Segunda tentativa após **60 minutos** da tentativa inicial.
  </Step>

  <Step title="3ª Retentativa - 90 minutos">
    Terceira e última tentativa após **90 minutos** da tentativa inicial.
  </Step>

  <Step title="Após 3 Retentativas">
    Após as 3 retentativas sem sucesso, **nenhuma nova tentativa será realizada**.

    O cliente precisará consultar o status manualmente usando o `transactionId` ou o `externalId` enviado na requisição original.
  </Step>
</Steps>

<Warning>
  **Importante**: Após 3 tentativas falhadas, você deve consultar o status do pedido manualmente através da API de consulta de pagamento.
</Warning>

### Timeline de Retentativas

```
Evento ocorre (t=0)
    ↓
Tentativa 1 (imediato)
    ↓ (falha)
Tentativa 2 (t=15min)
    ↓ (falha)
Tentativa 3 (t=60min)
    ↓ (falha)
Tentativa 4 (t=90min)
    ↓ (falha)
Fim das tentativas
```

## Recebendo Webhooks

### Estrutura do Payload

Quando um evento ocorre, enviamos uma requisição POST para sua URL cadastrada:

<Tabs>
  <Tab title="Débitos Encontrados">
    ```json theme={null}
    {
      "type": "debts",
      "transactionId": 817210768,
      "externalId": "c676c954-aa6d-4cb5-a812-c1907a53a442",
      "vehicle": {
        "uf": "DF",
        "document": "39268450828",
        "licensePlate": "DIS9865",
        "renavam": "01203988813"
      },
      "debts": [
        {
          "id": "9547B3A8-05B9-4D1C-8C72-A500BB6D93EF",
          "amount": 85.13,
          "title": "Infração Vencida - I004242123",
          "description": "I004242123 - Infração de Trânsito",
          "type": "ticket",
          "dueDate": "2024-01-15T00:00:00Z",
          "isExpired": true
        }
      ]
    }
    ```
  </Tab>

  <Tab title="Veículo Sem Débitos">
    ```json theme={null}
    {
      "type": "VehicleWithoutDebts",
      "transactionId": 817622466,
      "externalId": "0a384059-eaf0-4240-9dc7-f4125671b4c9",
      "vehicle": {
        "uf": "DF",
        "document": "3138777503",
        "licensePlate": "RCK0E56",
        "renavam": "33864569420"
      }
    }
    ```
  </Tab>

  <Tab title="Veículo Não Encontrado">
    ```json theme={null}
    {
      "type": "VehicleNotFound",
      "transactionId": 817211803,
      "externalId": "0a384059-eaf0-4240-9dc7-f4125671b4c9"
    }
    ```
  </Tab>

  <Tab title="Erro na Consulta">
    ```json theme={null}
    {
      "type": "search-error-event",
      "status": "ERROR",
      "transactionId": 817211803,
      "externalId": "bbf6c1c8-6c60-4806-9454-21a71e1ce1df",
      "error": [
        {
          "errorCode": "900",
          "message": "Serviço indisponível."
        }
      ]
    }
    ```
  </Tab>

  <Tab title="Pagamento Aprovado">
    ```json theme={null}
    {
      "type": "payment-status-update",
      "paymentId": "pay_abc123def456",
      "status": "paid",
      "transactionId": 817210768,
      "amount": 1302.86,
      "paidAt": "2024-12-15T14:30:00Z",
      "liquidationStatus": "settled"
    }
    ```
  </Tab>
</Tabs>

### Resposta Esperada

Seu endpoint **deve retornar HTTP 200** para confirmar o recebimento:

```http theme={null}
HTTP/1.1 200 OK
Content-Type: application/json

{
  "received": true
}
```

<Note>
  **Importante**: Retorne 200 o mais rápido possível. Processe o webhook de forma assíncrona em sua aplicação para não bloquear a resposta.
</Note>

## Implementação do Endpoint

### Exemplo Completo

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

  app.use(express.json());

  // Endpoint para receber webhooks
  app.post('/webhook/uvvipague', async (req, res) => {
    try {
      // 1. Retorne 200 imediatamente
      res.status(200).json({ received: true });
      
      // 2. Processe o webhook de forma assíncrona
      processWebhookAsync(req.body);
      
    } catch (error) {
      console.error('Erro ao receber webhook:', error);
      res.status(500).json({ error: 'Internal server error' });
    }
  });

  async function processWebhookAsync(payload) {
    const { type, transactionId, externalId } = payload;
    
    console.log(`Webhook recebido: ${type}`);
    console.log(`Transaction ID: ${transactionId}`);
    console.log(`External ID: ${externalId}`);
    
    // Processar baseado no tipo
    switch (type) {
      case 'debts':
        await handleDebtsFound(payload);
        break;
        
      case 'VehicleWithoutDebts':
        await handleNoDebts(payload);
        break;
        
      case 'VehicleNotFound':
        await handleVehicleNotFound(payload);
        break;
        
      case 'search-error-event':
        await handleSearchError(payload);
        break;
        
      case 'payment-status-update':
        await handlePaymentUpdate(payload);
        break;
        
      default:
        console.warn(`Tipo de webhook desconhecido: ${type}`);
    }
  }

  async function handleDebtsFound(payload) {
    const { transactionId, debts } = payload;
    
    // Salvar débitos no banco de dados
    await database.debts.create({
      transactionId,
      debts: debts,
      status: 'found',
      receivedAt: new Date()
    });
    
    // Notificar cliente
    await notifyClient(transactionId, 'Débitos encontrados');
  }

  async function handlePaymentUpdate(payload) {
    const { paymentId, status, liquidationStatus } = payload;
    
    // Atualizar status do pagamento
    await database.payments.update(
      { paymentId },
      { 
        status, 
        liquidationStatus,
        updatedAt: new Date() 
      }
    );
    
    // Se pago e liquidado, liberar serviço
    if (status === 'paid' && liquidationStatus === 'settled') {
      await releaseService(paymentId);
    }
  }

  app.listen(3000, () => {
    console.log('Servidor rodando na porta 3000');
  });
  ```

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

  app = Flask(__name__)
  logging.basicConfig(level=logging.INFO)

  @app.route('/webhook/uvvipague', methods=['POST'])
  def webhook_uvvipague():
      try:
          payload = request.json
          
          # 1. Retorne 200 imediatamente
          response = jsonify({'received': True})
          
          # 2. Processe o webhook em background
          thread = threading.Thread(
              target=process_webhook_async,
              args=(payload,)
          )
          thread.start()
          
          return response, 200
          
      except Exception as e:
          logging.error(f'Erro ao receber webhook: {e}')
          return jsonify({'error': 'Internal server error'}), 500

  def process_webhook_async(payload):
      webhook_type = payload.get('type')
      transaction_id = payload.get('transactionId')
      external_id = payload.get('externalId')
      
      logging.info(f'Webhook recebido: {webhook_type}')
      logging.info(f'Transaction ID: {transaction_id}')
      logging.info(f'External ID: {external_id}')
      
      # Processar baseado no tipo
      if webhook_type == 'debts':
          handle_debts_found(payload)
      elif webhook_type == 'VehicleWithoutDebts':
          handle_no_debts(payload)
      elif webhook_type == 'VehicleNotFound':
          handle_vehicle_not_found(payload)
      elif webhook_type == 'search-error-event':
          handle_search_error(payload)
      elif webhook_type == 'payment-status-update':
          handle_payment_update(payload)
      else:
          logging.warning(f'Tipo de webhook desconhecido: {webhook_type}')

  def handle_debts_found(payload):
      transaction_id = payload['transactionId']
      debts = payload['debts']
      
      # Salvar débitos no banco de dados
      # database.save_debts(transaction_id, debts)
      
      # Notificar cliente
      # notify_client(transaction_id, 'Débitos encontrados')
      
      logging.info(f'Débitos processados para transação {transaction_id}')

  def handle_payment_update(payload):
      payment_id = payload['paymentId']
      status = payload['status']
      liquidation_status = payload.get('liquidationStatus')
      
      # Atualizar status do pagamento
      # database.update_payment(payment_id, status, liquidation_status)
      
      # Se pago e liquidado, liberar serviço
      if status == 'paid' and liquidation_status == 'settled':
          # release_service(payment_id)
          logging.info(f'Serviço liberado para pagamento {payment_id}')

  if __name__ == '__main__':
      app.run(port=3000)
  ```
</CodeGroup>

## Segurança

### Validações Recomendadas

<AccordionGroup>
  <Accordion title="Validar Origem" icon="shield-check">
    Valide que a requisição vem dos servidores da Uvvipague:

    * Verifique o IP de origem (lista fornecida pelo suporte)
    * Implemente whitelist de IPs
  </Accordion>

  <Accordion title="Validar Estrutura" icon="code">
    Valide a estrutura do payload antes de processar:

    ```javascript theme={null}
    function isValidWebhook(payload) {
      return payload.type && 
             payload.transactionId && 
             payload.externalId;
    }
    ```
  </Accordion>

  <Accordion title="Idempotência" icon="repeat">
    Implemente idempotência usando `transactionId` ou `externalId`:

    ```javascript theme={null}
    async function processWebhook(payload) {
      const { transactionId, externalId } = payload;
      
      // Verificar se já foi processado usando externalId
      const exists = await database.webhooks.findOne({ externalId });
      if (exists) {
        console.log('Webhook já processado');
        return;
      }
      
      // Processar e marcar como processado
      await processPayload(payload);
      await database.webhooks.create({ 
        transactionId,
        externalId,
        processedAt: new Date() 
      });
    }
    ```
  </Accordion>

  <Accordion title="Timeout Adequado" icon="clock">
    Configure timeout adequado no seu servidor:

    * Responda em menos de 5 segundos
    * Processe de forma assíncrona
    * Use filas (Redis, RabbitMQ) para processamento
  </Accordion>
</AccordionGroup>

## Consulta Manual de Status

Se todas as tentativas de webhook falharem, consulte o status manualmente:

### Endpoint de Consulta

```http theme={null}
POST /uvvi/v1/payment/status
```

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST \
    --url 'https://api.uvvipague.com.br/uvvi/v1/payment/status' \
    --header 'x-api-key: SUA_API_KEY_AQUI' \
    --header 'Content-Type: application/json' \
    --data '{
      "paymentId": "pay_abc123def456"
    }'
  ```

  ```javascript JavaScript theme={null}
  // Usando transactionId
  const response = await fetch('https://api.uvvipague.com.br/uvvi/v1/payment/status', {
    method: 'POST',
    headers: {
      'x-api-key': 'SUA_API_KEY_AQUI',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      transactionId: 817210768
    })
  });

  // Ou usando externalId
  const response = await fetch('https://api.uvvipague.com.br/uvvi/v1/payment/status', {
    method: 'POST',
    headers: {
      'x-api-key': 'SUA_API_KEY_AQUI',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      externalId: "seu-id-interno-123"
    })
  });
  ```
</CodeGroup>

## Testando Webhooks

### Ferramentas Úteis

<CardGroup cols={2}>
  <Card title="Webhook.site" icon="globe">
    Use [webhook.site](https://webhook.site) para testar e visualizar webhooks durante o desenvolvimento.
  </Card>

  <Card title="ngrok" icon="tunnel">
    Use [ngrok](https://ngrok.com) para expor seu localhost e receber webhooks em desenvolvimento.
  </Card>

  <Card title="RequestBin" icon="inbox">
    Use [RequestBin](https://requestbin.com) para inspecionar payloads de webhook.
  </Card>

  <Card title="Postman" icon="paper-plane">
    Simule webhooks usando Postman para testar seu endpoint.
  </Card>
</CardGroup>

### Exemplo com ngrok

```bash theme={null}
# 1. Instale o ngrok
npm install -g ngrok

# 2. Inicie seu servidor local
node server.js  # rodando na porta 3000

# 3. Exponha com ngrok
ngrok http 3000

# 4. Use a URL gerada para cadastrar o webhook
# Exemplo: https://abc123.ngrok.io/webhook/uvvipague
```

## Boas Práticas

<Steps>
  <Step title="Responda Rapidamente">
    Retorne HTTP 200 em menos de 5 segundos. Processe o webhook de forma assíncrona.
  </Step>

  <Step title="Implemente Idempotência">
    Use `transactionId` ou `externalId` para evitar processar o mesmo webhook múltiplas vezes.
  </Step>

  <Step title="Log Tudo">
    Registre todos os webhooks recebidos para auditoria e debugging.
  </Step>

  <Step title="Use Filas">
    Implemente filas (Redis, RabbitMQ, SQS) para processar webhooks de forma resiliente.
  </Step>

  <Step title="Monitore Falhas">
    Configure alertas para webhooks que falharem após todas as retentativas.
  </Step>

  <Step title="Tenha Fallback">
    Implemente consulta periódica de status como fallback caso os webhooks falhem.
  </Step>
</Steps>

## Próximos Passos

<CardGroup cols={2}>
  <Card title="Fluxo Completo" icon="diagram-project" href="/fluxo-completo">
    Veja como integrar webhooks no fluxo completo
  </Card>

  <Card title="Consulta de Débitos" icon="file-invoice" href="/consulta-debitos">
    Entenda os tipos de resposta via webhook
  </Card>

  <Card title="ENUMs e Tipos" icon="list" href="/enums-tipos">
    Consulte os tipos de eventos e status
  </Card>

  <Card title="Autenticação" icon="key" href="/autenticacao">
    Configure sua API Key
  </Card>
</CardGroup>
