OctoSolveAjuda

Webhooks

Receba avisos da OctoSolve no seu sistema quando algo acontecer, com assinatura e reenvio automático.

Webhooks

Com a API você pergunta para a OctoSolve. Com webhook, a OctoSolve avisa você: toda vez que algo acontece na sua conta, mandamos uma requisição POST para o endereço que você cadastrar.

Serve para coisas como registrar o agendamento no seu sistema, avisar o time num canal interno quando cair um lead, ou disparar uma automação no Make ou no n8n quando um pagamento for confirmado.

Você cadastra os endereços em Configurações, aba Integrações.

Eventos disponíveis

Cada endereço cadastrado escolhe quais eventos quer receber. Não é tudo ou nada.

EventoQuando dispara
contact.createdUm contato novo foi criado
contact.updatedUm contato foi alterado
contact.deletedUm contato foi apagado, pela API ou pelo painel
appointment.createdUm agendamento foi criado
appointment.updatedUm agendamento mudou data, duração, profissional, serviço, valor ou status sem cancelamento
appointment.cancelledUm agendamento foi cancelado por qualquer fluxo
payment.confirmedUm pagamento foi confirmado
conversation.handoffUma conversa passou do agente para uma pessoa do seu time

Mensagens de conversa não geram evento nesta versão, de propósito: o volume é alto e o conteúdo é sensível.

Formato do envio

Todo evento chega com a mesma casca:

{
  "id": "evt_9f1c2d3e4a5b6c7d8e9f0a1b2c3d4e5f",
  "type": "appointment.created",
  "created_at": "2026-08-13T14:00:00+00:00",
  "data": {}
}

O data muda conforme o tipo. Campos podem vir null.

contact.created e contact.updated

Mesmo formato do contato na API: id, telefone, nome, email, cidade, data_nascimento, observacoes, tags, status, ig_username, telegram_username, updated_at.

O telefone pode vir null: contato que chegou pelo Instagram ou pelo Telegram se identifica por usuário.

O CPF não vai no evento, de propósito. O aviso sai da nossa casa e entra no seu sistema, e no caminho ele passa por lugares que ninguém controla depois: o log do seu servidor, o log do seu gateway e ferramentas de integração no meio (Make, n8n, Zapier). O CPF é do cliente final, não da sua empresa, então quanto menos lugares ele visitar, menor o estrago se um desses lugares vazar.

Se você precisa do CPF, ele está na API. Pegue o id que veio no evento e consulte:

GET /api/v1/contacts/{id}

Esse caminho é diferente do aviso: a consulta sai da sua chave, fica registrada, e você escolhe a hora. Vale a mesma ideia para qualquer dado que não esteja no evento.

contact.deleted

CampoTipo
iduuid
deleted_atdatetime

Payload mínimo, de propósito: quem apagou o próprio dado pessoal não pode receber telefone, nome nem CPF junto do aviso de que ele foi apagado. Use o id para casar com o contato que você já tinha guardado do contact.created.

Este evento dispara tanto quando o contato é apagado pela API (DELETE /v1/contacts/{id}) quanto pelo painel.

Apagar um contato apaga junto os eventos dele que ainda estavam na fila. Se o contato foi apagado logo depois de um agendamento ou cancelamento, esses eventos podem nunca chegar até você: só o contact.deleted chega. É por exigência de privacidade, porque a entrega guarda uma cópia do dado pessoal enquanto espera.

Duas coisas seguem disso. Não conte com o histórico de agendamentos de um contato apagado, e trate o contact.deleted apagando o registro do seu lado também: o que já saiu para o seu servidor continua aí, e a exclusão feita aqui não alcança a sua base.

appointment.created, appointment.updated e appointment.cancelled

CampoTipo
iduuid
contact_iduuid
data_horadatetime
statusstring
duracao_minutosint ou null
servico_iduuid ou null
profissional_iduuid ou null
valornúmero ou null
pagobooleano ou null
motivo_cancelamentostring ou null
created_atdatetime ou null

appointment.updated é enviado quando muda data_hora, duracao_minutos, profissional_id, servico_id, valor ou status, desde que o novo status não seja um cancelamento.

Alterar somente notas não envia webhook. A nota não faz parte do payload, de propósito: é texto livre do dono e pode conter dado sensível, inclusive clínico. Enviar um evento sem mostrar o que mudou também criaria ruído para a integração.

appointment.cancelled é enviado em qualquer cancelamento, inclusive no cancelamento automático por pagamento não feito. Os estados cancelado, cancelado_por_nao_pagamento, cancelado_estornado e cancelado_lgpd pertencem a essa família.

payment.confirmed

CampoTipo
iduuid
contact_iduuid ou null
appointment_iduuid ou null
valor_brlnúmero
tipostring
statusstring
paid_atdatetime ou null
created_atdatetime

conversation.handoff

CampoTipo
conversation_iduuid
contact_iduuid
canalstring
motivostring
ocorrido_emdatetime

Nenhum trecho da conversa é enviado.

Cabeçalhos

CabeçalhoPara que serve
X-Octo-EventTipo do evento
X-Octo-TimestampMomento do envio, em segundos Unix. Entra na assinatura
X-Octo-SignatureAssinatura no formato sha256=<hex>
X-Octo-DeliveryIdentificador da entrega. Estável entre as tentativas: use para deduplicar
X-Octo-Event-IdIdentificador do evento que originou a entrega
X-Octo-Event-TimestampMomento em que o fato aconteceu, ISO 8601 em UTC
X-Octo-AttemptNúmero da tentativa. 1 é a primeira

Deduplique por X-Octo-Delivery. Se o seu servidor processar o evento e a resposta se perder, nós reenviamos: a segunda entrega chega com o mesmo X-Octo-Delivery. Tratar cada chegada como nova duplica registro.

Use X-Octo-Event-Timestamp para decidir se o evento ainda faz sentido. Um appointment.cancelled represado por duas horas pode não valer mais no seu sistema, e só esse cabeçalho conta essa diferença: o X-Octo-Timestamp é a hora da tentativa e sempre parece recente.

Verificar a assinatura

Verifique sempre. Sem isso, qualquer um que descubra a sua URL pode fingir ser a OctoSolve.

A assinatura é um HMAC SHA256 do texto {timestamp}.{corpo}, usando o secret do endereço como chave. O corpo é o texto cru recebido, antes de virar objeto.

Rejeite envios com X-Octo-Timestamp de mais de 5 minutos atrás, para bloquear reenvio malicioso.

Python

import hashlib
import hmac
import time
 
SECRET = "whsec_exemplo_troque_pelo_seu"
 
def assinatura_confere(corpo: bytes, timestamp: str, assinatura: str) -> bool:
    if abs(time.time() - float(timestamp)) > 300:
        return False
    esperada = hmac.new(
        SECRET.encode(), f"{timestamp}.".encode() + corpo, hashlib.sha256
    ).hexdigest()
    recebida = assinatura.removeprefix("sha256=")
    return hmac.compare_digest(esperada, recebida)

Node

const crypto = require("crypto");
 
const SECRET = "whsec_exemplo_troque_pelo_seu";
 
function assinaturaConfere(corpo, timestamp, assinatura) {
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const esperada = crypto
    .createHmac("sha256", SECRET)
    .update(`${timestamp}.`)
    .update(corpo)
    .digest("hex");
  const recebida = assinatura.replace(/^sha256=/, "");
  return crypto.timingSafeEqual(Buffer.from(esperada), Buffer.from(recebida));
}

No Express, guarde o corpo cru com express.json({ verify: (req, _res, buf) => { req.rawBody = buf; } }). Se você assinar em cima do JSON já convertido e convertido de volta, a assinatura não vai bater.

O mesmo evento pode chegar duas vezes

Esta é a parte mais importante desta página.

A entrega é pelo menos uma vez. Em falha de rede ou reenvio, o mesmo evento pode chegar repetido. O seu sistema precisa aguentar isso.

Como resolver: guarde o campo id do evento (o evt_...) e ignore o que você já processou. Esse id é estável, inclusive quando você clica em Reenviar no painel.

A ordem também não é garantida. Uma tentativa que falhou e foi reenviada pode chegar depois de um evento mais novo. Se a ordem importa para você, use o created_at do evento, não a ordem de chegada.

Se o seu endereço não responder

Consideramos entregue quando a resposta é 2xx. Qualquer outra coisa é falha.

Tentamos de novo, com espaço crescente entre as tentativas:

TentativaDepois da anterior
2ª1 minuto
3ª5 minutos
4ª30 minutos
5ª2 horas
6ª (última)6 horas

Algumas respostas não são tentadas de novo, porque insistir não adiantaria:

  • 3xx (redirecionamento): não seguimos redirecionamento, por segurança. Cadastre o endereço final.
  • 4xx, com exceção de 408 e 429: quer dizer que o endereço existe mas recusou. Corrija e reenvie pelo painel.

Respondemos ao Retry-After quando ele pede um tempo maior que o nosso, com teto de 24 horas por espera nos eventos de pagamento e contato, e de 2 horas nos de agenda e atendimento.

A agenda depende do evento

Nem todo aviso envelhece igual, então nem todo aviso insiste igual:

EventoTentativasÚltima tentativa, contando da primeira
payment.confirmed6cerca de 8 h 36 min
contact.created, contact.updated, contact.deleted6cerca de 8 h 36 min
appointment.created, appointment.updated, appointment.cancelled4cerca de 36 min
conversation.handoff4cerca de 36 min

Evento de agenda entregue horas depois pode chegar quando o horário já passou: o seu sistema agiria sobre um fato morto. Pagamento é o contrário, e por isso insiste por mais tempo.

Endereço desligado automaticamente

Depois de 20 tentativas seguidas com falha, desligamos o endereço e avisamos você no painel. É proteção contra endereço abandonado gerando fila infinita.

Ele não é apagado: você conserta o seu lado e clica em reativar.

Trocar o endereço não cria um endpoint novo. Em Configurações, aba Integrações, edite o cadastro e salve o endereço novo: o secret e o histórico de entregas continuam os mesmos. As tentativas com falha não zeram ao trocar, então um endereço já desligado continua desligado depois de salvar, e precisa do clique em reativar.

Requisitos do endereço

RegraMotivo
Precisa ser https:// na porta 443Evento trafega com dado do seu cliente
Não pode apontar para rede internaProteção contra uso da OctoSolve para atacar redes privadas
Não pode ter usuário e senha na URLFormato usado para enganar leitor humano
Responda rápido, em até 10 segundosPassou disso, tratamos como falha e tentamos de novo

Responda 200 assim que receber e processe depois, em segundo plano. Não deixe o processamento inteiro dentro da requisição.

Limites por plano

PlanoWebhooksEndereços
Starternão0
Plussim5
Prosim10

O limite de requisições por minuto da página de Limites vale para a API. Envio de webhook é nosso, não consome a sua cota.

Retenção do histórico

O histórico de entregas, com tentativas, código de resposta e corpo devolvido pelo seu servidor, fica disponível por 30 dias. Depois disso a linha é apagada.

Isso importa em dois momentos: uma investigação de "por que não chegou" precisa acontecer dentro dessa janela, e qualquer auditoria de prazo maior tem que guardar o evento no seu lado, no momento em que ele chega.

Receita para Make ou n8n

  1. Crie um cenário (Make) ou workflow (n8n) começando com Webhook ou Webhook Trigger.
  2. Copie a URL que a ferramenta gerar.
  3. Na OctoSolve, vá em Configurações, aba Integrações, e cadastre essa URL escolhendo os eventos.
  4. Copie o secret que aparece uma única vez e guarde.
  5. No fluxo, adicione um passo que confira a assinatura (as duas ferramentas têm nó de código; use o exemplo em Node acima).
  6. Adicione um passo que ignore evento com id já visto, guardando os ids num Data Store (Make) ou numa tabela (n8n).
  7. Só depois disso, ligue o passo que faz o trabalho de verdade.

Os passos 5 e 6 parecem burocracia, mas são o que separa uma automação que funciona de uma que duplica cobrança no fim de semana.

Testar

Enquanto você monta, aponte para um coletor público (como o webhook.site) para ver o formato real do evento chegando. Depois troque para o seu endereço definitivo e rotacione o secret, porque o antigo passou por um serviço de terceiro.

Veja também

  • Integrações, a mesma tela explicada sem jargão, com prints de onde ficam o cadastro do endereço, a troca do segredo e o histórico de entregas.
  • Limites de requisições, para o teto da API que a sua integração também usa.