# Webhooks

Webhooks permitem que sua aplicacao receba notificacoes em tempo real sobre eventos que ocorrem na plataforma Monetarie NPC. Ao inves de consultar a API periodicamente, voce recebe dados automaticamente via HTTP POST quando um evento acontece.

## Como Funciona

```
Monetarie NPC                           Sua Aplicacao
       |                                   |
       |  1. Evento ocorre (ex: boleto pago)
       |                                   |
       |  2. HTTP POST com payload JSON    |
       |---------------------------------->|
       |                                   |
       |  3. Processa evento               |
       |                                   |
       |  4. Responde 200 OK               |
       |<----------------------------------|
       |                                   |
```

## Configuracao

Configure a URL do seu webhook no [Monetarie Portal](https://npcadmin-dev.fluxiq.com.br) ou via API de configuracao:

```bash
curl -X PUT "https://api.monetarie_npc.com.br/api/v1/central/config/central" \
  -H "X-API-Key: pk_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_url": "https://sua-aplicacao.com.br/webhooks/monetarie_npc",
    "enabled": true
  }'
```

## Requisitos da URL

Sua URL de webhook deve atender aos seguintes requisitos:

| Requisito | Descricao |
|-----------|-----------|
| **Protocolo** | HTTPS obrigatorio (HTTP nao aceito) |
| **Timeout** | Responder em ate 30 segundos |
| **Status Code** | Retornar codigo 2xx para sucesso |
| **Disponibilidade** | URL deve estar acessivel publicamente |
| **Certificado** | SSL/TLS valido (certificados autoassinados nao aceitos) |

::: warning HTTPS Obrigatorio
Por questoes de seguranca, apenas URLs HTTPS sao aceitas. Requisicoes para HTTP serao rejeitadas.
:::

## Seguranca

### Assinatura HMAC-SHA256

Todas as requisicoes de webhook incluem uma assinatura HMAC-SHA256 para garantir autenticidade e integridade. Voce deve validar esta assinatura antes de processar qualquer evento.

### Headers de Seguranca

| Header | Descricao |
|--------|-----------|
| `X-Webhook-Signature` | Assinatura HMAC-SHA256 do payload |
| `X-Webhook-Timestamp` | Timestamp Unix da requisicao |
| `X-Request-Id` | Identificador unico da requisicao |

### Formato da Assinatura

A assinatura e calculada usando:

```
signature = HMAC-SHA256(webhook_secret, timestamp + "." + payload)
```

Onde:
- `webhook_secret` e sua chave secreta configurada
- `timestamp` e o valor do header `X-Webhook-Timestamp`
- `payload` e o corpo JSON da requisicao (raw body)

### Validacao da Assinatura

::: code-group

```javascript [Node.js]
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, timestamp, secret) {
  // 1. Verificar timestamp (janela de 5 minutos)
  const currentTime = Math.floor(Date.now() / 1000);
  const webhookTime = parseInt(timestamp, 10);

  if (Math.abs(currentTime - webhookTime) > 300) {
    throw new Error('Timestamp fora da janela de tolerancia');
  }

  // 2. Calcular assinatura esperada
  const signedPayload = `${timestamp}.${payload}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  // 3. Comparar assinaturas (timing-safe)
  const signatureBuffer = Buffer.from(signature, 'hex');
  const expectedBuffer = Buffer.from(expectedSignature, 'hex');

  if (!crypto.timingSafeEqual(signatureBuffer, expectedBuffer)) {
    throw new Error('Assinatura invalida');
  }

  return true;
}

// Uso com Express.js
app.post('/webhooks/monetarie_npc', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const timestamp = req.headers['x-webhook-timestamp'];
  const payload = req.body.toString();

  try {
    verifyWebhookSignature(payload, signature, timestamp, process.env.WEBHOOK_SECRET);
    const event = JSON.parse(payload);
    // Processar evento...
    res.status(200).send('OK');
  } catch (error) {
    console.error('Webhook verification failed:', error.message);
    res.status(401).send('Unauthorized');
  }
});
```

```python [Python]
import hmac
import hashlib
import time
from flask import Flask, request, abort

app = Flask(__name__)

def verify_webhook_signature(payload: bytes, signature: str, timestamp: str, secret: str) -> bool:
    # 1. Verificar timestamp (janela de 5 minutos)
    current_time = int(time.time())
    webhook_time = int(timestamp)

    if abs(current_time - webhook_time) > 300:
        raise ValueError('Timestamp fora da janela de tolerancia')

    # 2. Calcular assinatura esperada
    signed_payload = f"{timestamp}.{payload.decode('utf-8')}"
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        signed_payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    # 3. Comparar assinaturas (timing-safe)
    if not hmac.compare_digest(signature, expected_signature):
        raise ValueError('Assinatura invalida')

    return True

@app.route('/webhooks/monetarie_npc', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Webhook-Signature')
    timestamp = request.headers.get('X-Webhook-Timestamp')
    payload = request.get_data()

    try:
        verify_webhook_signature(
            payload,
            signature,
            timestamp,
            os.environ['WEBHOOK_SECRET']
        )
        event = request.get_json()
        # Processar evento...
        return 'OK', 200
    except ValueError as e:
        print(f'Webhook verification failed: {e}')
        abort(401)
```

```php [PHP]
<?php

function verifyWebhookSignature(
    string $payload,
    string $signature,
    string $timestamp,
    string $secret
): bool {
    // 1. Verificar timestamp (janela de 5 minutos)
    $currentTime = time();
    $webhookTime = (int) $timestamp;

    if (abs($currentTime - $webhookTime) > 300) {
        throw new Exception('Timestamp fora da janela de tolerancia');
    }

    // 2. Calcular assinatura esperada
    $signedPayload = $timestamp . '.' . $payload;
    $expectedSignature = hash_hmac('sha256', $signedPayload, $secret);

    // 3. Comparar assinaturas (timing-safe)
    if (!hash_equals($expectedSignature, $signature)) {
        throw new Exception('Assinatura invalida');
    }

    return true;
}

// Uso
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$secret = getenv('WEBHOOK_SECRET');

try {
    verifyWebhookSignature($payload, $signature, $timestamp, $secret);
    $event = json_decode($payload, true);
    // Processar evento...
    http_response_code(200);
    echo 'OK';
} catch (Exception $e) {
    error_log('Webhook verification failed: ' . $e->getMessage());
    http_response_code(401);
    echo 'Unauthorized';
}
```

:::

### Validacao de Timestamp

A validacao do timestamp previne ataques de replay. Rejeite requisicoes onde o timestamp difere mais de 5 minutos do horario atual:

```
|current_time - webhook_timestamp| <= 300 segundos (5 minutos)
```

::: tip Sincronizacao de Relogio
Certifique-se de que o relogio do seu servidor esta sincronizado via NTP para evitar rejeitar webhooks legitimos.
:::

## Politica de Retry

Se a entrega do webhook falhar (timeout, erro de conexao ou status code diferente de 2xx), tentamos novamente com backoff exponencial:

| Tentativa | Intervalo | Tempo Total |
|-----------|-----------|-------------|
| 1 | Imediato | 0 |
| 2 | 1 minuto | 1 min |
| 3 | 5 minutos | 6 min |
| 4 | 15 minutos | 21 min |
| 5 | 1 hora | 1h 21min |
| 6 | 4 horas | 5h 21min |

Apos 6 tentativas sem sucesso, o webhook e marcado como falho e nao sera mais retentado. Voce pode consultar webhooks falhos no portal e disparar manualmente se necessario.

::: warning Entrega Garantida
Webhooks sao enviados com garantia de "at-least-once delivery". Isso significa que em casos raros, voce pode receber o mesmo evento mais de uma vez. Use o `X-Request-Id` para implementar idempotencia.
:::

## Idempotencia

Para garantir que sua aplicacao processe cada evento apenas uma vez, use o header `X-Request-Id`:

1. **Armazene o ID** de cada webhook processado com sucesso
2. **Verifique duplicatas** antes de processar novos webhooks
3. **Retorne 200** para duplicatas (indicando que ja foi processado)

```javascript
// Exemplo de verificacao de idempotencia
const processedWebhooks = new Set(); // Use Redis em producao

app.post('/webhooks/monetarie_npc', async (req, res) => {
  const requestId = req.headers['x-request-id'];

  // Verificar se ja processamos este webhook
  if (processedWebhooks.has(requestId)) {
    console.log(`Webhook ${requestId} ja processado, ignorando`);
    return res.status(200).send('OK');
  }

  // Processar webhook...

  // Marcar como processado
  processedWebhooks.add(requestId);

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

::: tip Redis para Idempotencia
Em producao, use Redis ou um banco de dados para armazenar IDs de webhooks processados. Configure uma expiracao (TTL) de pelo menos 24 horas.
:::

## Testando Webhooks

### Ambiente Sandbox

No ambiente sandbox, voce pode usar ferramentas como [webhook.site](https://webhook.site) para inspecionar os payloads:

1. Acesse [webhook.site](https://webhook.site) e copie sua URL unica
2. Configure esta URL no Monetarie Portal (sandbox)
3. Execute acoes no sandbox (criar boleto, etc.)
4. Visualize os webhooks recebidos em tempo real

### Endpoint de Teste

Use o endpoint de teste para disparar webhooks de exemplo:

```bash
curl -X POST "https://sandbox.monetarie_npc.com.br/api/v1/central/webhooks/test" \
  -H "X-API-Key: pk_test_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "event_type": "boleto_paid"
  }'
```

## Proximos Passos

- [Eventos](/webhooks/events) - Tipos de eventos e payloads
- [Implementacao](/webhooks/handling) - Guia completo de implementacao
- [Fluxo de Boleto](/guides/boleto-flow) - Entenda o ciclo de vida do boleto
