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.


Cada drain tem quatro partes:
- Uma source — a categoria de dados a exportar
- Um destination — para onde os dados vão
- Um schedule — com que frequência roda
- Um name — único na sua organização
Sources
Um drain exporta exatamente uma source. Para exportar várias sources, crie vários drains.
| Source | Descrição |
|---|---|
| Workflow logs | Registros de execução de workflow (uma linha por execução, só depois que a run chega a um estado terminal). |
| Job logs | Registros de job em background (APIs deployadas, schedules, webhooks). Só linhas em estado terminal são exportadas. |
| Audit logs | Eventos 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 chats | Histórico de chat do Mothership. |
| Copilot runs | Registros 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:PutObjectno 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}.ndjsonObjetos 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(estorage.objects.deletese 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/STRINGcom o resto comoignoreUnknownValues). - Service account JSON key — precisa de
roles/bigquery.dataEditor(insert) eroles/bigquery.metadataViewer(para o probetables.getusado 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
INSERTna tabela eUSAGEno 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,ap2ougov. - Service (opcional) — valor para o campo reservado
service. O padrão ésim. - Tags (opcional) —
ddtagsseparados por vírgula anexados a toda entrada junto com tags auto-injetadassim_drain_id:,sim_run_id:esim_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
| Cadence | O drain roda |
|---|---|
| Hourly | Uma vez por hora. |
| Daily | Uma 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 drain | Combina com a configuração de retenção |
|---|---|
| Workflow logs, Job logs | Log retention |
| Copilot chats, Copilot runs | Task 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
Setup self-hosted
Variáveis de ambiente
DATA_DRAINS_ENABLED=true
NEXT_PUBLIC_DATA_DRAINS_ENABLED=trueNEXT_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.