Webhooks

Em vez de consultar /v1/jobs repetidamente, registre um endpoint HTTPS e a Anthra envia a ele um POST com um evento JSON assinado quando uma tarefa em segundo plano termina ou um livro é publicado. Os endpoints também podem ser gerenciados em Conta → Webhooks ou via MCP (create_webhook, list_webhooks, delete_webhook, ping_webhook).

Os webhooks cobrem todos os livros da conta, então os endpoints abaixo recusam chaves de API restritas a livros com 403 forbidden.

Eventos

EventoDisparado quando
job.succeededUma tarefa em segundo plano terminou: geração do livro inteiro (kind: "generateBook"), tarefas de narração e exportação e renderizações em PDF (kind: "productionRender").
job.failedAs mesmas tarefas falharam, foram canceladas ou foram encerradas pelo monitor de tarefas travadas.
book.publishedUm livro foi publicado, de qualquer lugar (estúdio, Copiloto, REST, MCP). Exatamente um evento por publicação.
book.unpublishedUm livro publicado foi tirado do ar.
*Assina todos os eventos, inclusive os que forem adicionados depois.

Payload

Toda entrega é um POST com content-type: application/json e este envelope. O id é o mesmo em todas as novas tentativas do mesmo evento — use-o para eliminar duplicatas.

{
  "id": "evt_Qm9yZ2V0LW1lLW5vdA",
  "event": "job.succeeded",
  "createdAt": "2026-11-02T09:14:03.000Z",
  "data": {
    "kind": "productionRender",
    "jobId": "6c1e…",
    "bookId": "8f1c…",
    "format": "pdf",
    "status": "succeeded",
    "resultUrl": "https://…/book.pdf",
    "error": null
  }
}

book.published traz { bookId, title, slug, visibility, readerUrl }; book.unpublished traz { bookId, title }. Eventos de tarefa trazem kind, bookId, status e error, além de campos específicos do tipo.

Cabeçalhos

CabeçalhoValor
anthra-signaturet=<unix seconds>,v1=<hex HMAC-SHA256>
anthra-webhook-idO id do endpoint para o qual a entrega foi enviada.
user-agentAnthra-Webhooks/1.0

Verifique a assinatura

A assinatura é HMAC-SHA256(secret, "<t>.<raw body>") em hex, onde secret é o valor whsec_… retornado quando você criou o endpoint (ou na última rotação do segredo). O timestamp faz parte do material assinado, então rejeite entregas cujo t esteja a mais de cinco minutos do seu relógio — isso impede que uma entrega capturada seja reenviada. Sempre verifique sobre os bytes brutos da requisição, antes de qualquer parsing de JSON, e compare em tempo constante.

import { createHmac, timingSafeEqual } from 'node:crypto'

export function verifyAnthra(rawBody, header, secret, toleranceSecs = 300) {
  const parts = Object.fromEntries(header.split(',').map((kv) => kv.split('=')))
  const t = Number(parts.t)
  if (!Number.isFinite(t) || !parts.v1) return false
  if (Math.abs(Date.now() / 1000 - t) > toleranceSecs) return false
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest()
  const given = Buffer.from(parts.v1, 'hex')
  return given.length === expected.length && timingSafeEqual(given, expected)
}
import hashlib, hmac, time

def verify_anthra(raw_body: bytes, header: str, secret: str, tolerance=300) -> bool:
    parts = dict(kv.split("=", 1) for kv in header.split(","))
    t, v1 = int(parts.get("t", 0)), parts.get("v1", "")
    if abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)

Novas tentativas e desativação automática

Responda com qualquer 2xx em até 8 segundos para confirmar uma entrega. Qualquer outra coisa — status diferente de 2xx, tempo esgotado, erro de conexão — conta como falha, e o mesmo corpo é reenviado (com novos t e assinatura) neste cronograma:

TentativaEnviada
1Imediatamente
2~1 minuto depois que a tentativa 1 falha
3~5 minutos depois
4~30 minutos depois
5~2 horas depois
6~8 horas depois (última tentativa)

As novas tentativas são processadas a cada cinco minutos, então espere alguns minutos de variação. Depois de 10 tentativas seguidas com falha (somando todos os eventos), o endpoint é desativado: active passa a false, disabledAt é definido e as tentativas pendentes são descartadas. Corrija o receptor e reative-o com PATCH /v1/webhooks/{webhookId} e { "active": true }, o que também zera a sequência de falhas. As entregas nunca vão para endereços privados, de loopback ou link-local, e redirecionamentos não são seguidos.

Listar endpoints

GET/v1/webhooksescopo: books:read

Retorna { webhooks, availableEvents }. Cada webhook traz id, url, events, description, active, lastDeliveryAt, lastStatus, lastError, failureCount e disabledAt. O segredo de assinatura nunca é retornado aqui.

Registrar um endpoint

POST/v1/webhooksescopo: books:write

Corpo: url (uma URL http(s) acessível publicamente), events (nomes da tabela acima, ou ["*"]) e um description opcional. A resposta inclui o secret de assinatura — guarde-o agora, ele não é retornado de novo.

curl -X POST "$ANTHRA_BASE/webhooks" \
  -H "authorization: Bearer $ANTHRA_TOKEN" \
  -H "content-type: application/json" \
  -d '{ "url": "https://example.com/anthra", "events": ["job.succeeded", "job.failed", "book.published"] }'

{
  "data": {
    "webhook": { "id": "3d0f…", "url": "https://example.com/anthra", "active": true, … },
    "secret": "whsec_…"
  },
  "meta": { "warning": "Store this secret now — it is not returned again." }
}

Ler um endpoint e seu registro de entregas

GET/v1/webhooks/{webhookId}escopo: books:read

Retorna o endpoint e suas tentativas de entrega mais recentes, das mais novas para as mais antigas (?limit= 1–100, padrão 20). Cada tentativa tem eventId, event, attempt, status (pending | succeeded | failed), statusCode, latencyMs, error, nextRetryAt (quando outra tentativa está agendada) e createdAt. O registro guarda 30 dias.

Atualizar, reativar ou trocar o segredo

PATCH/v1/webhooks/{webhookId}escopo: books:write

Envie qualquer subconjunto de:

CampoTipoEfeito
urlstringNova URL de entrega (mesma verificação de endereço público do registro).
eventsstring[]Substitui a lista de assinaturas.
descriptionstring | nullRótulo de texto livre.
activebooleantrue reativa um endpoint desativado e zera a contagem de falhas; false pausa as entregas.
rotateSecretbooleanGera um novo segredo de assinatura, retornado uma única vez como secret. O segredo antigo deixa de valer imediatamente, inclusive para novas tentativas já agendadas.
curl -X PATCH "$ANTHRA_BASE/webhooks/$WEBHOOK" \
  -H "authorization: Bearer $ANTHRA_TOKEN" \
  -H "content-type: application/json" \
  -d '{ "active": true, "rotateSecret": true }'

Excluir um endpoint

DELETE/v1/webhooks/{webhookId}escopo: books:write

Remove o registro do endpoint. As entregas e as novas tentativas pendentes param na hora.