---
title: Automação, lotes e webhooks
description: Jobs duráveis, importação validada, exportação privada e notificações assinadas.
---

Produção: `https://api.agilink.io/v1`. Homologação isolada: `https://api.staging.agilink.io/v1`. Os [20 endpoints com requests e responses](/api/endpoints-a4b/) e a [referência OpenAPI](/referencia/) usam o contrato validado. OAuth será implementado na Tarefa 2; MCP, na Tarefa 3. A [área de desenvolvedores](https://developers.agilink.io/developers) gerencia credenciais, uso, operações, auditoria e webhooks.

## Autorização e convenções

Envie `Authorization: Bearer <sessão-humana-ou-chave>` em todas as chamadas. Cada operação exige a permissão indicada na referência e acesso ao app. Não há credencial em URL, download público ou wildcard de permissão. Cada mutação exige `Idempotency-Key` UUID v4 por intenção; preserve a chave e o corpo em respostas perdidas. Os recibos duram 24 horas. `If-Match` é obrigatório para commit de importação, alteração/desativação/rotação do webhook e reentrega. Use a revisão consultada imediatamente antes da operação.

As permissões de automação não substituem as permissões do recurso. Lotes exigem `batches:create` e `links:create`, `links:update` ou `links:delete` conforme a ação. Importação exige `imports:create`, `imports:validate`, `imports:commit`, `imports:read` nas respectivas etapas e `links:create` no preflight/commit/execução. Exportação exige `exports:create` e `links:read` ou `analytics:read`; download exige também `exports:read` e a leitura original **atual**. Cadastro/envio de webhook requer `webhooks:create`; leitura, edição, teste, rotação, retry e desativação têm permissões próprias. Habilitar/reautorizar um destino exige `webhooks:create`.

O executor reconstrói o grant atual e verifica sessão/chave, dono, app, estado do job e lease antes de cada efeito. Reduzir o alcance ou revogar uma chave impede os próximos efeitos. Revogação não desfaz itens já concluídos nem bytes já enviados. A origem da assinatura é o segredo do webhook, nunca o bearer da API.

## Lotes

`POST /link-batches` recebe `appId` e até 50 itens. Cada `clientId` deve ser único no lote e usar letras, dígitos, ponto, hífen ou underscore, até 100 caracteres. O limite HTTP continua 32 KiB, incluindo JSON. Um erro no envelope impede o cadastro; erros nos dados de domínio de um item ficam no resultado daquele item.

```json
{
  "appId": "app_loja",
  "items": [
    {"clientId":"novo-1","action":"create","input":{"appId":"app_loja","domain":"go.example.com","slug":"produto","desktopUrl":"https://shop.example.com/produto"}},
    {"clientId":"arquivar-1","action":"archive","linkId":"go.example.com~-~-antigo","revision":"20000000-0000-4000-8000-000000000001","input":{"lifecycle":{"archived":true}}}
  ]
}
```

| Ação | Campos do item | Efeito |
| --- | --- | --- |
| `create` | `input` com LinkCreate e mesmo app | Cria pelo contrato atual de links; domínio deve estar autorizado |
| `update` | `linkId`, `revision`, `input` LinkPatch | Altera apenas campos aceitos pela API de links |
| `move` | `linkId`, `revision`, `input.folderId` UUID ou null | Vincula/desvincula pasta do mesmo app |
| `archive` | `linkId`, `revision`, `input.lifecycle.archived` boolean | Preserva os demais campos do ciclo de vida |
| `delete` | `linkId`, `revision`, sem `input` | Exclui respeitando dependências, QR, histórico e publicação |

Consulte `GET /operations/{id}` e `GET /link-batches/{id}/items?limit=50`. Estados terminais: `succeeded`, `partially_succeeded`, `failed`, `cancelled`. Cada resultado tem `clientId`, `ordinal`, estado, identificador/revisão ou erro sanitizado. Falhas de revisão não são sobrescritas. Um item é confirmado na mesma transação do efeito; retomada não executa novamente itens confirmados. Uma falha transitória desfaz somente a transação atual.

Retente em um **novo lote** apenas os itens falhos que forem corrigidos, preservando seus `clientId` e consultando revisões atuais. Não reenvie itens concluídos com nova intenção: isso poderia gerar colisões. A mesma chave e corpo no endpoint original apenas repetem o recibo.

## Importar CSV ou JSON

1. `POST /imports`: envie `appId`, `format` (`csv` ou `json`) e `content` como texto dentro do JSON. Máximo 24.000 bytes UTF-8 de conteúdo, 50 linhas/objetos e 32 KiB totais. URLs de arquivo não são buscadas.
2. Opcionalmente envie `mapping` de **coluna de origem para campo da API**. Com mapping, colunas não mapeadas são ignoradas; sem mapping, os nomes já devem ser campos aceitos. O app vem do envelope, não de uma coluna.
3. `POST /imports/{id}/validate` com `{}`: normaliza e valida domínio, campos, app, colisões existentes e duplicatas internas. Produz estados `valid`/`invalid`, sem criar links. Repita o preflight se o contexto mudar.
4. Consulte resumo e `GET /imports/{id}/items`. Para relatório completo autenticado, use `?format=csv`, sem cursor/limit.
5. `POST /imports/{id}/commit` com `{}` e `If-Match` da validação: enfileira somente os itens válidos. Não existe commit sem preflight ou quando todos são inválidos. A execução revalida colisões e permissões; o preflight não reserva slugs.

CSV aceita vírgula, CRLF/LF, campos entre aspas, aspas duplicadas e quebras de linha em campos citados; BOM UTF-8 é opcional. Cabeçalhos duplicados, número de colunas inconsistente, UTF-8 malformado, NUL e CSV inválido são rejeitados. `webOnly` aceita `true`, `false` ou vazio no CSV. JSON recebe um array de objetos. `clientId` é opcional; o padrão é `row-1`, `row-2` etc. Demais campos seguem LinkCreate; recursos associados avançados podem usar JSON ou lotes. Não há importação de segredos, permissões ou chaves.

```json
{"appId":"app_loja","format":"csv","mapping":{"dominio":"domain","codigo":"slug","destino":"desktopUrl"},"content":"dominio,codigo,destino\ngo.example.com,produto,https://shop.example.com/produto"}
```

## Exportar e baixar

`POST /exports` recebe `appId`, `dataset` (`links`, `events`, `summary`), `format` (`json`, `csv`) e, para eventos/resumo, `filters.start`, `filters.end` e opcionais `timezone: "UTC"` e `group`. O intervalo inicial é inclusivo e o final exclusivo; limite de 366 dias, conforme analytics.

O job produz arquivo a partir de uma consulta limitada na execução, até 1.000 registros ou 1 MiB. Se ultrapassar, falha explicitamente, sem exportação silenciosamente truncada. `events` usa a projeção sanitizada de analytics e todos os tipos do intervalo, sem IP/hash bruto; `summary` mantém cobertura/limitações e agrega apenas linhas retidas. Campos objetos são serializados como JSON em células CSV.

Consulte `GET /exports/{id}` e, quando pronto, `GET /exports/{id}/download`. O caminho retornado é relativo ao host da API; não contém token. Arquivo disponível por 24 horas após a solicitação, com `Cache-Control: no-store, no-transform`, `Content-Disposition` e `nosniff`. Ausência/expiração/estado indisponível retorna `409 resource_conflict`; após retenção do registro, `404`. Permissão retirada retorna `403`; outra conta/app retorna `404`.

CSV de exportação e relatório recebe BOM, aspas escapadas e neutralização com apóstrofo de células que começariam fórmulas (`=`, `+`, `-`, `@`, inclusive após espaços/controles). Preserve essa proteção ao abrir os arquivos em planilhas.

## Webhooks

Cada app pode registrar até 10 destinos HTTPS públicos IPv4, porta 443. Não são aceitos usuário/senha, fragmento, IP literal, localhost, redes privadas, link-local, documentação ou reservadas. A resolução IPv4 é conferida em **cada tentativa** e o socket TLS conecta ao IP público validado, preservando Host/SNI e validação de certificado. IPv6 exclusivo não é suportado nesta versão. Respostas 3xx não são seguidas. O timeout total da entrega é de 5 segundos; o corpo da resposta é descartado.

O segredo `whsec_…` aparece apenas na criação/rotação. Guarde-o no servidor receptor. Listas, detalhes, auditoria e recibos não o reexibem; replay da emissão retorna `409 secret_not_replayable` e Location do webhook. Os segredos ficam criptografados em AES-256-GCM com chave separada da base, contexto por dono/app/webhook e tag autenticada. Nunca devem ser incluídos em frontend ou logs.

Eventos disponíveis: `link.created`, `link.updated`, `link.deleted`, `domain.status_changed`, `template.publication_succeeded`, `template.publication_failed`, `operation.completed`, `operation.failed`, `export.ready`. A geração ocorre na transação do dado, sem chamada de rede no trigger. Múltiplas alterações confirmadas podem gerar eventos distintos; cliques individuais não geram webhooks.

```json
{"id":"b0000000-0000-4000-8000-000000000001","type":"link.created","schemaVersion":"1","appId":"app_loja","occurredAt":"2026-10-20T01:00:00Z","test":false,"resource":{"id":"go.example.com~-~-produto","revision":"20000000-0000-4000-8000-000000000001"}}
```

Payload mínimo: busque os dados completos pela API com sua própria permissão. Header `Agilink-Event-Id` conserva o evento, `Agilink-Delivery-Id` conserva a entrega. `POST /webhooks/{id}/test` produz `webhook.test` com `test: true`, sem fingir alteração de um recurso.

## Verificar a assinatura no receptor

Header: `Agilink-Signature: t=1792458000,v1=<hex>`. Calcule HMAC-SHA256 usando o segredo **completo**, sobre `timestamp + "." + corpo bruto UTF-8`. Não recodifique JSON antes da verificação. Compare os 32 bytes em tempo constante; aceite timestamp até 300 segundos para mais ou menos e um dos `v1` válidos. Durante rotação pode haver dois valores `v1`, um por segredo, até `overlapSeconds` (0–86.400).

```js
import {createHmac, timingSafeEqual} from 'node:crypto';
export function verify(secret, header, rawBody) {
  const parts = header.split(',');
  const t = parts.find(p => p.startsWith('t='))?.slice(2);
  if (!/^\d{10,12}$/.test(t ?? '') || Math.abs(Date.now()/1000-Number(t)) > 300) return false;
  const expected = createHmac('sha256', secret).update(t+'.').update(rawBody).digest();
  return parts.filter(p => p.startsWith('v1=')).some(p => {
    const actual = Buffer.from(p.slice(3), 'hex');
    return actual.length === expected.length && timingSafeEqual(actual, expected);
  });
}
```

Após verificar, deduplique por `event.id` de modo durável antes de aplicar um efeito; responda 2xx rapidamente e processe trabalho demorado em fila. A entrega é **pelo menos uma vez**, sem ordem global. Se o receptor confirmar e a confirmação se perder, haverá repetição legítima do mesmo evento.

## Retentativas, rotação e desativação

A primeira entrega é assíncrona. Até cinco tentativas automáticas por entrega, com intervalos de 30, 60, 120, 240 segundos entre falhas; leases impedem confirmação de um executor antigo. Agendamento é aproximado, conforme cron/capacidade. Timeout, falha de rede, DNS bloqueado e qualquer resposta fora de 2xx falham; não há log de corpo/header do receptor. Metadados sanitizados ficam por sete dias. Dez falhas consecutivas do destino suspendem novos envios; uma entrega 2xx zera o contador.

`GET /webhooks/{id}/deliveries` e `GET /webhook-deliveries/{id}` permitem diagnóstico com estado, status HTTP, erro, tentativas e próxima tentativa. `POST /webhook-deliveries/{id}/retry` com `{}` e If-Match reinicia uma entrega terminal ainda retida, mantém eventId e exige destino habilitado e autorização atual. Não repita envios em andamento.

Alterar configuração, desativar ou rotacionar muda a revisão do destino: entregas pendentes da revisão anterior são canceladas. A reentrega explícita vincula o evento à revisão nova. Isso evita encaminhar conteúdo antigo automaticamente para um novo endereço. Rotação aceita sobreposição para receptores em atualização. DELETE desativa e preserva histórico; PATCH `enabled: true` reautoriza com o ator atual. Expirar/revogar a sessão/chave que autorizou o destino impede novos envios até reautorização; não transfere acesso para outra credencial.

## Limites e recuperação

Até cinco operações ativas e 100 registros de automação retidos por app; lotes/importações com 50 itens e corpo HTTP de 32 KiB. Um ciclo processa até dez itens e um webhook, respeitando quotas de credencial, conta e app. O executor reenvia à fila o progresso confirmado sem consumir tentativas de recuperação; falhas sem progresso usam leases/backoff e falham após cinco recuperações. Cancelamento interrompe novos itens assim que observado; não desfaz itens confirmados.

A manutenção remove arquivos expirados, segredos anteriores fora da sobreposição e registros/entregas vencidos. Os registros de jobs/auditoria seguem a política da fundação. Backup dos metadados inclui ciphertext; restaurar webhooks exige também a chave externa preservada, que não integra o snapshot de dados. O lançamento foi validado com dados sintéticos em produção; o ensaio de carga foi curto e isolado, sem promessa de capacidade sustentada.
