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
| Evento | Disparado quando |
|---|---|
job.succeeded | Uma 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.failed | As mesmas tarefas falharam, foram canceladas ou foram encerradas pelo monitor de tarefas travadas. |
book.published | Um livro foi publicado, de qualquer lugar (estúdio, Copiloto, REST, MCP). Exatamente um evento por publicação. |
book.unpublished | Um 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çalho | Valor |
|---|---|
anthra-signature | t=<unix seconds>,v1=<hex HMAC-SHA256> |
anthra-webhook-id | O id do endpoint para o qual a entrega foi enviada. |
user-agent | Anthra-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:
| Tentativa | Enviada |
|---|---|
| 1 | Imediatamente |
| 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
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
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
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
Envie qualquer subconjunto de:
| Campo | Tipo | Efeito |
|---|---|---|
url | string | Nova URL de entrega (mesma verificação de endereço público do registro). |
events | string[] | Substitui a lista de assinaturas. |
description | string | null | Rótulo de texto livre. |
active | boolean | true reativa um endpoint desativado e zera a contagem de falhas; false pausa as entregas. |
rotateSecret | boolean | Gera 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
Remove o registro do endpoint. As entregas e as novas tentativas pendentes param na hora.