Endpoint para agentes

Conecte Eventos duráveis da BotoZap ao seu runtime de agente e responda ao Contato sem consultar mensagens em loop.

O exemplo oficial é um projeto Node.js executável no repositório público botozap-js/examples/agent-endpoint. Ele usa apenas o SDK e a API públicos: a BotoZap continua sendo o canal oficial e o modelo/runtime do agente continua sendo seu.

Fluxo orientado a Eventos

Meta → BotoZap (inbox/outbox) → seu Endpoint
                                  │
                            INSERT + NOTIFY
                                  │
                            ACK 2xx rápido
                                  ▼
                         worker do agente
                                  │
                       POST /v1/messages → Meta

O Endpoint valida o HMAC sobre os bytes crus, grava o Evento numa fila PostgreSQL e só então confirma com 2xx. O ACK não espera o agente nem a Meta responder. Depois do commit, LISTEN/NOTIFY acorda o worker; a linha durável continua sendo a fonte da verdade caso um processo reinicie.

A coluna idempotency_key é única. Retry ou replay com a mesma X-Idempotency-Key encontra o job existente e não produz outra mensagem outbound. Veja também Confiabilidade e entrega.

Rodar o exemplo

git clone https://github.com/bytecraft-fernando/botozap-js.git
cd botozap-js
pnpm install

export DATABASE_URL='postgresql://app:senha@localhost:5432/app'
export BOTOZAP_API_KEY='bz_live_xxx'
export BOTOZAP_WEBHOOK_SECRET='whsec_xxx'
export BOTOZAP_FALLBACK_TEMPLATE='retomar_atendimento'

pnpm --filter example-agent-endpoint start

Para produção, rode start:endpoint e start:worker como processos separados. Ambos usam o mesmo PostgreSQL; não existe fila em memória entre eles. Publique a rota https://seu-dominio.example/webhooks/botozap. Um túnel é útil no desenvolvimento, mas não deve virar Endpoint permanente.

Sem configuração de modelo, a resposta local é determinística. Com AGENT_ENDPOINT_URL, o worker envia { input, event } ao seu runtime e espera { reply }. O README do exemplo documenta o contrato.

Criar o Endpoint

Cadastre a URL em Webhooks no painel ou pela API. A resposta 201 é a única leitura que contém o secret; guarde-o em BOTOZAP_WEBHOOK_SECRET.

curl -sS https://botozap.com.br/api/v1/webhooks \
  -H "Authorization: Bearer $BOTOZAP_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://seu-dominio.example/webhooks/botozap",
    "events": ["messages"],
    "active": true
  }'

Testar sem enviar WhatsApp

curl -sS -X POST \
  https://botozap.com.br/api/v1/webhooks/$ENDPOINT_ID/test \
  -H "Authorization: Bearer $BOTOZAP_API_KEY"

O teste usa os mesmos headers, assinatura e caminho HTTP de uma Entrega real. O exemplo persiste webhook.test, confirma o ACK e o marca como ignored; só whatsapp.message.received com texto aciona uma resposta.

Janela de 24h e Template

O worker usa o timestamp inbound com margem de segurança: envia texto livre dentro da janela e o Template aprovado configurado em BOTOZAP_FALLBACK_TEMPLATE fora dela. A API BotoZap ainda é a autoridade. Se a janela fechar entre a decisão e o envio, ela responde outside_window antes de chamar a Meta e o adapter troca para Template sem gerar duas mensagens.

Antes do I/O outbound, o job muda para sending. Esse estado nunca é retomado automaticamente: uma queda nesse ponto pode exigir inspeção humana, mas não dispara uma resposta duplicada para o Contato.

Observar Entregas

curl -sS \
  "https://botozap.com.br/api/v1/webhook_deliveries?webhook_id=$ENDPOINT_ID" \
  -H "Authorization: Bearer $BOTOZAP_API_KEY"

A listagem mostra status, response_code, número de tentativas e próxima retentativa. No banco do exemplo, consulte agent_jobs sem imprimir payload para observar os estados sem expor a conversa.

Replay

Depois de corrigir um Endpoint cuja Entrega chegou a exhausted, um owner/admin pode usar Operação → Replay. A plataforma reenvia o mesmo corpo com a mesma chave. O exemplo responde 200 duplicate e não cria outro job. A Entrega permanece observável e o replay é auditado.

Se a Entrega teve sucesso, mas o runtime do agente esgotou três tentativas, o estado failed é a DLQ local. Rode pnpm --filter example-agent-endpoint replay -- <job-id>para fazer failed → queued e acordar o worker. O comando recusa qualquer job que não esteja em failed.

Não retente automaticamente jobs locais em sending ou ambiguous: a Meta não fornece uma chave de idempotência no envio e uma tentativa anterior pode ter sido aceita.

Endpoint BotoZap não é receiver Meta

Endpoint do agenteReceiver Meta da BotoZap
Quem chamaBotoZapMeta
HeaderX-Webhook-SignatureX-Hub-Signature-256
Segredowhsec_…App Secret da Meta
Formato HMAChex purosha256=<hex>

Não aponte o callback do app Meta para este exemplo. A BotoZap já opera esse receiver, persiste o envelope cru antes do 200 e cria a Entrega durável que chega ao seu Endpoint.

Para detalhes do header e do payload, continue em Webhooks.