# Entrega autenticada opcional de eventos Meta

Versão: 2026-10-24. Produto: HookCloud Partner. Referência online: https://developers.hookcloud.app/authenticated-webhooks.html

## Escolha o transporte por integração

O modo direto continua sendo o padrão: a Meta entrega os eventos elegíveis ao callback aplicado ao número. Contratar um pacote, conectar um número ou salvar um webhook global não habilita automaticamente a entrega autenticada.

No serviço opcional, a HookCloud valida a assinatura original da Meta antes de processar o corpo, confere o vínculo da integração, armazena temporariamente o envelope cifrado e entrega os mesmos bytes ao receptor com uma assinatura exclusiva. Contratação com pagamento confirmado (ou concessão interna expressa) e preparação do receptor são obrigatórias. O Meta Access Token da instância continua disponível para chamadas diretas à Graph API; este serviço não transforma a HookCloud em uma API de envio de mensagens.

## Contratação, franquia e pagamento

**Webhook Autenticado + Fila custa R$ 99,90 por mês por conta Partner**, com 100.000 unidades por ciclo mensal. O excedente custa R$ 0,90 a cada 1.000 unidades, proporcionalmente: 1.500 unidades excedentes custam R$ 1,35. A cobrança não arredonda o consumo para blocos inteiros de mil. As tarifas de mensagens da Meta são separadas.

A franquia é compartilhada entre todas as integrações autenticadas da conta, dentro da capacidade de números já contratada. Não há uma mensalidade deste adicional por número. Não há trial nem acúmulo de unidades não utilizadas. A primeira mensalidade é antecipada e o serviço só é liberado após confirmação do pagamento pela Stripe; o excedente é apurado após o ciclo. O mês comercial segue o início e o fim retornados em `billing.period`, e não necessariamente o primeiro dia do mês do calendário.

No Portal, o **owner** acessa **Endpoints → Webhook Autenticado + Fila → Contratar**. A tela informa preço, franquia e excedente e pede concordância antes de abrir o checkout da Stripe. Voltar do checkout não confirma pagamento. O Portal consulta o estado no servidor, mostra a contratação confirmada e permite preparar e ativar individualmente as instâncias. O Admin apenas acompanha o estado comercial; uma habilitação técnica não substitui uma contratação paga. Contas internas de teste identificadas como `internal` são exceções expressas, sem cobrança automática.

O aceite inclui as [Condições específicas do adicional, versão 2026-10-25](https://partner-politica-de-privacidade.hookcloud.app/webhook-autenticado/2026-10-25/). O checkout registra responsável, conta, versão, data e condições aceitas. A versão é um documento conservado em URL própria, não uma autorização para mudar retroativamente o preço contratado.

A barra mostra unidades consumidas e restantes, excedente estimado, período contratado e data da medição. Há avisos a partir de 80% e 100%. A barra é informativa: atingir a franquia inicia o excedente, não interrompe automaticamente as entregas. Os dados são coletados periodicamente, sem promessa de atualização instantânea. Confira `usage_updated_at` e `summary_partial`; integrações sem coleta não equivalem a consumo zero. A fatura final pode incluir admissões coletadas após sua última consulta.

| Resposta de `get-authenticated-webhook-billing` | Significado |
| --- | --- |
| `billing.status` | `not_subscribed` sem contratação; `pending_payment` aguardando pagamento; `active` contratado; `past_due` pagamento pendente; `canceled` encerrado; `internal` piloto interno sem cobrança. |
| `billing.enabled` / `checkout_available` | Permissão comercial atual e disponibilidade de iniciar checkout. Não substituem as verificações técnicas de cada integração. |
| `billing.base_amount_cents` / `currency` | Mensalidade em centavos e moeda. R$ 99,90 é `9990`, moeda `brl`. |
| `billing.included_units` / `unit_bytes` | Franquia por ciclo e tamanho do bloco: `100000` unidades e `65536` bytes. |
| `billing.excess_per_1000_cents` | Valor do excedente: `90` centavos por mil unidades, aplicado proporcionalmente. |
| `billing.period.id`, `start`, `end` | Identificador e limites exatos do ciclo contratado. Não derive o ciclo comercial das datas do relatório técnico diário. |
| `billing.period.used_units`, `remaining_units`, `excess_units` | Unidades do ciclo, saldo de franquia e excedente já observado. |
| `billing.period.estimated_excess_cents` / `percent_used` | Estimativa financeira e percentual consumido; a coleta final pode alterar o total. |
| `billing.cancel_at_period_end` | Renovação cancelada para o fim do período; não cancela pacotes de números. |
| `billing.breakdown[]` / `breakdown_limit` | Atribuição de unidades por `customer_id` e `instance_id`; esta consulta mostra até 100 grupos. Os totais do ciclo consideram todos os grupos. O filtro local do Portal pesquisa apenas os grupos exibidos. |
| `billing.usage_updated_at` / `summary_partial` | Data da medição e indicação de cobertura parcial. Não trate dados ausentes como zero. |
| `billing.renewal_processing` / `renewal_processing_grace_until` | Conciliação da renovação de um ciclo anteriormente pago e seu limite de continuidade temporária, de até 24 horas. Não se aplica à primeira ativação nem à renovação cancelada e não se estende por novas tentativas. |
| `billing.billing_attention` | Conferência interna do fechamento: `requires_operator_review` indica atenção da equipe; `export_count` e `invoice_count` contam registros de consumo e faturas a conferir. `codes` contém somente `relay_billing_export_uncertain` ou `relay_billing_invoice_requires_review`. Não significa pendência do cliente ou da Meta e não altera, por si só, `enabled`. |

Se `billing_attention.requires_operator_review` vier como `true`, apresente: “Estamos conferindo o fechamento do consumo. Não é necessário contratar novamente; o suporte pode acompanhar esta conferência.” Continue respeitando `billing.enabled` e o estado técnico da integração. Não gere outro checkout, não repita uma cobrança e não mude o transporte para entrega sem assinatura por causa desse aviso. O suporte confere o resultado financeiro antes de qualquer repetição.

O GET comercial pode ser consultado com `webhook.read` ou pela sessão autorizada do Portal. **Checkout e cancelamento exigem sessão JWT do owner; a Partner API Key não autoriza compras.** `create-authenticated-webhook-checkout` exige `terms_accepted: true` e `terms_version: '2026-10-25'` e devolve `checkout_url`. `cancel-authenticated-webhook-subscription` recebe `cancel_at_period_end: true`; envie `false` para manter a renovação enquanto isso ainda for permitido. Todas as mutações exigem `Idempotency-Key` e não devem ser repetidas cegamente após timeout.

Somente na renovação de um ciclo antes pago, pode existir continuidade temporária de até 24 horas para processamento da cobrança, com limite fixo devolvido pelo servidor. Não é teste gratuito nem prazo que se renova a cada tentativa. A primeira ativação continua exigindo pagamento confirmado e a renovação já cancelada não recebe essa continuidade. O consumo admitido no intervalo integra a apuração do ciclo correspondente. Depois do limite, a falta de confirmação pode suspender novas admissões; não transforme a suspensão em entrega sem assinatura. Eventos já aceitos permanecem sujeitos à fila e aos limites técnicos.

Cancelar a renovação encerra as renovações deste adicional, preserva o período já contratado e não elimina excedentes consumidos. Não cancela os pacotes de números nem revoga o token Meta. O retorno ao modo direto é uma decisão explícita e exige drenagem segura em cada integração. Falha de cobrança ou fim da contratação nunca significam autorização para enviar automaticamente POSTs sem assinatura. Mantenha o receptor enquanto houver eventos pendentes e confira o estado retornado antes de alterar sua infraestrutura.

## O que conta como unidade

Uma unidade corresponde a um **webhook único, autenticado e admitido de forma durável**, com até 64 KiB (65.536 bytes originais). Acima disso, cada bloco adicional iniciado de 64 KiB é uma unidade: um corpo de 65.537 bytes conta duas. O cálculo é feito individualmente na admissão de cada envelope: `ceil(bytes_originais / 65536)`. Não calcule unidades dividindo a soma mensal de bytes, pois isso produz outro resultado.

Uma unidade não é uma mensagem WhatsApp: o envelope pode conter uma mensagem, várias atualizações de status ou lotes de histórico. Duplicidades reconhecidas e tentativas adicionais de uma mesma entrega não contam novamente. Requisições inválidas ou recusadas antes da admissão não contam. Eventos aceitos contam mesmo quando o receptor fica indisponível e o prazo ou o limite de tentativas termina. Consumo anterior à contratação não é cobrado retroativamente.

Há três contratos diferentes de POST:

| Fluxo | Identificação | Verificador |
| --- | --- | --- |
| Meta direta | `object`, `entry`, `changes`; `X-Hub-Signature-256` | App Secret do aplicativo emissor. Não é o Meta Access Token. O App Secret da HookCloud não é compartilhado. |
| Meta pela entrega autenticada opcional | Mesmo envelope Meta, `X-HookCloud-Delivery-Id`, `X-HookCloud-Integration-Id`, `X-HookCloud-Key-Id`, `X-HookCloud-Timestamp`, `X-HookCloud-Signature` | Segredo exclusivo da integração e contrato do verificador fornecido nesta referência. |
| Eventos de aplicação HookCloud | `X-HookCloud-Event`, `X-HookCloud-Delivery`, assinatura `sha256=...` | Segredo do endpoint dos eventos de aplicação; cálculo `timestamp + '.' + corpo_bruto`. Não usar o segredo do transporte Meta. |

Não escolha um verificador apenas pela presença de `X-HookCloud-Signature`: os dois contratos HookCloud usam formatos e credenciais diferentes. Não faça fallback para aceitar o mesmo POST sem autenticação quando a assinatura do modo esperado falhar. A configuração no servidor deve estabelecer qual contrato e integração são aceitos em cada rota.

## Preparar e ativar

1. Configure a rota HTTPS final do seu receptor, com certificado válido, sem redirecionamento. A HookCloud bloqueia destinos privados e não autorizados; não use URLs com credenciais ou segredos na query string.
2. Implemente o GET de verificação no padrão Meta. Compare `hub.verify_token` com o token do seu servidor e, para `hub.mode=subscribe`, devolva somente `hub.challenge` em HTTP 200. O GET confirma o destino; não autentica POSTs.
3. Consulte `get-authenticated-webhook-service`. O retorno informa a habilitação da sua conta, integrações e limites. A autorização real é feita no backend.
4. Chame `provision-authenticated-webhook` para uma instância sua, informando o destino validável e seu verify token. Guarde o `integration_id` retornado. O callback atual ainda não é substituído pela preparação.
5. A resposta de preparação apresenta o segredo uma única vez. Salve `signing_secret` e `key_id` no gerenciador de segredos do seu backend, associados à integração correta. Não registre resposta integral, token ou corpo em logs/APM. Não coloque o segredo na IA, no front-end, em URLs ou no repositório.
6. Configure o receptor com o verificador do exemplo. O receptor precisa aceitar o corpo bruto, conferir timestamp/assinatura/integração, deduplicar e persistir antes do 2xx.
7. Somente após configurar o receptor, chame `activate-authenticated-webhook` com `receiver_ready: true`. Esse campo é uma confirmação explícita, não um substituto da implementação.
8. Consulte o estado. Uma resposta de preparação ou início de ativação não prova que a mudança de callback terminou. Se houver resposta ambígua, use `reconcile-authenticated-webhook` e leia o estado; não envie outra mutação com outra chave.

As mutações exigem `Idempotency-Key`. Use um UUID por intenção, guarde a intenção no seu backend e não reutilize a chave com outro corpo. Uma rede interrompida após POST pode esconder um sucesso remoto; não repita automaticamente. O retorno de uma consulta posterior não revela novamente o segredo.

As três consultas GET — `get-authenticated-webhook-service`, `get-authenticated-webhook-usage` e `get-authenticated-webhook-billing` — exigem escopo `webhook.read` quando feitas com Partner API Key. As mutações técnicas de preparação, ativação, reconciliação, drenagem e rotação exigem `webhook.write`. Essas consultas e mutações técnicas também aceitam sessão JWT de membro owner/admin. A exceção são as operações comerciais `create-authenticated-webhook-checkout` e `cancel-authenticated-webhook-subscription`: elas exigem sessão JWT do owner e não aceitam Partner API Key. As operações administrativas de habilitação não fazem parte da API do assinante. Na preparação, você pode informar uma URL HTTPS própria com seu token de verificação, ou selecionar um `endpoint_id` cadastrado do mesmo parceiro. Se enviar também uma URL com esse ID, ela precisa coincidir com o destino cadastrado.

No Portal: **Endpoints → Webhook Autenticado + Fila**. Depois da contratação confirmada, prepare, guarde o segredo, configure o servidor e ative individualmente a instância. Contas sem habilitação continuam no modo direto. Preparar uma instância não inicia por si só uma nova compra: contratação e ativação técnica são etapas diferentes.

## Validar o POST e persistir

Use o exemplo de receptor Node.js publicado junto desta referência e valide sua implementação conforme a seção “Critérios de teste da sua implementação” deste guia. A assinatura `v1=HEX` usa HMAC-SHA256 com o segredo de 32 bytes decodificado de hexadecimal, sobre o prefixo UTF-8 `v1\n{delivery_id}\n{timestamp}\n{integration_id}\n` seguido dos bytes originais do corpo. Não altere delimitadores, ordem ou codificação. O `X-HookCloud-Key-Id` seleciona uma das chaves configuradas para a mesma integração; nunca busque segredos arbitrários apenas pelo header recebido.

- Capture o corpo antes de qualquer parser JSON. Em Express, monte `express.raw({ type: 'application/json', limit: '2mb', inflate: false })` na rota antes de `express.json()`.
- Não use `JSON.stringify(req.body)` para tentar reconstruir os bytes assinados.
- Limite o corpo recebido. O limite de envelope do serviço é 2 MiB; payloads maiores são recusados antes da aceitação. Isso é diferente do limite total da fila.
- Exija exatamente um valor para cada cabeçalho de assinatura. Rejeite headers duplicados ou malformados.
- Compare `integration_id` com a configuração privada do servidor; não use um ID enviado no corpo para escolher o segredo de outro cliente.
- Verifique o timestamp em segundos com tolerância máxima de cinco minutos, mantendo o relógio do servidor sincronizado. O timestamp é da tentativa atual, não da criação da mensagem. Reentregas podem trazer timestamp/assinatura novos para o mesmo delivery ID.
- Confira o HMAC em tempo constante antes de interpretar o JSON ou produzir efeitos.
- Verifique também os IDs esperados de WABA/número/geração no contexto mantido pelo backend. Um payload válido não pode atualizar outra empresa do seu SaaS.
- Em uma única transação durável, reserve a chave `(integration_id, delivery_id)` e grave o trabalho/efeito. Retorne 2xx somente após commit. Um registro ainda pendente não é duplicata concluída.
- Deduplique também o evento de negócio: mensagens pelo identificador WhatsApp e status pelo identificador/estado do fato. Um evento lógico pode vir reagrupado pela Meta em outro envelope, com outro delivery ID.
- Em falha de persistência, devolva erro para permitir recuperação. Não responda sucesso e depois descarte o corpo, nem espere o chatbot/IA terminar para confirmar.

O serviço não oferece execução exatamente uma vez nem ordem global. A fila do receptor pode ser uma tabela transacional com processamento de jobs; não exige um produto externo específico. Ela continua sendo necessária para separar a confirmação HTTP do processamento de atendimento, IA, mídias e campanhas.

## Fila, recuperação e limites

O prazo máximo é de até 24 horas desde a primeira aceitação durável, limitado também a até 32 tentativas. Repetições não reiniciam esse prazo. As esperas crescem com backoff e pequena variação aleatória; não assuma horários exatos. O prazo pode vencer antes da tentativa 32.

Leia a capacidade efetiva retornada para sua integração. Há limite de eventos pendentes e bytes cifrados. **24 horas não significam capacidade ilimitada para todo o tráfego de um dia.** Ao atingir capacidade antes de aceitar um novo envelope, a HookCloud retorna falha transitória à origem em vez de declarar que armazenou algo que não armazenou.

O corpo fica cifrado na fila temporária. Após confirmação 2xx do receptor, é removido da fila ativa. No esgotamento ou encerramento autorizado, o corpo também é removido e permanece somente o resultado técnico. Recibos sem conteúdo podem permanecer por sete dias para deduplicação.

**As 24 horas são o limite de retenção operacional da aplicação, não uma garantia de apagamento físico de todas as cópias de infraestrutura nesse prazo.** A infraestrutura SQLite de Durable Objects oferece recuperação de versões anteriores por até 30 dias, conforme a [documentação oficial da Cloudflare sobre recuperação no tempo (PITR)](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/). Assim, versões cifradas anteriores podem permanecer nesse ciclo técnico de recuperação, mesmo após remoção da fila ativa. Elas não formam um histórico de mensagens consultável pelo Portal ou pela API do Partner.

Não existe replay de corpos já removidos. Seu SaaS é responsável por histórico, retenção de conversas, backups e política de tratamento dos dados. Não derive janelas de atendimento ou aprovação de templates do tempo de retenção da fila.

## Trocar segredo e retornar ao modo direto

A rotação tem duas etapas: `rotate-authenticated-webhook-secret` com `phase: 'prepare'` gera uma nova chave exibida uma única vez; salve o `rotation_id`. Configure a nova chave no receptor mantendo a anterior. Depois confirme com `phase: 'confirm'` e o identificador da rotação. Observe o `key_id` e a expiração da chave anterior retornados pelo serviço. Não remova a chave anterior antes do prazo de transição e não registre os segredos.

A preparação expira após 24 horas ou quando substituída por outra preparação. Depois da confirmação, mantenha a chave anterior por até 60 minutos, conforme `accept_previous_until`. O serviço bloqueia outra rotação enquanto essa transição estiver ativa para não invalidar uma chave ainda necessária.

Para cancelar o transporte opcional, use `drain-authenticated-webhook`. A HookCloud coordena a restauração do callback direto verificado e a conclusão das entregas já aceitas no destino original. Acompanhe os estados em `get-authenticated-webhook-service` e use reconciliação quando indicada. Enquanto houver fila anterior, mantenha o receptor e a verificação de assinatura funcionando. Não troque uma flag local para fingir que a drenagem acabou.

A mudança de configuração pode levar até 15 segundos para se propagar, e uma requisição HTTP já iniciada pode ainda chegar ao receptor. Depois de restaurar o destino direto, há uma margem mínima de 30 segundos antes de declarar o serviço encerrado, além do término da fila anterior. Por isso, `draining` pode continuar mesmo com `pending: 0`. Cancelar ou revogar não desfaz um POST já enviado; confirme o estado final antes de desligar o receptor.

Cancelar este recurso não desconecta o WhatsApp, não revoga o Meta Access Token e não cancela a assinatura do cliente. Revogação de segurança ou perda de propriedade pode interromper entregas ao vínculo revogado; esse caso é diferente do cancelamento planejado. Corpos pendentes nunca devem ser enviados automaticamente a um novo proprietário ou outro cliente.

## Consumo por cliente e período

Consulte `get-authenticated-webhook-usage` com `from` e `to` em datas UTC, ambas inclusivas, em intervalos de até 31 dias. Por exemplo, `2026-10-01` até `2026-10-31` cobre outubro. Não consulte datas futuras. Filtre por `customer_id` ou `instance_id` quando necessário e percorra `limit`/`offset`; a resposta informa `pagination.total`. As consultas não chamam a Meta.

Interprete os campos separadamente:

| Campo | Significado |
| --- | --- |
| `ingested_unique` | Envelopes efetivamente admitidos uma vez na fila. Não é quantidade de mensagens WhatsApp. |
| `payload_bytes` | Bytes brutos admitidos uma vez, antes da criptografia. |
| `duplicates` | Repetições reconhecidas; não são novas admissões. |
| `delivery_attempts` | Tentativas assumidas pelo processador; podem incluir falha anterior ao HTTP. |
| `retry_attempts` | Tentativas adicionais do mesmo envelope. |
| `delivered` | Entregas confirmadas com 2xx. Não prova que a IA respondeu ou que o software processou corretamente depois. |
| `expired` / `failed` | Encerramento pelo prazo ou pelo limite de tentativas. |
| `connection_closed` / `configuration_changed` / `operator_canceled` | Encerramentos por vínculo, configuração ou cancelamento; não são entregas. |
| `queue_rejected` | Recusas de capacidade antes da aceitação. |

O relatório técnico usa `period_basis: accepted_utc_day`: resultados e retentativas continuam atribuídos ao dia UTC da admissão, mesmo quando ocorrem depois. Uma entrega admitida em outubro e concluída em novembro atualiza o registro de outubro; não crie outra admissão. `collection: periodic` e `last_collected_at` indicam que a leitura é uma fotografia periódica. Pendências e bytes ativos no status são um retrato da fila. Confira início da medição, cobertura e data de atualização. Ausência de observação não significa consumo zero. Não trate um contador cumulativo como fatura fechada. Para franquia e excedente comercial use `get-authenticated-webhook-billing`, com o ciclo exato; não some dias UTC como se os limites comerciais sempre fossem meia-noite.

`coverage: post_upgrade_admissions_only` informa que os registros abrangem somente envelopes admitidos após o início da medição atual da integração, exposto como `usage_collection_started_at` na consulta do serviço. O tráfego antigo não é reconstruído a partir de conversas nem de recibos expirados. Uma ausência de registro anterior a esse início não representa zero consumo histórico.

A atribuição segue parceiro → cliente → instância → integração → geração. Ela preserva o cliente da admissão; mover a instância depois não transfere o consumo anterior. O parceiro pode usar os agregados para criar sua própria política de cobrança por cliente no seu sistema, com preço, contrato e transparência próprios. A API não cobra o cliente final nem determina valor de revenda.

## Critérios de teste da sua implementação

Teste dois clientes separados; corpo adulterado; assinatura inválida; timestamp vencido; chave errada; duplicidade simultânea; mesmo evento em outro envelope; indisponibilidade do banco; 503/timeout do receptor; retomada após falha; transição de chave; cancelamento com fila pendente; reconexão com nova geração; consulta indevida ao cliente de outro parceiro; virada de período. Nunca envie mensagens reais nem altere callbacks de terceiros sem autorização.

Referências: [configuração inicial do webhook](https://developers.hookcloud.app/webhook-setup.html), [eventos de aplicação HookCloud](https://developers.hookcloud.app/webhooks.html), [campos Meta](https://developers.hookcloud.app/meta-app-fields.html), [referência oficial Meta](https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/overview/).
