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

# Webhooks

> Receba notificações automáticas da Upay sempre que algo importante acontecer — como um pagamento confirmado ou um saque concluído.

Pense nos webhooks como **"mensagens enviadas pela Upay para o seu sistema"**, sem que você precise ficar consultando a API o tempo todo.

***

## Por que usar webhooks?

Sem webhooks, sua aplicação teria que **perguntar para a API a cada segundo**:

> "Essa transação já foi confirmada?"

Isso é lento e ineficiente.

Com webhooks, a Upay avisa você **imediatamente**:

> "O pagamento foi confirmado. Aqui estão os dados."

Assim você pode:

* atualizar o status de um pedido
* liberar acesso a um produto
* enviar e-mails automáticos de confirmação
* registrar movimentações financeiras

Tudo isso de forma automática, sem fazer seu cliente esperar.

***

## Como funciona na prática?

1. **Você cria um endpoint no seu sistema**
   Ex.: `https://meusite.com/webhooks/upay`

2. **Você cadastra esse endpoint criando uma assinatura de webhook via API**

3. **Sempre que algo importante acontece**, como um pagamento aprovado:
   * A Upay envia um `POST` para a sua URL
   * O body contém o evento (ex: `transaction.paid`, `withdrawal.completed`)
   * Seu sistema processa esse evento

É como receber uma notificação push — só que para servidores.

***

## Ambientes (Desenvolvimento vs Produção)

<Card title="Ambientes da Upay" horizontal>
  * Webhooks criados com chaves de **Desenvolvimento** recebem eventos simulados
  * Webhooks criados com chaves de **Produção** recebem eventos reais

  Dessa forma, você pode testar tudo antes de ir para produção.
</Card>

***

## Segurança dos webhooks

Webhooks precisam ser seguros — afinal, qualquer pessoa poderia tentar enviar requisições falsas para sua aplicação.

Por isso, recomendamos duas camadas de proteção:

***

### 🔐 1. Secret na URL

Cada assinatura de webhook tem um secret único, que pode ir na query string do seu endpoint.

Ex.: `https://meusite.com/webhook/upay?secret=SEU_SECRET`

Seu backend confere:

```js theme={null}
if (req.query.secret !== process.env.WEBHOOK_SECRET) {
  return res.status(401).json({ error: "Unauthorized" });
}
```

### 🛡️ 2. Assinatura HMAC (verificação do corpo)

Mesmo que alguém descubra sua URL, ainda não consegue falsificar o evento.

**Por quê?**
Porque cada webhook enviado pela Upay inclui uma assinatura no header `X-Webhook-Signature`.

Essa assinatura é gerada usando **HMAC-SHA256** e garante que:

* o corpo da requisição **não foi alterado**
* o evento realmente foi enviado pela Upay

Seu backend deve recalcular a assinatura e comparar:

* Se for igual → ✅ evento é legítimo
* Se for diferente → ❌ evento rejeitado

Esse é o mesmo método usado por **Stripe, PayPal, Shopify, GitHub**, etc.

***

## 🔧 Exemplo de validação HMAC (Node.js)

> Este exemplo mostra como validar a assinatura HMAC enviada no header `X-Webhook-Signature`.

```typescript theme={null}
import crypto from "node:crypto";

/**
 * Verifica se a assinatura do webhook é válida.
 * @param rawBody String com o body bruto da requisição.
 * @param signatureFromHeader Assinatura recebida no header X-Webhook-Signature.
 * @param secret Secret configurado na sua assinatura de webhook.
 * @returns true se a assinatura for válida, false caso contrário.
 */
export function verifyUpaySignature(
  rawBody: string,
  signatureFromHeader: string,
  secret: string
) {
  const bodyBuffer = Buffer.from(rawBody, "utf8");

  const expectedSig = crypto
    .createHmac("sha256", secret)
    .update(bodyBuffer)
    .digest("base64");

  const A = Buffer.from(expectedSig);
  const B = Buffer.from(signatureFromHeader);

  return A.length === B.length && crypto.timingSafeEqual(A, B);
}
```

***

## Criando uma assinatura de webhook

<Steps>
  <Step title="Defina seu endpoint">
    Crie um endpoint HTTPS público que receberá as notificações.

    <Card title="Requisitos do endpoint" horizontal>
      * Deve ser **HTTPS** (HTTP não é aceito)
      * Deve responder **200 OK** dentro de 10 segundos
      * Deve aceitar requisições `POST` com body JSON
    </Card>
  </Step>

  <Step title="Crie a assinatura via API">
    Envie uma requisição `POST /api/v1/webhooks/subscriptions`:

    ```bash theme={null}
    curl -X POST "https://upay-sistema-api.onrender.com/api/v1/webhooks/subscriptions" \
      -H "Authorization: Bearer SUA_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://meusite.com/webhooks/upay",
        "events": ["transaction.paid", "withdrawal.completed"]
      }'
    ```
  </Step>

  <Step title="Valide os eventos recebidos">
    Implemente a validação HMAC no seu endpoint para garantir que os eventos são legítimos.
  </Step>
</Steps>

***

## Eventos suportados

| Evento                         | Quando é disparado                        |
| ------------------------------ | ----------------------------------------- |
| `transaction.paid`             | Pagamento de uma transação foi confirmado |
| `transaction.refunded`         | Reembolso de uma transação foi concluído  |
| `transaction.cancelled`        | Transação foi cancelada                   |
| `transaction.expired`          | Transação expirou sem pagamento           |
| `payment_link.paid`            | Pagamento realizado via link de pagamento |
| `payment_link.expired`         | Link de pagamento expirou                 |
| `withdrawal.completed`         | Saque concluído com sucesso               |
| `withdrawal.failed`            | Saque falhou                              |
| `affiliate.commission_created` | Comissão de afiliado gerada               |

Todos os webhooks compartilham o mesmo formato de payload:

```json theme={null}
{
  "id": "evt_abc123xyz",
  "event": "transaction.paid",
  "devMode": false,
  "data": { }
}
```

<Info>
  **Dados sensíveis:** O campo `taxId` (CPF/CNPJ) é mascarado nos payloads (ex: `123.***.***-**`). Para pagamentos com cartão, apenas os últimos 4 dígitos e a bandeira são enviados.
</Info>

***

## Eventos de Transação

<Tabs>
  <Tab title="transaction.paid">
    Disparado quando o pagamento de uma transação é confirmado.

    ```json theme={null}
    {
      "id": "evt_abc123xyz",
      "event": "transaction.paid",
      "devMode": false,
      "data": {
        "transaction": {
          "id": "txn_abc123xyz",
          "externalId": "pedido-123",
          "amount": 10000,
          "paidAmount": 10000,
          "status": "PAID",
          "method": "PIX",
          "currency": "BRL",
          "receiptUrl": "https://app.upaybr.com/receipt/...",
          "createdAt": "2026-01-15T10:30:00.000Z",
          "updatedAt": "2026-01-15T10:30:05.000Z"
        },
        "customer": {
          "id": "cust_abc123",
          "name": "João Silva",
          "email": "joao@exemplo.com",
          "taxId": "123.***.***-**"
        },
        "paymentLink": {
          "id": "pl_abc123",
          "slug": "meu-produto"
        }
      }
    }
    ```
  </Tab>

  <Tab title="transaction.refunded">
    Disparado quando uma transação é reembolsada.

    ```json theme={null}
    {
      "id": "evt_abc123xyz",
      "event": "transaction.refunded",
      "devMode": false,
      "data": {
        "transaction": {
          "id": "txn_abc123xyz",
          "externalId": "pedido-123",
          "amount": 10000,
          "paidAmount": 10000,
          "status": "REFUNDED",
          "method": "PIX",
          "currency": "BRL",
          "createdAt": "2026-01-15T10:30:00.000Z",
          "updatedAt": "2026-01-15T11:00:00.000Z"
        },
        "customer": {
          "id": "cust_abc123",
          "name": "João Silva",
          "email": "joao@exemplo.com",
          "taxId": "123.***.***-**"
        },
        "reason": "requested_by_customer"
      }
    }
    ```
  </Tab>

  <Tab title="transaction.cancelled">
    Disparado quando uma transação é cancelada.

    ```json theme={null}
    {
      "id": "evt_abc123xyz",
      "event": "transaction.cancelled",
      "devMode": false,
      "data": {
        "transaction": {
          "id": "txn_abc123xyz",
          "externalId": "pedido-123",
          "amount": 10000,
          "paidAmount": 0,
          "status": "CANCELLED",
          "method": "PIX",
          "currency": "BRL",
          "createdAt": "2026-01-15T10:30:00.000Z",
          "updatedAt": "2026-01-15T10:45:00.000Z"
        },
        "customer": {
          "id": "cust_abc123",
          "name": "João Silva",
          "email": "joao@exemplo.com",
          "taxId": "123.***.***-**"
        }
      }
    }
    ```
  </Tab>

  <Tab title="transaction.expired">
    Disparado quando uma transação expira sem pagamento.

    ```json theme={null}
    {
      "id": "evt_abc123xyz",
      "event": "transaction.expired",
      "devMode": false,
      "data": {
        "transaction": {
          "id": "txn_abc123xyz",
          "externalId": "pedido-123",
          "amount": 10000,
          "paidAmount": 0,
          "status": "EXPIRED",
          "method": "PIX",
          "currency": "BRL",
          "createdAt": "2026-01-15T10:30:00.000Z",
          "updatedAt": "2026-01-15T10:30:00.000Z"
        },
        "customer": {
          "id": "cust_abc123",
          "name": "João Silva",
          "email": "joao@exemplo.com",
          "taxId": "123.***.***-**"
        }
      }
    }
    ```
  </Tab>
</Tabs>

***

## Eventos de Link de Pagamento

<Tabs>
  <Tab title="payment_link.paid">
    Disparado quando um pagamento é realizado via link de pagamento.

    ```json theme={null}
    {
      "id": "evt_abc123xyz",
      "event": "payment_link.paid",
      "devMode": false,
      "data": {
        "paymentLink": {
          "id": "pl_abc123",
          "slug": "meu-produto",
          "name": "Meu Produto",
          "amount": 10000,
          "currency": "BRL",
          "status": "ACTIVE"
        },
        "transaction": {
          "id": "txn_abc123xyz",
          "amount": 10000,
          "paidAmount": 10000,
          "status": "PAID",
          "method": "PIX",
          "receiptUrl": "https://app.upaybr.com/receipt/...",
          "createdAt": "2026-01-15T10:30:00.000Z",
          "updatedAt": "2026-01-15T10:30:05.000Z"
        },
        "customer": {
          "id": "cust_abc123",
          "name": "João Silva",
          "email": "joao@exemplo.com",
          "taxId": "123.***.***-**"
        }
      }
    }
    ```
  </Tab>

  <Tab title="payment_link.expired">
    Disparado quando um link de pagamento expira.

    ```json theme={null}
    {
      "id": "evt_abc123xyz",
      "event": "payment_link.expired",
      "devMode": false,
      "data": {
        "paymentLink": {
          "id": "pl_abc123",
          "slug": "meu-produto",
          "name": "Meu Produto",
          "amount": 10000,
          "currency": "BRL",
          "status": "EXPIRED",
          "expiredAt": "2026-01-16T00:00:00.000Z"
        }
      }
    }
    ```
  </Tab>
</Tabs>

***

## Eventos de Saque

<Tabs>
  <Tab title="withdrawal.completed">
    Disparado quando um saque é concluído com sucesso.

    ```json theme={null}
    {
      "id": "evt_abc123xyz",
      "event": "withdrawal.completed",
      "devMode": false,
      "data": {
        "withdrawal": {
          "id": "wth_abc123xyz",
          "amount": 50000,
          "status": "COMPLETED",
          "method": "PIX",
          "pixKey": "joao@exemplo.com",
          "receiptUrl": "https://app.upaybr.com/receipt/...",
          "createdAt": "2026-01-15T10:30:00.000Z",
          "updatedAt": "2026-01-15T10:30:10.000Z"
        }
      }
    }
    ```
  </Tab>

  <Tab title="withdrawal.failed">
    Disparado quando um saque falha.

    ```json theme={null}
    {
      "id": "evt_abc123xyz",
      "event": "withdrawal.failed",
      "devMode": false,
      "data": {
        "withdrawal": {
          "id": "wth_abc123xyz",
          "amount": 50000,
          "status": "FAILED",
          "method": "PIX",
          "pixKey": "joao@exemplo.com",
          "failureReason": "invalid_pix_key",
          "createdAt": "2026-01-15T10:30:00.000Z",
          "updatedAt": "2026-01-15T10:30:05.000Z"
        }
      }
    }
    ```
  </Tab>
</Tabs>

***

## Boas práticas

<Card title="Recomendações importantes" horizontal>
  * Use **HTTPS** em todos os webhooks
  * Valide a **assinatura HMAC** em cada requisição recebida
  * Registre cada evento recebido e processe cada um **uma única vez** (idempotência)
  * Responda **200 OK** somente após concluir o processamento
  * Implemente **retentativas com idempotência** caso o processamento falhe
  * **Não valide o payload inteiro** com schemas rígidos (como Zod) — novos campos podem ser adicionados sem aviso prévio
</Card>

***

<Card title="Precisa de ajuda?" icon="headset" color="#9747FF">
  Nossa equipe pode te ajudar. Contate-nos: <Icon icon="envelope" type="solid" /> [suporte@upaybr.com](mailto:suporte@upaybr.com)
</Card>
