> For the complete documentation index, see [llms.txt](https://ereemby.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ereemby.gitbook.io/docs/informacoes-gerais/notificacoes/notificacoes-de-vendas-proprio.md).

# Notificações de vendas próprio

Vamos utilizar o sistema de [Webhooks](/docs/desenvolvedores/webhooks.md).\
Você vai precisar saber um pouco de programação.\
Você também vai precisar de um endpoint público para receber `POST` em JSON.

{% hint style="warning" %}
Isso é integração técnica.\
Se você nunca montou uma API, peça ajuda de um dev.
{% endhint %}

### Evento enviado

Quando o pagamento é aprovado, a Ereemby envia:

* [`order_paid`](/docs/desenvolvedores/webhooks/vendas.md): evento enviado quando o pedido foi pago com sucesso.

Esse é o evento recomendado para:

* notificação em Discord, Telegram ou Slack
* integração com CRM, BI e ERP
* automações internas após pagamento confirmado

### Exemplo completo: notificação no Discord (Node.js)

Esse exemplo usa:

* Node.js com `express`
* Validação HMAC com `crypto`
* Envio no Discord com `axios`
* `ngrok` para testar no PC local

{% hint style="danger" %}
Não coloque `webhook_secret` e `discord_webhook_url` no código.\
Use variável de ambiente.
{% endhint %}

{% code title="server.js" %}

```js
const express = require("express");
const crypto = require("crypto");
const axios = require("axios");

const app = express();

const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
const DISCORD_WEBHOOK_URL = process.env.DISCORD_WEBHOOK_URL;

if (!WEBHOOK_SECRET) throw new Error("Missing WEBHOOK_SECRET");
if (!DISCORD_WEBHOOK_URL) throw new Error("Missing DISCORD_WEBHOOK_URL");

app.use(
  express.json({
    verify: (req, res, buf) => {
      req.rawBody = buf;
    },
  })
);

// A Ereemby assina o corpo bruto com HMAC-SHA256 e envia o digest em hex
// no header "x-webhook-signature" (64 caracteres hexadecimais).
function isValidSignature(signature, rawBody) {
  if (typeof signature !== "string") {
    return false;
  }

  // Garante que a assinatura recebida é hex puro antes de qualquer comparacao
  if (!/^[0-9a-f]+$/i.test(signature)) {
    return false;
  }

  const expectedSignature = crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(rawBody)
    .digest("hex");

  if (signature.length !== expectedSignature.length) {
    return false;
  }

  return crypto.timingSafeEqual(
    Buffer.from(signature, "hex"),
    Buffer.from(expectedSignature, "hex")
  );
}

app.post("/", async (req, res) => {
  try {
    const signature = req.headers["x-webhook-signature"];

    if (!signature) {
      return res.status(401).send("Missing signature");
    }

    if (!isValidSignature(signature, req.rawBody)) {
      return res.status(403).send("Invalid signature");
    }

    const payload = req.body;

    if (payload.event_name !== "order_paid") {
      return res.status(200).send("Evento ignorado");
    }

    // Estrutura do order_paid (campos em ingles)
    const order = payload.order ?? {};
    const customer = order.customer ?? {};
    const items = payload.items ?? [];

    const itemSummary = items.length
      ? items
          .slice(0, 10)
          .map((item) => `• ${item.name} x${item.quantity}`)
          .join("\n")
      : "Consulte o payload completo no seu handler.";

    // No payload real o "name" do cliente pode vir vazio, entao cai pro e-mail
    const clienteLabel =
      (customer.name && customer.name.trim()) || customer.email || "N/A";

    const totalLabel =
      order.total != null
        ? `${order.currency ?? "R$"} ${Number(order.total).toFixed(2)}`
        : "N/A";

    const embed = {
      title: "Pedido pago",
      color: 0x2ecc71,
      fields: [
        { name: "Evento", value: "order_paid", inline: true },
        { name: "Pedido", value: String(order.id ?? "N/A"), inline: true },
        { name: "Cliente", value: clienteLabel, inline: false },
        {
          name: "Pagamento",
          value: String(order.method ?? "N/A").toUpperCase(),
          inline: true,
        },
        { name: "Total", value: totalLabel, inline: true },
        { name: "Itens", value: itemSummary, inline: false },
      ],
      footer: { text: "Ereemby • Pagamento confirmado" },
      timestamp: order.paid_at ?? new Date().toISOString(),
    };

    await axios.post(DISCORD_WEBHOOK_URL, {
      embeds: [embed],
    });

    return res.status(200).send("OK");
  } catch (error) {
    console.error("Erro no webhook:", error);
    return res.status(500).send("Internal error");
  }
});

app.listen(3000, () => {
  console.log("Webhook ouvindo em http://localhost:3000/");
});

```

{% endcode %}

#### Testando no PC local com ngrok

1. Rode sua API local na porta `3000`.
2. Rode o ngrok apontando para essa porta.

```bash
ngrok http 3000
```

3. Copie a URL HTTPS gerada pelo ngrok.
4. Cole essa URL na configuração do webhook na Ereemby.

{% hint style="info" %}
Quando você parar o ngrok, a URL muda.\
Você vai precisar atualizar o webhook.
{% endhint %}

### Boas práticas

* Valide `x-webhook-signature` antes de processar.
* Trate webhooks como “pelo menos uma vez”.
* Responda `2xx` rápido e processe em background.
* Registre `event_name`, `pedido.id` e horário de recebimento.

{% hint style="warning" %}
No dia **27/07/2026**, os eventos `cart` e `sale` serão descontinuados.\
Depois dessa data, apenas `order_paid` será enviado.
{% endhint %}
