# Webhooks

> Parte da documentação da API da Aruba. Documentação completa: https://docs.arubabr.com/llms-full.txt · Página no site: https://docs.arubabr.com/#webhooks

A Aruba avisa o seu servidor com um `POST` em JSON quando uma transação sua é atualizada. Use o aviso como gatilho e confirme o status na consulta antes de liberar o pedido.

## Cadastrar a URL

1. No painel da Aruba, abra **Integração → Webhooks** e clique em **Novo Webhook**.
2. Informe um nome e a **URL de destino**. Use HTTPS.
3. Escolha os **eventos** que esse webhook deve receber (veja a tabela abaixo). É preciso marcar ao menos um.
4. Se o seu endpoint exige autenticação, escolha o **tipo de autenticação**: *Basic Auth* (usuário e senha) ou *Bearer Token*. Sem autenticação, deixe em *Nenhuma*.
5. Clique em **Cadastrar**. O webhook já nasce ativo.

- Você pode cadastrar mais de uma URL (o painel aceita até 10). Cada URL ativa recebe os eventos que escolheu.
- Em **Editar**, dá para trocar URL, eventos e autenticação. Com **Pausar**, o webhook deixa de receber disparos até ser ativado de novo.
- Os avisos cobrem todas as transações da sua conta, não só as criadas pela API. Ignore os `transaction_id` que a sua integração não conhece.

## Eventos

Cada aviso leva o nome do evento no campo `event`. O webhook só recebe os eventos que foram marcados nele.

| Evento | Status | Descrição |
| --- | --- | --- |
| `transaction.pending` | `PENDING` | Venda criada, aguardando pagamento. |
| `transaction.processing` | `PROCESSING` | Venda em processamento. |
| `transaction.paid` | `AUTHORIZED` | Pagamento aprovado. |
| `transaction.refunded` | `REFUNDED` | Venda reembolsada. |
| `transaction.canceled` | `CANCELED` | Venda cancelada. |
| `transaction.rejected` | `REJECTED` | Pagamento recusado. |
| `transaction.chargeback` | `CHARGEBACK` | Chargeback recebido. |
| `transaction.dispute` | `IN_DISPUTE` | Venda em disputa. |

> **Atenção — Marque ao menos transaction.paid**
>
> Para liberar pedidos, o webhook precisa receber `transaction.paid`. Para tratar devoluções, inclua também `transaction.refunded`, `transaction.chargeback` e `transaction.dispute`.

## Quando a Aruba envia

- Quando o processador informa uma atualização da transação: pagamento aprovado, recusa, cancelamento, estorno, chargeback, disputa e atualizações de pendência.
- Quando a equipe da Aruba aprova uma transação manualmente.
- Não há aviso na criação da cobrança. Uma falha ao gerar a cobrança (`FAILED`) também não gera aviso: nesse caso a própria criação já respondeu erro.
- O mesmo status pode chegar mais de uma vez, por exemplo duas atualizações seguidas com `PENDING`.

## Requisição enviada

`POST` para a sua URL, com `Content-Type: application/json` e este corpo:

**PIX · transaction.paid**

```json
{
  "event": "transaction.paid",
  "transaction_id": "7KQ2M9XA4T1B",
  "total_value": 99.9,
  "fee": 4.49,
  "net_value": 95.41,
  "status": "AUTHORIZED",
  "product": null,
  "payment_method": "PIX",
  "producer": null,
  "payment_data": {
    "pix_key": "00020126580014br.gov.bcb.pix0136…6304A1B2",
    "expiration_date": null,
    "payment_id": "c1a7e0b2-5d3f-4e8a-9b61-0f2d7c4e9a13",
    "status": "PENDING",
    "status_detail": "WAITING_PAYMENT",
    "total_transaction_value": 99.9
  },
  "customer": {
    "name": "João da Silva",
    "email": "joao@exemplo.com",
    "document": "12345678909",
    "phone": "+5511999999999",
    "birth_date": "15/01/1990",
    "address": {}
  }
}
```

| Campo | Descrição |
| --- | --- |
| `event` | Nome do evento que gerou o aviso (tabela acima). |
| `transaction_id` | A transação atualizada. Localize o seu pedido por ele. |
| `status` | Status da transação no momento do envio. |
| `total_value`, `fee`, `net_value` | Valor, taxa e líquido em reais. |
| `payment_method` | Em maiúsculas: `PIX`. |
| `product`, `producer` | `null` nas cobranças da API; preenchidos nas vendas de produto do checkout da Aruba. |
| `payment_data` | O mesmo objeto da [consulta de status](https://docs.arubabr.com/llms/consultar-status.md). |
| `customer` | Comprador: `name`, `email`, `document`, `phone`, `birth_date` e `address` (com as chaves `cep`, `street`, `number`, `complement`, `neighborhood`, `city` e `state`; vazio se não foi enviado). |

## Entrega e retentativas

- Responda com HTTP `2xx`. Qualquer outro status, ou erro de rede, conta como falha.
- São até **3 tentativas** no total, com **15 segundos** entre elas. Depois da terceira falha, aquele aviso não é reenviado: recupere o estado pela consulta de status.
- Responda rápido: grave o aviso, devolva `200` e processe em segundo plano.
- A ordem de chegada não é garantida. Por isso, confirme o status atual na consulta antes de agir.

## Segurança

A Aruba não assina o corpo do aviso. Proteja o seu endpoint com a autenticação do cadastro (Basic Auth ou Bearer Token), que é enviada no header `Authorization` a cada disparo, e confirme sempre o status na consulta.

> **Atenção — Confirme sempre na consulta**
>
> Trate o corpo do webhook como um aviso, não como prova de pagamento. Antes de liberar o pedido, confirme com `GET /api/v1/payments/{transaction_id}/status`. Use HTTPS e confira o header `Authorization` ao receber.

## Idempotência

- O mesmo aviso pode chegar mais de uma vez (retentativas e atualizações repetidas). Liberar o pedido duas vezes precisa ser impossível.
- Uma transação aprovada pode mudar depois para `REFUNDED`, `CHARGEBACK` ou `IN_DISPUTE`. Não descarte um aviso só porque já viu aquele `transaction_id`: deduplique por `transaction_id` + `status`.
- Torne a liberação atômica: marque o pedido como pago numa atualização condicional (por exemplo, `UPDATE … WHERE status <> 'pago'`) e entregue só se a atualização mudou uma linha.

## Exemplo de receptor

No cadastro do webhook, escolha *Bearer Token* e informe o valor de `ARUBA_WEBHOOK_TOKEN`.

**Node.js**

```javascript
import express from "express";

const app = express();
app.use(express.json());

const API = "https://api.arubabr.com/api/v1";

app.post("/webhooks/aruba", async (req, res) => {
  if (req.get("authorization") !== `Bearer ${process.env.ARUBA_WEBHOOK_TOKEN}`) return res.sendStatus(401);
  res.sendStatus(200); // responda logo; processe em seguida

  const transactionId = req.body?.transaction_id;
  if (!transactionId) return;

  const pedido = await db.pedidos.porTransacao(transactionId);
  if (!pedido) return; // transação que não é desta integração

  // Confirma o status na fonte antes de agir
  const r = await fetch(`${API}/payments/${transactionId}/status`);
  if (!r.ok) return;
  const { data } = await r.json();

  if (data.status === "AUTHORIZED") {
    const mudou = await db.pedidos.marcarPagoSeAindaNao(pedido.id); // UPDATE condicional
    if (mudou) await liberarPedido(pedido);
  } else if (["REFUNDED", "CHARGEBACK"].includes(data.status)) {
    await revogarPedido(pedido);
  }
});

app.listen(3000);
```

**PHP**

```php
<?php
// Bearer Token cadastrado no webhook = ARUBA_WEBHOOK_TOKEN
// Em alguns servidores o header precisa ser repassado ao PHP (ex.: Apache com CGI).
$auth = $_SERVER["HTTP_AUTHORIZATION"] ?? "";
if (!hash_equals("Bearer " . getenv("ARUBA_WEBHOOK_TOKEN"), $auth)) {
  http_response_code(401);
  exit;
}

$aviso = json_decode(file_get_contents("php://input"), true);
http_response_code(200);
$transactionId = $aviso["transaction_id"] ?? null;
if (!$transactionId) exit;

// Confirma o status na fonte antes de agir
$ch = curl_init("https://api.arubabr.com/api/v1/payments/" . urlencode($transactionId) . "/status");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$status = json_decode(curl_exec($ch), true)["data"]["status"] ?? null;
curl_close($ch);

if ($status === "AUTHORIZED") {
  // UPDATE pedidos SET status = 'pago' WHERE transaction_id = ? AND status <> 'pago'
  // Se mudou 1 linha, libere o pedido.
}
```
