Builders Program

Build with us for a whole month. Unlimited usage and add-ons, weekly calls and your own Slack channel.

Learn more
Polymorfa

Command Palette

Search for a command to run...

Drains

Envie os dados para onde você trabalha

Encaminhe logs operacionais e Analytics por número para seus próprios endpoints HTTPS. Assinados, deduplicáveis e reenviados até serem aceitos.

Drains por projeto
5
Regras de amostragem em ordem
20
Tentativas de entrega em 24 h
6
Janelas de Analytics
1 a 168 h

Fontes

Logs e Analytics, sem conteúdo.

Escolha uma fonte por drain. Nenhuma das duas carrega o texto das mensagens, contatos ou credenciais.

Logs operacionais. Requisições da API, mudanças nos números e entregas de webhook, com IDs de requisição e de trace.
Analytics por hora. Um retrato por número a cada hora, em janelas de 1, 24 ou 168 horas. Exige Analytics ativado.
Amostragem em ordem. Até 20 regras de 0 a 100%. A primeira que combina decide, e registros do mesmo pedido seguem juntos.

Entrega

Um contrato em que você pode confiar.

JSON ou NDJSON por HTTPS POST, assinado com HMAC, pelo menos uma vez, com cabeçalhos próprios se quiser.

Assinado. HMAC-SHA256 sobre o timestamp e o corpo original. Troque, gere de novo ou remova o segredo.
Reenviado, sem duplicar. Cada nova tentativa mantém o mesmo ID de entrega no Idempotency-Key. Diagnósticos ficam 7 dias.
Status visível. Entregas pendentes e com falha, o último status HTTP e o último erro. Pause, retome ou exclua.

No seu código

Seu receptor, em poucas linhas.

Verifique, deduplique e confirme. Ou leia as mesmas métricas pela API.

app/drain/route.ts
import { createHmac, timingSafeEqual } from "node:crypto";

const MAX_AGE_SECONDS = 300; // Retries are re-signed with a fresh timestamp.

export async function POST(request: Request) {
  const body = Buffer.from(await request.arrayBuffer());
  const timestamp = request.headers.get("x-polymorfa-timestamp") ?? "";
  const age = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!/^\d+$/.test(timestamp) || age > MAX_AGE_SECONDS) {
    return new Response(null, { status: 401 });
  }
  const expected = "sha256=" + createHmac("sha256", drainSecret)
    .update(timestamp + "." + body)
    .digest("hex");
  const given = request.headers.get("x-polymorfa-signature") ?? "";
  if (given.length !== expected.length ||
      !timingSafeEqual(Buffer.from(given), Buffer.from(expected))) {
    return new Response(null, { status: 401 });
  }

  // Take the delivery ID from the signed body, not a header. NDJSON
  // carries it on every line; retries keep it, so store once.
  const ndjson = request.headers.get("content-type")?.startsWith("application/x-ndjson");
  const envelope = JSON.parse(ndjson ? body.toString().split("\n")[0] : body.toString());
  await warehouse.insertOnce(envelope.deliveryId, body);
  return new Response(null, { status: 204 });
}

Superfície da API

O contrato de entrega.

Configure drains no Console. Cada entrega chega ao seu endpoint com estes cabeçalhos e um destes corpos.

Requisição

  • POSThttps://your-endpointUm HTTPS POST por entrega, 10 s de limite
  • HeaderX-Polymorfa-TimestampSegundos Unix do envio
  • HeaderX-Polymorfa-Signaturesha256=HMAC(timestamp + "." + body)
  • HeaderIdempotency-KeyO ID de entrega, igual em cada tentativa

Corpos

  • Bodyapplication/json{ version, deliveryId, records }
  • Bodyapplication/x-ndjsonUma linha { version, deliveryId, record }

Puxar em vez de receber

  • GET/platform/analytics/metricsMétricas para o seu Collector
  • GET/platform/projects/{projectId}/request-logsLogs de requisições, com logs:read

Comece com um número grátis

Seu primeiro número real é grátis. Adicione mais números no Pay-As-You-Go.