Webhooks

Receba no seu servidor, em tempo real, os eventos de pedido e de SMS da sua conta.

Eventos

  • ORDER_CREATED — pedido criado e número ativo
  • SMS_RECEIVED — chegou um SMS no pedido
  • ORDER_COMPLETED — pedido encerrado com /finish
  • ORDER_CANCELLED — pedido cancelado com /cancel, ou vencido sem receber SMS

Corpo da requisição

Cada evento chega como um POST em JSON. Exemplo de SMS_RECEIVED:

JSON
{
  "event": "SMS_RECEIVED",
  "data": {
    "id": "clxxx...",
    "orderId": "clxxx...",
    "sender": "WhatsApp",
    "message": "Seu código é 123456",
    "code": "123456",
    "receivedAt": "2026-07-05T15:21:08.000Z"
  },
  "timestamp": 1720185600000
}

Cabeçalhos

HTTP
X-AltiviaCloud-Event: SMS_RECEIVED
X-AltiviaCloud-Signature: t=1720185600,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Content-Type: application/json

Verificar a assinatura

Toda entrega é assinada com HMAC-SHA256 usando o segredo do webhook — o valor whsec_ exibido quando você cria o webhook no painel. O segredo nunca viaja na requisição: ele só serve para calcular a assinatura, dos dois lados.

  1. Separe t (o momento do envio, em segundos) e v1 (a assinatura) do cabeçalho.
  2. Recuse se t estiver a mais de 5 minutos do seu relógio. É o que impede alguém de reenviar depois uma requisição capturada.
  3. Calcule o HMAC-SHA256 de {t}.{corpo} com o seu segredo e compare com v1 em tempo constante.

Use o corpo cru, exatamente como chegou. Fazer JSON.parse e serializar de novo muda espaçamento e ordem das chaves, e a assinatura nunca vai bater.

Node.js
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

function assinaturaValida(cabecalho, corpoCru, segredo, toleranciaSeg = 300) {
  const partes = Object.fromEntries(
    (cabecalho ?? "").split(",").map((par) => par.split("=", 2)),
  );
  const t = Number(partes.t);
  if (!partes.v1 || !Number.isFinite(t)) return false;
  if (Math.abs(Date.now() / 1000 - t) > toleranciaSeg) return false;

  const esperado = createHmac("sha256", segredo).update(`${t}.${corpoCru}`).digest("hex");
  const a = Buffer.from(partes.v1, "hex");
  const b = Buffer.from(esperado, "hex");
  // timingSafeEqual lança se os tamanhos diferem
  return a.length === b.length && timingSafeEqual(a, b);
}

const app = express();

// express.raw entrega o corpo como veio, sem interpretar
app.post("/webhooks/altivia", express.raw({ type: "application/json" }), (req, res) => {
  const corpoCru = req.body.toString("utf8");
  const assinatura = req.get("X-AltiviaCloud-Signature");

  if (!assinaturaValida(assinatura, corpoCru, process.env.WEBHOOK_SECRET)) {
    return res.status(401).end();
  }

  const evento = JSON.parse(corpoCru); // só depois de validar
  res.status(200).end();               // responda logo, processe depois
  processar(evento);
});

Entrega

Cada evento é enviado uma vez, com espera máxima de 10 segundos pela resposta. Se o seu endpoint falhar, responder com erro ou demorar mais que isso, o evento não é reenviado e o contador de falhas do webhook aumenta. Responda 2xx logo e faça o processamento depois.

URL do webhook

A URL precisa usar https e apontar para um endereço público na internet. Endereços de rede interna, de loopback e de metadados de nuvem são recusados no cadastro e de novo a cada entrega. Redirecionamentos não são seguidos: responda direto na URL cadastrada.

Configure os webhooks em Painel → Webhooks.