# Webhooks

A Fluxis usa webhooks em **nível de conta** para notificar sua aplicação em tempo real quando eventos ocorrem, como mudanças de status de pagamento. Este guia cobre registro, comportamento de entrega e um resumo da verificação de assinatura.

Para o passo a passo completo de verificação HMAC (JSON canônico, exemplo prático, endpoint de teste), consulte [Verificação de assinatura de webhook](/pt/docs/developers/webhook-signature-verification).

## Visão geral

Quando você registra um webhook para uma **conta**, a Fluxis envia requisições HTTP POST à sua URL sempre que eventos correspondentes ocorrem. Cada requisição inclui:

- Um payload JSON com dados do evento
- Cabeçalhos de segurança para verificação de assinatura

::::warning
Sempre verifique as assinaturas de webhook antes de processar o payload. Consulte [Verificação de assinatura de webhook](/pt/docs/developers/webhook-signature-verification).
::::

## Cabeçalhos de webhook

Cada requisição de webhook inclui os seguintes cabeçalhos:

| Cabeçalho                | Descrição                                             |
| --------------------- | ------------------------------------------------------- |
| `X-Request-Signature` | Assinatura HMAC-SHA256 do payload                    |
| `X-Request-Timestamp` | Timestamp Unix (segundos) quando a requisição foi gerada |
| `Content-Type`        | Sempre `application/json`                               |

## Registrar webhooks

Webhooks são gerenciados em **nível de conta** (não por Ponto de Venda). Use as credenciais da organização para registrar um endpoint HTTPS para a conta vinculada ao seu POS.

**Criar webhook:** `POST /v1/account/{accountID}/webhook`

```json
{
  "url": "https://your-server.com/webhooks/fluxis",
  "event_type": "payment_request",
  "description": "Order status updates"
}
```

A resposta inclui um `secret` — **armazene-o imediatamente**; ele só é retornado na criação.

| `event_type`        | Quando dispara                                      |
| ------------------- | -------------------------------------------------- |
| `payment_request`   | Mudanças de status da solicitação de pagamento (veja abaixo)         |
| `incoming_transfer` | Eventos de transferência recebida (reservado / uso futuro) |
| `refund`            | Eventos de reembolso                                      |

**Outros endpoints de gerenciamento:**

| Ação            | Endpoint                                                          |
| ----------------- | ----------------------------------------------------------------- |
| List              | `GET /v1/account/{accountID}/webhook/list`                        |
| Update URL        | `PUT /v1/account/{accountID}/webhook/{webhookID}/url`             |
| Activate          | `PATCH /v1/account/{accountID}/webhook/{webhookID}/activate`      |
| Deactivate        | `PATCH /v1/account/{accountID}/webhook/{webhookID}/deactivate`    |
| Delete            | `DELETE /v1/account/{accountID}/webhook/{webhookID}/delete`       |
| Test delivery     | `POST /v1/account/{accountID}/webhook/{webhookID}/test`           |
| Delivery logs     | `GET /v1/account/{accountID}/webhook/logs?page=1&page_size=20`    |

Consulte a [Referência API — Webhooks](/api/webhooks#create-webhook).

::::note
O gerenciamento de webhooks exige credenciais de API em **nível de organização**. A conta deve pertencer à sua organização.
::::

---

## Verificar assinaturas de webhook (resumo)

A Fluxis assina o **corpo JSON parseado completo** — por exemplo `{ "data": { … } }` — não os bytes brutos da requisição nem apenas o objeto interno `data`.

**Algoritmo:**

1. Serialize o corpo parseado em **JSON canônico** (compacto, chaves ordenadas alfabeticamente em cada nível de objeto)
2. Monte a string assinada: `<unix_timestamp>.<canonical_json>`
3. Calcule `HMAC-SHA256(webhook_secret, signed_string)` e codifique o resultado em hexadecimal
4. Compare com `X-Request-Signature` usando função de tempo constante
5. Rejeite se o timestamp tiver mais de **300 segundos** (5 minutos)

**Exemplo de string assinada** (timestamp `1712345678`, payload de amostra `payment_request`):

```
1712345678.{"data":{"account_id":"c87464d1-c54a-48c3-80c9-bee4fddf593d","external_reference_id":"order-123","notification_type":"payment_request_status_update","payment_request_id":"aa5d6c80-0b0b-4a2a-bbc8-14a41e6245bf","payment_request_type":"fixed","point_of_sale_id":"3cc1f7ca-c017-4e7e-b00d-aca0da6e464e","status":"completed"}}
```

::::note Campos opcionais
Campos como `external_reference_id` e `account_id` usam `omitempty` na API — são omitidos do JSON quando não definidos. Seu verificador deve canonicalizar o **corpo parseado como recebido**, não um template de esquema fixo.
::::

**Verificação rápida em Node.js** (consulte o [guia completo](/pt/docs/developers/webhook-signature-verification) para Python, Go e um exemplo prático):

```typescript
import crypto from "crypto";

function sortKeys(value: unknown): unknown {
  if (Array.isArray(value)) return value.map(sortKeys);
  if (value !== null && typeof value === "object") {
    return Object.keys(value as object)
      .sort()
      .reduce((acc, key) => {
        acc[key] = sortKeys((value as Record<string, unknown>)[key]);
        return acc;
      }, {} as Record<string, unknown>);
  }
  return value;
}

function verifyWebhookSignature(
  payload: unknown,
  signature: string,
  timestamp: string,
  secret: string,
): boolean {
  const requestTimestamp = parseInt(timestamp, 10);
  if (Math.floor(Date.now() / 1000) - requestTimestamp > 300) return false;

  const canonicalJson = JSON.stringify(sortKeys(payload));
  const signedString = `${timestamp}.${canonicalJson}`;
  const expectedSignature = crypto
    .createHmac("sha256", secret)
    .update(signedString)
    .digest("hex");

  if (signature.length !== expectedSignature.length) return false;
  return crypto.timingSafeEqual(
    Buffer.from(signature, "utf8"),
    Buffer.from(expectedSignature, "utf8"),
  );
}
```

---

## Quais status disparam webhooks

A Fluxis entrega webhooks `payment_request` quando uma solicitação de pagamento entra em **`processing`** (pagamento detectado) ou um estado terminal de confirmação (**`completed`**, **`underpaid`** ou **`overpaid`**).

Mudanças de status como **`expired`** ou **`failed`** **não** são enviadas via webhook — faça polling do endpoint de solicitação de pagamento ou concilie via transações.

## Estrutura do payload de webhook

Webhooks de atualização de status de solicitação de pagamento têm a seguinte estrutura:

```json
{
  "data": {
    "notification_type": "payment_request_status_update",
    "payment_request_id": "aa5d6c80-0b0b-4a2a-bbc8-14a41e6245bf",
    "payment_request_type": "fixed",
    "external_reference_id": "your-order-id-123",
    "status": "completed",
    "point_of_sale_id": "3cc1f7ca-c017-4e7e-b00d-aca0da6e464e",
    "account_id": "c87464d1-c54a-48c3-80c9-bee4fddf593d"
  }
}
```

| Campo                   | Descrição                                                  |
| ----------------------- | ------------------------------------------------------------ |
| `notification_type`     | Tipo de notificação (ex.: `payment_request_status_update`) |
| `payment_request_id`    | Identificador único da solicitação de pagamento                     |
| `payment_request_type`  | Tipo de solicitação de pagamento (ex.: `fixed`)                      |
| `external_reference_id` | Seu ID de referência interna (opcional; omitido se não definido)      |
| `status`                | Novo status da solicitação de pagamento                            |
| `point_of_sale_id`      | ID do Ponto de Venda                                      |
| `account_id`            | ID da conta associada (opcional; omitido se não definido)    |

---

## Boas práticas

### Responder rapidamente

Seu endpoint deve retornar HTTP `200 OK` em **2 segundos**. Se precisar de processamento pesado, confirme o webhook imediatamente e enfileire o trabalho para execução em segundo plano.

```typescript
app.post("/webhooks/fluxis", async (req, res) => {
  if (!verifyWebhookSignature(req.body, /* ... */)) {
    return res.status(401).send();
  }
  res.status(200).json({ received: true });
  processWebhookAsync(req.body).catch(console.error);
});
```

### Tratar duplicatas (idempotência)

Você pode receber o mesmo evento de webhook várias vezes. Use `payment_request_id` e `status` para deduplicar.

### Consciência de lógica de retry

Se seu endpoint não responder com HTTP `200`, a Fluxis pode reenviar o webhook. Garanta que seu handler seja idempotente.

---

## Testar webhooks

1. Exponha seu servidor local com [ngrok](https://ngrok.com) ou similar durante o desenvolvimento.
2. Registre a URL pública via `POST /v1/account/{accountID}/webhook`.
3. Dispare uma entrega de teste: `POST /v1/account/{accountID}/webhook/{webhookID}/test` — usa o mesmo caminho de assinatura da produção.
4. Confirme que seu handler verifica a assinatura e retorna HTTP `200`.

Consulte [Verificação de assinatura de webhook](/pt/docs/developers/webhook-signature-verification) para verificação passo a passo e falhas comuns.

---

## Solução de problemas

| Problema | O que verificar |
| ----- | ------------- |
| Assinatura falha | Segredo correto da resposta de criação; assinar o corpo parseado `{data:…}`; regras de JSON canônico — [guia completo](/pt/docs/developers/webhook-signature-verification) |
| Não recebe eventos | URL HTTPS pública; HTTP `200` em 2 s; webhook habilitado; `event_type` correto |
| Faltam webhooks `expired` / `failed` | Esperado — faça polling de `GET /v1/pos/{posID}/payment-request/{paymentRequestID}` |

---

## Precisa de ajuda?

Entre em contato em [support@fluxis.us](mailto:support@fluxis.us) com o ID da conta, ID do webhook e timestamp da entrega problemática.
