Data Drains

Data Drains permitem que owners e admins de organização em planos Enterprise exportem continuamente dados do Zoen para um destino que controlam — um bucket S3 do cliente, bucket Google Cloud Storage, container Azure Blob, tabela BigQuery, tabela Snowflake, intake de logs Datadog ou um webhook HTTPS. Um drain roda num agendamento, pega só as linhas novas desde a última execução bem-sucedida e as escreve no destino. Ver a configuração do drain e o histórico de execuções é restrito a owners e admins também, já que destinos expõem nomes internos de bucket, identificadores de tabela e URLs de webhook.

Drains são independentes de Data Retention, mas feitos para compor com ele — veja Combinando com Data Retention abaixo.


Setup

Vá em Settings → Enterprise → Data Drains no seu workspace e clique em New drain.

Página de settings de Data Drains mostrando dois drains configurados — um exportando logs de workflow para Amazon S3 diariamente, outro exportando chats do Copilot para um webhook HTTPS a cada hora

Diálogo New data drain com campos para nome, source, cadence, destination e credenciais S3

Cada drain tem quatro partes:

  1. Uma source — a categoria de dados a exportar
  2. Um destination — para onde os dados vão
  3. Um schedule — com que frequência roda
  4. Um name — único na sua organização

Sources

Um drain exporta exatamente uma source. Para exportar várias sources, crie vários drains.

SourceDescrição
Workflow logsRegistros de execução de workflow (uma linha por execução, só depois que a run chega a um estado terminal).
Job logsRegistros de job em background (APIs deployadas, schedules, webhooks). Só linhas em estado terminal são exportadas.
Audit logsEventos de audit escopados a organização e workspace — logins, mudanças de permissão, criação/exclusão de recursos, mudanças de configuração de drain.
Copilot chatsHistórico de chat do Mothership.
Copilot runsRegistros de run do Mothership (só estado terminal).

Cada linha é entregue como uma única linha de NDJSON. A forma de cada linha faz parte do schema público e é estável entre versões; toda linha carrega um campo id que consumidores downstream podem usar para dedupe.

Drains exportam cada linha exatamente uma vez com base no cursor de criação. Campos mutáveis em Copilot chats (messages, title, lastSeenAt) são um snapshot pontual e não serão reemitidos se o chat for atualizado depois. Trate a exportação como append-only e reconstitúa o estado atual a partir do seu próprio sistema de registro se precisar.


Destinations

Amazon S3 (ou qualquer store compatível com S3)

Escreve um objeto NDJSON por chunk entregue no seu bucket.

  • Bucket — o nome do bucket. Já precisa existir; o Zoen não cria buckets.
  • Region — região AWS (ex.: us-east-1).
  • Prefix (opcional) — caminho de pasta dentro do bucket. Barra final opcional.
  • Access key ID / Secret access key — credenciais IAM com s3:PutObject no bucket. O botão "Test connection" faz um probe real de escrita para verificar e depois o exclui.
  • Endpoint (opcional) — para stores não AWS como MinIO, Cloudflare R2 ou GCS S3-interop. Deixe em branco para AWS S3.
  • Force path-style (opcional) — obrigatório para MinIO/Ceph, deve ficar desligado para AWS S3 e R2.

Object keys são determinísticas:

{prefix}/{source}/{drainId}/{yyyy}/{mm}/{dd}/{runId}-{seq}.ndjson

Objetos são escritos com encryption server-side AES256.

Google Cloud Storage

Escreve um objeto NDJSON por chunk entregue no seu bucket GCS.

  • Bucket — o nome do bucket. Já precisa existir; o Zoen não cria buckets.
  • Prefix (opcional) — caminho de pasta dentro do bucket. Barra final opcional.
  • Service account JSON key — cole a JSON key completa de uma service account com storage.objects.create (e storage.objects.delete se quiser que "Test connection" limpe o probe). O Zoen autentica via OAuth2 service-account JWT e faz upload pela GCS JSON API.

Nomes de objeto seguem o mesmo layout {prefix}/{source}/{drainId}/{yyyy}/{mm}/{dd}/{runId}-{seq}.ndjson do S3. Metadados do objeto espelham as keys sim-* do destino S3 via headers x-goog-meta-*.

Azure Blob Storage

Escreve um block blob NDJSON por chunk entregue no seu container.

  • Account name — sua storage account (3–24 chars minúsculos).
  • Container — já precisa existir; o Zoen não cria containers.
  • Prefix (opcional) — caminho de pasta dentro do container.
  • Account key — uma access key da storage account com acesso de escrita ao container.

Nomes de blob seguem o mesmo layout {prefix}/{source}/{drainId}/{yyyy}/{mm}/{dd}/{runId}-{seq}.ndjson. Os metadados sim-* são expostos como metadados de blob Azure (colapsados para minúsculas pelas regras de identificador do Azure).

Para clouds soberanas, defina Endpoint suffix como blob.core.usgovcloudapi.net (US Gov), blob.core.chinacloudapi.cn (China) ou blob.core.cloudapi.de (Germany).

Google BigQuery

Faz stream de cada linha numa tabela alvo via a API tabledata.insertAll, com dedup por insertId por linha.

  • Project ID — seu projeto GCP (suporta IDs com domínio como example.com:my-project).
  • Dataset ID / Table ID — já precisam existir; o Zoen não cria tabelas. O schema da tabela precisa acomodar a forma da linha da source (uma coluna por campo top-level, ou uma única coluna JSON/STRING com o resto como ignoreUnknownValues).
  • Service account JSON key — precisa de roles/bigquery.dataEditor (insert) e roles/bigquery.metadataViewer (para o probe tables.get usado por "Test connection"). O Zoen autentica via OAuth2 service-account JWT.

Cada linha é enviada com um insertId de {drainId}-{runId}-{sequence}-{index}. O BigQuery faz dedupe de inserts com o mesmo insertId por ~60 segundos, então retries nessa janela não duplicam. Se um chunk reportar falha parcial (insertErrors), a execução falha com os índices das linhas ofensoras e um retry do outer-driver pode duplicar linhas que já tiveram sucesso — o retry limitado do dispatcher minimiza esse risco. Limites por request impostos: body de 10 MB, 50.000 linhas.

Snowflake

Insere cada linha numa coluna VARIANT alvo via Snowflake SQL API v2 com auth JWT por key-pair.

  • Account — o identificador da conta Snowflake. A forma preferida é <orgname>-<acctname> (sem pontos). O legado <locator>.<region>.<cloud> também é aceito.
  • User / Warehouse / Database / Schema / Table — já precisam existir. O usuário precisa de privilégio INSERT na tabela e USAGE no warehouse, database e schema.
  • Column (opcional) — nome da coluna VARIANT alvo. O padrão é DATA (bate com o folding de identificadores unquoted do Snowflake).
  • Role (opcional) — role Snowflake a assumir.
  • Private key (PEM) — private key RSA em PKCS8. Registre a public key correspondente no usuário Snowflake via ALTER USER ... SET RSA_PUBLIC_KEY = '...'.

Cada chunk vira um único INSERT INTO "DB"."SCHEMA"."TABLE" ("col") VALUES (PARSE_JSON(?)), ... com um binding TEXT por linha. Identificadores são quoted para preservar case. O destino trata o padrão async 202-then-poll do Snowflake de forma transparente. Payloads JSON por linha têm teto de 16 MB para bater com o limite VARIANT do Snowflake.

Datadog Logs

Faz POST de cada linha como uma entrada de log no intake de logs v2 do Datadog.

  • Site — seu site Datadog: us1, us3, us5, eu1, ap1, ap2 ou gov.
  • Service (opcional) — valor para o campo reservado service. O padrão é sim.
  • Tags (opcional)ddtags separados por vírgula anexados a toda entrada junto com tags auto-injetadas sim_drain_id:, sim_run_id: e sim_source:.
  • API key — uma API key Datadog (não Application key) com permissão de escrita de logs.

Campos top-level da linha são auto-indexados como atributos de log Datadog. Os campos reservados ddsource, service, ddtags e message são sempre definidos pelo Zoen e sobrescrevem qualquer coisa na linha. Payloads acima de 1 KB são comprimidos com gzip. Limites impostos batem com o intake do Datadog: 5 MB por request (pós-compressão), 1000 entradas por request, 1 MB por entrada.

HTTPS Webhook

Faz POST de cada chunk como NDJSON no seu endpoint.

  • URL — precisa ser HTTPS. O Zoen resolve o hostname e se recusa a entregar a IPs privados, loopback ou de cloud-metadata. O IP resolvido é fixado pela duração de uma execução para prevenir DNS rebinding.
  • Signing secret — secret compartilhado usado para assinatura HMAC-SHA256.
  • Bearer token (opcional) — enviado como Authorization: Bearer <token>.
  • Signature header name (opcional) — o padrão é X-Zoen-Signature.

Cada request inclui:

Content-Type: application/x-ndjson
User-Agent: Zoen-DataDrain/1.0
X-Zoen-Timestamp: <unix-seconds>
X-Zoen-Signature-Version: v1
X-Zoen-Signature: t=<unix-seconds>,v1=<hex(hmac-sha256)>
X-Zoen-Drain-Id: <drain id>
X-Zoen-Run-Id: <run id>
X-Zoen-Source: <source name>
X-Zoen-Sequence: <chunk index>
X-Zoen-Row-Count: <rows in this chunk>
Idempotency-Key: <runId>-<sequence>

A assinatura é calculada como HMAC-SHA256(secret, "${timestamp}.${body}") e serializada como t=<timestamp>,v1=<hex>. Verifique recalculando sobre a mesma string e rejeitando timestamps com mais de ~5 minutos — isso defende contra ataques de replay de request capturada.

Entregas falhas retentam até 3 vezes com exponential backoff (500ms, 1s, 2s com ±20% jitter), respeitando Retry-After em 429/503. Respostas 4xx não retentáveis falham a execução imediatamente.


Schedule

CadenceO drain roda
HourlyUma vez por hora.
DailyUma vez por dia.

Você também pode desabilitar um drain com o toggle Enabled (para de rodar mas é preservado), ou disparar uma execução fora do schedule com Run now em qualquer linha de drain.


Semântica de entrega

Drains usam um cursor opaco que só avança em sucesso completo. Se uma entrega falha no meio de uma execução, o cursor não muda e a próxima execução replaya a partir da última posição bem-sucedida.

Isso é entrega at-least-once. Combinado com o campo id em toda linha e o header Idempotency-Key em todo chunk de webhook, sistemas downstream podem fazer dedupe de forma determinística.

As últimas 10 execuções de cada drain ficam visíveis expandindo sua linha na página de settings, com status, contagem de linhas, bytes escritos, locator do destino (s3://... ou URL do webhook) e a mensagem de erro se falhou.


Segurança

  • Credenciais de destino são criptografadas em repouso usando a mesma encryption aware de key-rotation que protege tokens OAuth.
  • Credenciais nunca são retornadas pela API do Zoen depois da criação. Updates aceitam novas credenciais; omiti-las deixa o blob criptografado existente no lugar.
  • URLs de webhook são validadas contra SSRF: só HTTPS, sem IPs privados/loopback/metadata, com o IP resolvido fixado para derrotar DNS rebinding.
  • Toda chamada de create, update, delete, run manual e test-connection é registrada no Audit Log.

Combinando com Data Retention

Drains e Data Retention são módulos independentes. O Zoen não condiciona retenção ao progresso do drain — se um drain está falhando, a retenção ainda purga dados no próprio agendamento. Isso bate com o modelo usado por Datadog Archives e AWS CloudWatch + S3 Export: manter as duas configurações ortogonais e deixar o cliente combiná-las de propósito.

Para usar os dois juntos com segurança, defina a cadence do drain mais curta que o período de retenção da mesma categoria de dados:

Source do drainCombina com a configuração de retenção
Workflow logs, Job logsLog retention
Copilot chats, Copilot runsTask cleanup
Audit logs(sem configuração de retenção hoje — audit logs são mantidos indefinidamente)

Por exemplo, com Log retention em 30 dias, defina o drain de workflow-logs como Hourly ou Daily para que toda linha seja exportada bem antes da retenção purgá-la do Zoen. Monitore execuções recentes de drain na página de settings; se um drain estiver falhando por mais tempo que sua janela de retenção, você pode perder linhas que a retenção purga antes de serem exportadas.

Depois que os dados aterrissam no seu bucket ou sistema de webhook, o lifecycle de archive (transições para Glacier, expiration, propagação de right-to-erasure GDPR) é governado pela sua própria infraestrutura — o Zoen não tem mais visibilidade daqueles dados depois que a entrega tem sucesso.


Common Questions

Só owners e admins da organização podem ver, criar, editar, rodar ou excluir drains. No Zoen Cloud, a organização precisa estar num plano Enterprise.
O cursor do drain só avança em sucesso geral, então uma falha replaya os mesmos chunks na próxima execução. Toda linha tem um campo `id` estável e todo chunk de webhook tem um header `Idempotency-Key` para os receivers fazerem dedupe.
Sim — crie um drain por source, todos apontando para o mesmo bucket ou endpoint. Destinos S3 fazem namespace por source automaticamente; receivers de webhook podem ramificar no header `X-Zoen-Source`.
Não. A exclusão só remove a configuração do drain e seu histórico de execuções do Zoen. Dados já escritos no seu bucket ou enviados ao seu webhook são seus e não são afetados.
A execução falha, o cursor do drain não avança e a execução falha é registrada com o erro. Depois que você corrigir as credenciais com um Update ou recriando o drain, a próxima execução replaya de onde a última execução bem-sucedida parou.
NDJSON — JSON delimitado por newline, uma linha por registro. Cada chunk é um único objeto S3 ou um único body de POST.

Setup self-hosted

Variáveis de ambiente

DATA_DRAINS_ENABLED=true
NEXT_PUBLIC_DATA_DRAINS_ENABLED=true

NEXT_PUBLIC_DATA_DRAINS_ENABLED mostra a página Settings → Enterprise → Data Drains na UI. DATA_DRAINS_ENABLED gateia os endpoints mutáveis server-side e o dispatcher do cron — quando não definido num deployment self-hosted, requests de create/update/delete/run de drain retornam 404 e o dispatcher é no-op. Ambos devem ser definidos como true juntos.

Data Drains de outra forma dependem da infraestrutura padrão de background jobs do Trigger.dev usada em outras partes do Zoen — nenhum setup adicional é necessário. O dispatcher do cron roda a cada hora e faz fan-out dos drains devidos como background jobs.

On this page