Pular para o conteúdo

Webhooks do CRM

Um webhook do CRM é configurado por funil, no painel Maestron (CRM → abrir um funil → Configurar funil → aba Webhooks). Ele dispara um POST assinado pra uma URL https:// sua toda vez que um evento assinado acontece em qualquer negócio daquele funil — criado pelo app, pela API pública ou por um formulário de captação.

Se preferir puxar em vez de receber, o feed de eventos abaixo é a mesma informação, por API Key, sem precisar expor uma URL pública.

EventoDispara quando
lead.createdUm negócio é criado.
lead.updatedQualquer campo do negócio muda (inclusive junto com stage_changed/won/lost abaixo).
lead.stage_changedO negócio muda de estágio.
lead.wonO status muda para um valor cujo tipo (kind) é ganho.
lead.lostO status muda para um valor cujo tipo (kind) é perdido.
lead.deletedO negócio é removido.
lead.comment_addedUm comentário é adicionado ao negócio.
lead.activity_createdUma atividade é criada no negócio.
lead.activity_completedUma atividade do negócio é marcada como concluída.

Um negócio que muda de etapa dispara os dois lead.stage_changed e lead.updated — assine só o que precisar.

Na aba Webhooks, “Adicionar webhook” pede:

  • Nome e URL — só https://.
  • Eventos — quais da lista acima este endpoint recebe.
  • Campos enviados — por padrão todos os campos do negócio; você pode restringir a uma lista (o id do negócio sempre vem, mesmo restringindo).
  • Ativo.

Ao salvar, o painel mostra o segredo (whsec_...) — usado pra verificar a assinatura. “Enviar teste” dispara um ping síncrono pro endpoint. Um endpoint com muitas falhas consecutivas de entrega é desativado automaticamente (reative manualmente na aba).

Gerenciar exige a permissão crm.webhooks.manage no painel.

{
"event": "lead.stage_changed",
"occurred_at": "2026-09-08T18:15:52Z",
"funnel_id": "b7e8c1a2-3d4f-4a5b-8c9d-0e1f2a3b4c5d",
"lead": {
"id": "01a0823b-cf5d-71fc-9177-bac53df79b12",
"funnel_id": "b7e8c1a2-3d4f-4a5b-8c9d-0e1f2a3b4c5d",
"stage_id": "c3d4e5f6-7a8b-4c9d-0e1f-2a3b4c5d6e7f",
"contact_name": "Rogério Corona",
"status": "negociando",
"custom_fields": {}
},
"context": {
"from_stage_id": "a1c2d3e4-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"to_stage_id": "c3d4e5f6-7a8b-4c9d-0e1f-2a3b4c5d6e7f"
}
}

lead traz o shape de um Negócio (filtrado pelos “Campos enviados”, se configurado). context traz metadados específicos do evento — nunca filtrado por “Campos enviados”:

Eventocontext
lead.stage_changed{ from_stage_id, to_stage_id }
lead.won / lead.lost{ status }
lead.updated{ changed: string[] } — nomes dos campos que mudaram
lead.comment_added{ comment_id }
lead.activity_created / lead.activity_completed{ activity_id, subject }
demais{}
HeaderConteúdo
X-Maestron-EventO evento (lead.created, …).
X-Maestron-DeliveryID único desta tentativa de entrega.
X-Maestron-Signaturesha256=<hex> — HMAC-SHA256 do corpo bruto, com o segredo do webhook.

Verificação (Node.js):

const crypto = require('crypto');
function isValid(rawBody, signatureHeader, secret) {
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}

Use sempre o corpo bruto (antes de fazer JSON.parse) — reserializar o JSON pode mudar a ordem das chaves e quebrar a comparação.

Seu endpoint tem até 5 segundos para responder.

RespostaComportamento
2xxSucesso.
4xx (exceto 429)Erro seu — sem retentativa.
429 ou 5xx ou timeout/redeRetentativa exponencial: 1min, 5min, 30min, 2h, 6h, 24h (6 tentativas).

A mesma fonte de dados dos webhooks, por API Key — sem precisar expor uma URL pública. Útil pra sincronizar seu sistema periodicamente ou como plano B se seu endpoint ficar fora do ar por muito tempo (a entrega por webhook não guarda histórico além das 6 tentativas).

Escopo: crm.view.

GET /crm/events

Listar eventos

Retorna eventos em ordem cronológica (mais antigo primeiro), paginado por cursor.

Parâmetros de query

CampoTipoObrigatórioDescrição
funnel_id string — Restringe a um funil.
event string — Restringe a um tipo de evento.
cursor string — Da resposta anterior (next_cursor) — continua de onde parou.
limit integer — Entre 1 e 100. Padrão 50.
curl -X GET https://api.maestron.com.br/api/v1/crm/events \
  -H "Authorization: Bearer SEU_TOKEN"
<?php
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.maestron.com.br/api/v1/crm/events', [
    'headers' => [
        'Authorization' => 'Bearer SEU_TOKEN',
        'Accept' => 'application/json',
    ],
]);
$data = json_decode($response->getBody(), true);
import requests
headers = {"Authorization": "Bearer SEU_TOKEN"}
response = requests.get(
    "https://api.maestron.com.br/api/v1/crm/events",
    headers=headers,
)
data = response.json()
const response = await fetch("https://api.maestron.com.br/api/v1/crm/events", {
  method: "GET",
  headers: {
    "Authorization": "Bearer SEU_TOKEN",
  },
});
const data = await response.json();
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "SEU_TOKEN");
var response = await client.GetAsync("https://api.maestron.com.br/api/v1/crm/events");
var data = await response.Content.ReadAsStringAsync();
GET /crm/events HTTP/1.1
Host: api.maestron.com.br
Authorization: Bearer SEU_TOKEN

Respostas

{
  "message": "Eventos.",
  "data": {
    "items": [
      {
        "id": "01a08246-cd97-7300-bc9c-36c1b5d303cc",
        "event": "lead.created",
        "occurred_at": "2026-09-08T15:27:53Z",
        "funnel_id": "b7e8c1a2-3d4f-4a5b-8c9d-0e1f2a3b4c5d",
        "lead_id": "01a08246-c214-737b-b690-49910e76d62c",
        "lead": {
          "id": "01a08246-c214-737b-b690-49910e76d62c",
          "contact_name": "Smoke Test Lead",
          "status": "sem_contato"
        },
        "context": {}
      }
    ],
    "next_cursor": "eyJ2IjoiMjAyNi0wOS0wOFQxNTo..."
  }
}
{
  "message": "Não autenticado.",
  "data": null
}
{
  "message": "A chave não tem o escopo necessário.",
  "data": null
}

Sem next_cursor na resposta = não há mais eventos além dos retornados. Guarde o next_cursor e passe de volta na próxima chamada para continuar de onde parou — não filtre por occurred_at na mão.

Para o detalhamento de cada código de status, consulte Erros.