API da Gaio

Webhooks

Receba no seu sistema, na hora, o que acontece na conta: mensagens, comentários, contatos, execuções de automações e conexões, sem consultar a API.

Nesta página

Como funciona

Um webhook é um endereço do seu sistema, o endpoint, que a Gaio chama quando algo acontece na conta: chega uma mensagem, um contato ganha uma etiqueta, uma automação termina. A Gaio envia um POST com o evento em JSON, assinado com um segredo que só a Gaio e o seu sistema conhecem. Assim o seu sistema reage na hora, sem consultar a API de tempos em tempos.

O evento traz o recurso como a API o responde: um message.received traz a mensagem como GET /v1/messages/{messageId}. Para agir, como responder à mensagem, use a API.

Os webhooks fazem parte da API: só funcionam numa conta com a API liberada (veja Como obter acesso). Se a API for desligada para a conta, a Gaio para de enviar.

Cadastrar um endpoint

Cada conta tem até 10 endpoints, contando os desativados: exclua um para cadastrar outro. Cada endpoint recebe só os tipos de evento escolhidos para ele, e um evento vai para todos os endpoints que recebem o tipo dele.

No console

No console da Gaio, vá em Configurações › Desenvolvedores › Webhooks e clique em Adicionar endpoint. Como as chaves, os webhooks só aparecem para o dono da conta, entrando com o próprio login: um endpoint continua recebendo os dados da conta depois que a sessão termina.

Preencha:

  • URL: para onde a Gaio envia os eventos (veja A URL).
  • Descrição: opcional, quem recebe os eventos, como "Integração com o CRM".
  • Eventos: os tipos de evento que o endpoint recebe.

Depois de criar, o console mostra o segredo de assinatura uma única vez. Guarde-o no servidor que recebe os eventos, como a chave da API: é com ele que você confere a assinatura. Se perdê-lo, gere outro.

Pela API

Com uma chave que tenha o escopo webhooks:write:

curl https://api.gaio.social/v1/webhooks \
  -H "Authorization: Bearer $GAIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -d '{
    "url": "https://seu-servidor.com/webhooks/gaio",
    "description": "Integração com o CRM",
    "event_types": ["message.received", "contact.tagged"]
  }'

Os campos: url, até 2048 caracteres; description, opcional, até 200 caracteres (em branco, fica sem descrição); event_types, de 1 a 50 tipos, guardados sem repetição e em ordem alfabética.

A resposta, 201, é o endpoint com o secret, o segredo de assinatura:

{
  "id": "…",
  "url": "https://seu-servidor.com/webhooks/gaio",
  "description": "Integração com o CRM",
  "event_types": ["contact.tagged", "message.received"],
  "status": "enabled",
  "disabled_reason": null,
  "disabled_at": null,
  "failing_since": null,
  "previous_secret_expires_at": null,
  "created_at": "…",
  "updated_at": "…",
  "secret": "whsec_…"
}

Só esta resposta e a da troca do segredo trazem o secret: guarde-o na hora. Repetir o cadastro com a mesma chave de idempotência devolve o mesmo endpoint, com o segredo atual dele.

As rotas dos webhooks:

RotaO que fazEscopo
GET /v1/webhooksLista os endpoints, do mais novo para o mais antigo, numa página só.webhooks:read
POST /v1/webhooksCadastra um endpoint.webhooks:write
GET /v1/webhooks/{webhookId}Lê um endpoint.webhooks:read
PATCH /v1/webhooks/{webhookId}Muda url, description, event_types (a lista inteira) ou status (enabled ou disabled).webhooks:write
DELETE /v1/webhooks/{webhookId}Exclui um endpoint.webhooks:write
POST /v1/webhooks/{webhookId}/rotate-secretTroca o segredo.webhooks:write
POST /v1/webhooks/{webhookId}/testEnvia um evento de teste.webhooks:write
GET /v1/webhooks/{webhookId}/deliveriesLista as entregas.webhooks:read
POST /v1/webhooks/{webhookId}/deliveries/{deliveryId}/retryReenvia uma entrega.webhooks:write

Nos limites de uso, o teste e o reenvio contam como envio, as leituras como leitura e o resto como escrita.

Para assinar um tipo de evento, a chave também precisa do escopo que lê os dados dele pela API, porque um webhook nunca entrega ao seu sistema o que a própria chave não poderia ler. Sem ele, a resposta é 403, com insufficient_scope e o escopo que falta em context.required_scope.

EventosEscopo de leitura
message.*, comment.received e live_comment.receivedconversations:read
contact.* e connection.*contacts:read
workflow_run.ended e link.clickedworkflows:read

Desativar um endpoint (status: disabled) ou excluí-lo desiste do que ele ainda tinha para receber. Ativado de novo, ele recebe os eventos que acontecerem a partir daí. O disabled_reason diz por que o endpoint está desativado: requested, quando alguém o desativou (no console ou com PATCH e {"status": "disabled"}), ou failing, quando a Gaio o desativou por falhas.

Cadastrar, alterar, excluir um endpoint e trocar o segredo dele entra no log de auditoria da conta, com a chave como autora quando a mudança vem pela API: cadastrar um endpoint é a instrução da conta para enviar os dados dela àquele endereço.

A URL

A Gaio só envia para uma URL:

  • https, sem porta (a porta é sempre a 443);
  • sem usuário e senha;
  • num domínio que aponta para um endereço público, e não para uma rede privada, local ou reservada.

A URL é conferida no cadastro e de novo a cada envio. Uma URL recusada recebe 422, com invalid_webhook_url e o motivo em context.reason:

reasonO que significa
invalidNão é uma URL.
https_requiredNão é https.
credentialsTem usuário ou senha.
portTem uma porta diferente da 443.
unreachableO domínio não aponta para um endereço público.

O que chega

Cada evento chega num POST para a URL do endpoint, com o corpo em JSON:

{
  "id": "…",
  "type": "message.received",
  "api_version": "v1",
  "created_at": "…",
  "data": {
    "id": "…",
    "conversation_id": "…",
    "channel": { "type": "instagram", "connection_id": "…" },
    "type": "message",
    "direction": "inbound",
    "sender": { "type": "contact", "user_id": null, "name": null },
    "text": "Oi! Vocês entregam em Curitiba?",
    "attachments": [],
    "template": null,
    "reply_to_message_id": null,
    "button": null,
    "post": null,
    "status": "received",
    "is_unsupported": false,
    "is_removed": false,
    "is_edited": false,
    "reactions": [],
    "read_at": null,
    "created_at": "…"
  }
}
CampoO que é
idO id do evento. É o mesmo em todas as tentativas e nos reenvios: é por ele que você descarta os repetidos.
typeO tipo do evento.
api_versionA versão da API em que o data está escrito: v1.
created_atQuando aconteceu.
dataO recurso, como a API o responde.

E com estes cabeçalhos:

CabeçalhoO que é
Content-Typeapplication/json.
User-AgentGaio-Webhooks/1.0.
Gaio-SignatureA assinatura do corpo.
Gaio-Event-IdO id do evento.
Gaio-Event-TypeO type do evento.
Gaio-Delivery-AttemptO número da tentativa: 1 na primeira, 2 na segunda, e assim por diante.

Conferir a assinatura

Qualquer um pode chamar a URL do seu endpoint. Confira a assinatura de cada entrega antes de confiar no conteúdo: ela prova que foi a Gaio que enviou aquele corpo, com o segredo do endpoint, há pouco tempo.

O cabeçalho Gaio-Signature tem o formato t=1760020200,v1=…:

  • t é o momento da tentativa, em segundos desde 1970 (Unix).
  • v1 é o HMAC-SHA256, em hexadecimal, de <t>.<corpo>: o valor de t, um ponto e o corpo exatamente como chegou. A chave do HMAC é o segredo inteiro, com o whsec_.
  • Durante uma troca de segredo, vêm dois v1, um com cada segredo.

Para conferir:

  1. Leia o corpo cru, os bytes que chegaram, antes de interpretar o JSON. Um JSON interpretado e escrito de novo muda espaços, ordem e acentos, e a assinatura não bate.
  2. Calcule o HMAC-SHA256 de <t>.<corpo> com o segredo.
  3. Compare o resultado com cada v1, em tempo constante, e aceite se um deles bater.
  4. Recuse se t estiver a mais de 5 minutos do relógio do seu servidor: assim, quem capturou uma entrega não consegue reenviá-la depois. Mantenha o relógio sincronizado.

Cada tentativa é assinada de novo, com o seu próprio t: uma nova tentativa da mesma entrega traz outro Gaio-Signature e o mesmo id.

Responda com um erro, como 400, a uma entrega que não passa. Se o problema for do seu lado, como um segredo desatualizado, a Gaio tenta de novo, e nada se perde enquanto você corrige.

Os exemplos abaixo usam só a biblioteca padrão para conferir a assinatura e leem o segredo da variável de ambiente GAIO_WEBHOOK_SECRET. enqueue é onde o seu sistema guarda o evento para processar depois, como uma fila ou uma tabela (veja Responder e processar).

Node.js

Com Express, que entrega o corpo cru com express.raw:

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

const TOLERANCE_SECONDS = 5 * 60;

export function verifyGaioSignature(rawBody, header, secret) {
  const fields = (header ?? '').split(',').map((field) => field.trim().split('='));
  const timestamp = Number(fields.find(([key]) => key === 't')?.[1]);

  if (!Number.isInteger(timestamp)) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > TOLERANCE_SECONDS) return false;

  const expected = createHmac('sha256', secret).update(`${timestamp}.`).update(rawBody).digest();

  return fields.some(
    ([key, value]) =>
      key === 'v1' &&
      /^[0-9a-f]{64}$/.test(value ?? '') &&
      timingSafeEqual(expected, Buffer.from(value, 'hex'))
  );
}

const app = express();

app.post('/webhooks/gaio', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('Gaio-Signature');
  const secret = process.env.GAIO_WEBHOOK_SECRET;

  if (!Buffer.isBuffer(req.body) || !verifyGaioSignature(req.body, header, secret)) {
    return res.sendStatus(400);
  }

  enqueue(JSON.parse(req.body.toString('utf8')));
  res.sendStatus(200);
});

app.listen(3000);

Python

Com Flask, que entrega o corpo cru em request.get_data():

import hashlib
import hmac
import json
import os
import time

from flask import Flask, request

TOLERANCE_SECONDS = 5 * 60


def verify_gaio_signature(raw_body: bytes, header: str, secret: str) -> bool:
    fields = [field.strip().partition("=") for field in header.split(",")]
    timestamp = next((value for key, _, value in fields if key == "t"), "")
    signatures = [value for key, _, value in fields if key == "v1"]

    if not (timestamp.isascii() and timestamp.isdigit()):
        return False
    if abs(int(time.time()) - int(timestamp)) > TOLERANCE_SECONDS:
        return False

    signed = timestamp.encode() + b"." + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()

    return any(hmac.compare_digest(expected.encode(), value.encode()) for value in signatures)


app = Flask(__name__)


@app.post("/webhooks/gaio")
def gaio_webhook():
    raw_body = request.get_data()
    header = request.headers.get("Gaio-Signature", "")

    if not verify_gaio_signature(raw_body, header, os.environ["GAIO_WEBHOOK_SECRET"]):
        return "", 400

    enqueue(json.loads(raw_body))
    return "", 200

Em outro framework, o que muda é como ler o corpo cru: desligue o leitor de JSON na rota do webhook, ou leia o corpo antes dele.

Testar um endpoint

No console, clique em Enviar evento de teste no endpoint. Pela API, use POST /v1/webhooks/{webhookId}/test. A Gaio envia na hora um evento do tipo webhook.test, assinado como qualquer outro, com data igual a {"webhook_id": "…"}, e responde como o endpoint o recebeu:

{
  "delivered": true,
  "response_status": 200,
  "error": null,
  "duration_ms": 182
}

delivered diz se o endpoint respondeu 2xx, response_status é o status que ele respondeu, quando respondeu, e duration_ms é quanto o envio levou, em milissegundos. Quando o envio falha, delivered é false e error traz um dos motivos de last_error; quando dá certo, error vem null. O teste vai para o endpoint mesmo desativado, não entra no registro de entregas e não é tentado de novo. Responda 2xx a ele como a qualquer evento.

Responder e processar

Responda rápido

Responda 2xx em até 10 segundos e processe depois: guarde o evento, responda e faça o trabalho em seguida. Uma chamada ao seu CRM ou a um modelo de IA antes de responder pode passar dos 10 segundos.

Qualquer outro resultado é uma tentativa que falhou: um status fora de 2xx, inclusive um redirecionamento (a Gaio não segue redirecionamentos), uma resposta depois dos 10 segundos ou uma conexão que não se completa. O corpo da sua resposta é ignorado.

A Gaio faz até 5 entregas ao mesmo tempo para cada endpoint. Quando há mais eventos do que isso, eles esperam e saem aos poucos, até cerca de 100 por minuto.

Descarte os repetidos

A entrega é pelo menos uma vez: o mesmo evento pode chegar mais de uma vez, por exemplo quando a sua resposta se perde no caminho. Descarte os repetidos pelo id, que é o mesmo em todas as tentativas e nos reenvios. Guarde os id processados por pelo menos 7 dias, o tempo em que uma entrega ainda pode ser reenviada.

Não conte com a ordem

Os eventos não chegam necessariamente na ordem em que aconteceram: uma nova tentativa pode chegar depois de um evento mais novo, e um message.updated pode chegar antes do message.received da mesma mensagem. Quando a ordem importa, compare o created_at ou leia o recurso de novo pela API, que responde o estado atual.

Novas tentativas

Uma entrega que falhou é tentada de novo, até 10 tentativas, ao longo de uns 2 dias:

TentativaQuando
1ªLogo depois do evento.
2ª1 minuto depois da 1ª.
3ª5 minutos depois da 2ª.
4ª30 minutos depois da 3ª.
5ª2 horas depois da 4ª.
6ª5 horas depois da 5ª.
7ª a 10ª10 horas depois da anterior.

Depois da 10ª, a entrega fica com falha (failed), e você ainda pode reenviá-la.

Registro de entregas

Cada evento enviado a um endpoint é uma entrega, guardada por 7 dias. No console, abra Entregas no endpoint. Pela API, use GET /v1/webhooks/{webhookId}/deliveries, da mais recente para a mais antiga, com o filtro status (pending, succeeded ou failed):

{
  "id": "…",
  "webhook_id": "…",
  "event": { "id": "…", "type": "message.received", "created_at": "…" },
  "status": "failed",
  "attempts": 10,
  "next_attempt_at": null,
  "last_attempt_at": "…",
  "last_response_status": 500,
  "last_error": "http_status",
  "delivered_at": null,
  "created_at": "…"
}

O status é pending (esperando uma tentativa, ou numa agora), succeeded (o endpoint respondeu 2xx) ou failed (a Gaio desistiu). last_response_status é o status que o endpoint respondeu na última tentativa, quando respondeu, e last_error diz por que ela falhou:

last_errorO que significa
http_statusO endpoint respondeu um status fora de 2xx, inclusive um redirecionamento.
timeoutO endpoint não respondeu em 10 segundos.
dns_failedO domínio não foi encontrado.
connection_failedNão foi possível conectar ao servidor.
tls_failedO certificado HTTPS do servidor foi recusado.
unsafe_targetO domínio passou a apontar para uma rede para a qual a Gaio não envia.
endpoint_disabledO endpoint foi desativado ou excluído antes do envio.
account_disabledA conta estava suspensa ou sem a API.

Reenviar uma entrega

No console, clique em Reenviar na entrega. Pela API, use POST /v1/webhooks/{webhookId}/deliveries/{deliveryId}/retry, que responde 202 com a entrega de volta na fila. Dá para reenviar uma entrega que falhou ou uma que deu certo e o seu sistema precisa de novo; uma entrega pendente é respondida como está.

O reenvio manda o mesmo evento, com o mesmo id. Se ele falhar, as novas tentativas continuam de onde a entrega parou, então uma entrega que já usou as 10 tentativas falha de novo sem outras. O endpoint precisa estar ativo (409, com webhook_disabled), e depois de 7 dias a entrega não existe mais (404, com not_found).

Endpoint desativado por falhas

Se todas as tentativas para um endpoint falham por 3 dias, a Gaio desativa o endpoint (status: disabled, com disabled_reason: failing), desiste das entregas pendentes dele e avisa o dono da conta por e-mail. Enquanto as tentativas falham, failing_since diz desde quando, e uma entrega que dá certo zera a contagem.

Para voltar a receber, corrija o servidor, envie um evento de teste e ative o endpoint: no console, em Ativar, ou com PATCH /v1/webhooks/{webhookId} e {"status": "enabled"}. Ele recebe os eventos que acontecerem a partir daí; o que ficou para trás você reenvia pelo registro, dentro dos 7 dias.

Conta suspensa

Enquanto a conta está suspensa, ou sem a API, a Gaio não registra nem entrega eventos: o que acontece nesse período não gera evento, nem depois que a conta volta. As entregas que estavam pendentes, inclusive as da própria suspensão (como o connection.disconnected de cada conexão que ela desliga), ficam no registro de entregas com falha (account_disabled) e não são tentadas de novo sozinhas. Quando a conta volta, você pode reenviá-las dentro dos 7 dias.

Trocar o segredo

No console, clique em Gerar novo segredo no endpoint. Pela API, use POST /v1/webhooks/{webhookId}/rotate-secret, que responde o endpoint com o novo secret. Como no cadastro, o segredo só aparece nessa resposta.

O segredo anterior continua assinando por 24 horas, até o previous_secret_expires_at do endpoint: nesse tempo, cada entrega traz dois v1, um com cada segredo. Para trocar sem recusar nenhuma entrega:

  1. Gere o novo segredo.
  2. Passe o seu servidor para o novo segredo nas 24 horas seguintes. Os exemplos acima aceitam a entrega quando qualquer v1 bate, então funcionam antes e depois dessa troca.

Gerar outro segredo dentro dessas 24 horas descarta na hora o mais antigo dos dois.

Se o segredo vazou, gere outro e passe o seu servidor para ele logo em seguida: a partir daí, o seu servidor recusa uma assinatura feita com o segredo vazado, mesmo que a Gaio ainda assine com ele até o fim das 24 horas.

Eventos

O type diz o que aconteceu, e o data traz o recurso como a API o responde naquele momento. A lista de tipos e os campos de data crescem sem mudar de versão, como o resto da /v1 (veja Versionamento): ignore os campos que você não conhece.

Mensagens e comentários

O data é a mensagem como GET /v1/messages/{messageId} a responde naquele momento, com a conversa em conversation_id. Pedem o escopo conversations:read.

TipoQuando é enviado
message.receivedO contato mandou uma mensagem direta, respondeu ou mencionou um story, ou tocou num botão ou num quebra-gelo (o título dele vem em text), no Instagram ou no WhatsApp.
comment.receivedO contato comentou num post ou num reel.
live_comment.receivedO contato comentou numa live.
message.sentA conta enviou uma mensagem: alguém da equipe pela Gaio, uma automação, uma integração pela API (a sua também) ou alguém no app do Instagram ou do WhatsApp. Inclui as respostas da conta a comentários, públicas (data.type igual a comment_reply) ou no direct (comment_dm_reply).
message.updatedO remetente editou a mensagem, ou alguém reagiu a ela ou tirou a reação. Só no Instagram.
message.deletedO remetente apagou a mensagem no Instagram, ou a conta excluiu o comentário pela Gaio.
message.failedO WhatsApp aceitou um envio e depois avisou que ele não foi entregue.

Respostas e menções em story não têm um tipo próprio: chegam como message.received, com data.type igual a story_message (uma resposta a um story) ou story_mention (uma menção num story).

  • O histórico não é enviado: as mensagens que a Gaio guarda ao reconectar uma conta, ou que importa do app WhatsApp Business, não geram evento. Os comentários que a Gaio encontra sozinha, quando o Instagram não os entregou, geram.
  • Uma mensagem apagada antes de o evento dela ser entregue chega apagada: as tentativas que ainda faltam trazem a mensagem como a API a responde agora (is_removed: true, text: null). A exceção é o message.updated, de uma edição ou de uma reação: ele mantém a mensagem como estava naquela mudança.
  • Reconheça os seus próprios envios pelo id que o envio respondeu, e não pelo sender.type: quando o canal informa a mensagem antes de a Gaio registrar quem a enviou, o message.sent vem com unattributed, e a mensagem lida depois mostra o remetente.
  • A mídia de uma mensagem pode responder 404 por alguns segundos depois que o evento chega, enquanto a Gaio a guarda: tente de novo.
  • Um contato bloqueado das automações continua gerando message.received, mas um envio pela API para ele é recusado, com contact_blocked.

Contatos

O data traz o contato como GET /v1/contacts/{contactId} o responde depois da mudança. Pedem o escopo contacts:read.

TipoQuando é enviadodata
contact.createdUm contato foi criado, de qualquer jeito: alguém novo escreveu ou comentou, a conta escreveu para um número novo, uma conversa veio do histórico do canal, uma importação ou POST /v1/contacts.O contato, com as etiquetas e os valores com que foi criado.
contact.taggedEtiquetas foram postas num contato que não as tinha, por qualquer caminho: o console, uma automação, uma ação em massa, uma importação ou uma chave.{ "contact": …, "tags": […] }, com as etiquetas postas.
contact.untaggedEtiquetas foram tiradas de um contato que as tinha. Tirar uma etiqueta tira de todos os contatos da mesma pessoa, e cada um que a tinha gera um evento.{ "contact": …, "tags": […] }, com as etiquetas tiradas.
contact.custom_fields_updatedValores de campos personalizados foram preenchidos ou limpos, por qualquer caminho, inclusive uma pergunta de automação que guarda um e-mail ou um telefone.{ "contact": …, "custom_fields": […] }, uma entrada por campo que mudou, com field_id, name, type e value (null quando o valor foi limpo).
  • Um contato criado com etiquetas ou valores gera um único contact.created, que já os traz, e não também contact.tagged ou contact.custom_fields_updated.
  • Gravar o que o contato já tem não gera evento. Um contato que volta depois de excluído mantém o id e não chega como criado.
  • Excluir ou renomear uma etiqueta ou um campo personalizado não gera evento nos contatos que o tinham: se você guarda uma cópia, leia esses contatos de novo.
  • Uma ação em massa ou uma importação gera um evento por contato que mudou. Esses eventos saem a cerca de 100 por minuto por endpoint: uma ação em 5.000 contatos chega ao endpoint ao longo de uns 50 minutos.

Automações

Pedem o escopo workflows:read.

TipoQuando é enviadodata
workflow_run.endedUma execução terminou: chegou ao fim (completed), parou (stopped), por exemplo numa proteção da Gaio como a que impede laços, foi barrada (blocked), quando o Instagram recusa as respostas privadas no post, ou falhou e nada vai tentá-la de novo. Um envio adiado só termina a execução quando a Gaio desiste dele.A execução como GET /v1/workflows/{workflowId}/runs/{runId} a responde, com os passos.
link.clickedUm contato abriu um link que uma automação enviou, a cada vez que abre.workflow_id, run_id, contact_id, node_id, button_id, url e clicked_at.
  • Uma execução esperando o contato (waiting) não terminou. Uma execução stopped que o contato retoma depois, respondendo, termina de novo e gera outro evento.
  • O data do workflow_run.ended traz os passos dos trechos mais recentes da execução, com steps_truncated quando os mais antigos ficaram de fora. Numa execução iniciada por um comentário, a resposta pública ao comentário pode ser registrada depois do fim: lida depois, a execução a mostra entre os passos, com o mesmo status.
  • No link.clicked, url é para onde o contato foi levado, node_id é o bloco que enviou o link, button_id é o id do botão no grafo (mantenha os ids ao reescrever um bloco) e run_id é a execução que enviou o link ao contato por último. Cada um vem null quando a Gaio não sabe, e contact_id vem null para um contato que a conta não tem mais.
  • Toda abertura de um link conta, inclusive recarregar a página, como nas métricas de clique da automação. Um botão que passa a conversa para o WhatsApp não gera evento.
  • O clique chega aos webhooks por melhor esforço: numa falha rara, um clique pode ficar sem evento. Um link aberto mais de um ano depois do último envio mostra a página de link expirado e não gera evento.

Conexões

O data é a conexão como GET /v1/connections a lista depois da mudança, e o created_at do evento é quando ela mudou. Pedem o escopo contacts:read.

TipoQuando é enviado
connection.connectedUma conexão passou a conectada: no console, num login, quando a configuração de um número de WhatsApp termina ou quando uma fatura paga traz de volta o que uma suspensão desligou.
connection.disconnectedUma conexão passou a desconectada: no console, porque o canal retirou o acesso, por uma suspensão, pela Gaio ou porque a conta não foi usada nos dias depois de conectar.

O disconnect_reason da conexão diz por que ela está desconectada:

disconnect_reasonO que significa
authorization_lostO canal retirou o acesso da Gaio: o app foi removido, uma permissão foi revogada ou a senha mudou. Conectar de novo resolve.
disconnected_by_userAlguém da conta desconectou.
account_suspendedA conta foi suspensa, ou a Gaio desligou a conexão.
activation_expiredA conta não foi usada nos dias depois de conectar.
setup_incompleteUm número de WhatsApp com a configuração em andamento, com falha ou esperando o PIN da confirmação em duas etapas do número.

Ele vem null numa conexão conectada, e também numa conexão desconectada antes de a Gaio passar a registrar o motivo. Outros valores podem aparecer.

  • Um evento sai a cada vez que o status de uma conexão muda de verdade. Uma mudança que encontra a conexão já naquele status não gera evento, então a mesma mudança feita duas vezes, ao mesmo tempo ou numa nova tentativa, é um evento só.
  • Um número de WhatsApp configurado de novo enquanto conectado gera connection.disconnected, com setup_incomplete, até a configuração terminar: nesse meio-tempo ele não envia mensagens.
  • Remover uma conexão no console a desconecta antes, e isso gera evento; a remoção em si não gera, nem a troca de um número por outro.
  • Os connection.disconnected de uma suspensão não são entregues (veja Conta suspensa).

Erros

Os erros das rotas de webhooks seguem o formato de todas as rotas (veja Erros), com estes códigos próprios:

CódigoStatusQuando aconteceCampos extras em context
invalid_webhook_url422A URL não é uma para a qual a Gaio envia (veja A URL).reason
webhook_limit_reached409A conta já tem o máximo de endpoints.limit
webhook_disabled409Uma entrega reenviada para um endpoint desativado. Ative o endpoint antes.—
api_disabled403A API foi desligada para a conta.—
service_unavailable503Os webhooks não podem ser assinados agora. Tente de novo.—