Webhooks
Webhooks permitem que sua equipe receba notificações automáticas em tempo real no seu próprio servidor quando eventos acontecem no Pliic, como uma nova sugestão enviada ou uma mudança de status em um ticket.
Webhooks estão disponíveis nos planos Starter (até 3 endpoints) e Pro (ilimitados). O plano Gratuito não inclui este recurso. Apenas membros com papel Administrador ou Dono podem gerenciar webhooks.
Criando um endpoint
Seção intitulada “Criando um endpoint”- Acesse Configurações → Webhooks
- Clique em Adicionar endpoint
- Preencha os campos:
- Nome: identificação interna do endpoint
- URL: endereço HTTPS público que receberá as notificações
- Eventos: selecione quais eventos devem disparar este endpoint
- Clique em Criar
Ao criar, um segredo de assinatura (whsec_…) é exibido uma única vez. Copie e armazene com segurança, ele não pode ser recuperado depois.
Se o segredo for perdido ou comprometido, abra o menu do endpoint e escolha Rotacionar segredo. O Pliic mostra o novo segredo uma vez e o anterior deixa de funcionar imediatamente — atualize o receptor antes ou logo depois de rotacionar.
Eventos disponíveis
Seção intitulada “Eventos disponíveis”| Evento | Descrição |
|---|---|
suggestion.created | Uma nova sugestão foi enviada. |
suggestion.status_changed | O status de uma sugestão foi alterado. |
suggestion.commented | Um comentário foi adicionado a uma sugestão. |
ticket.created | Um novo ticket foi aberto. |
ticket.status_changed | O status de um ticket foi alterado. |
ticket.replied | Uma resposta pública foi adicionada a um ticket. Notas internas nunca disparam este evento — elas não aparecem em nenhuma superfície fora do painel da sua equipe. |
survey.response.created | Um usuário respondeu a uma pesquisa (survey). |
Formato do payload
Seção intitulada “Formato do payload”Cada entrega é uma requisição POST com Content-Type: application/json e o seguinte formato:
{ "id": "018e1234-5678-7abc-def0-123456789abc", "event": "suggestion.created", "created_at": "2024-01-15T14:30:00Z", "app": { "id": 42, "public_key": "pk_live_..." }, "data": { ... }}| Campo | Tipo | Descrição |
|---|---|---|
id | string (UUID) | Identificador único da entrega. |
event | string | Nome do evento disparado. |
created_at | string (ISO 8601) | Data e hora do evento em UTC. |
app | object | O app do seu time que originou o evento: id (identificador interno) e public_key (a mesma pk_live_... usada no widget desse app), para correlacionar o evento com a configuração certa quando o seu time tem mais de um app. |
data | object | Dados específicos do evento. |
Identificando quem fez o quê
Seção intitulada “Identificando quem fez o quê”Todo data de evento traz dois objetos de identidade, além dos campos específicos do evento:
| Campo | Descrição |
|---|---|
author | O dono do registro: quem abriu o ticket, criou a sugestão ou respondeu a pesquisa. Formato { external_id, name }. external_id é o id que o seu próprio sistema atribuiu a esse usuário ao criá-lo. |
sender | Quem causou este evento específico. Formato { type, external_id, name }. type é "member" quando alguém da sua equipe agiu pelo painel do Pliic (external_id vem null, membros não têm id externo) ou "app_user" quando foi o próprio usuário final (external_id presente). |
Em eventos disparados pelo próprio autor do registro (por exemplo, ele mesmo criando a sugestão), author e sender apontam para a mesma pessoa. Em eventos disparados por outra pessoa (a sua equipe respondendo um ticket, ou mudando o status de uma sugestão pelo board), eles divergem.
Exemplo de ticket.replied respondido pela sua equipe:
{ "ticket_id": 123, "ticket_number": "TKT-0042", "message_id": 987, "sender_type": "agent", "author": { "external_id": "usr_42", "name": "Ana" }, "sender": { "type": "member", "external_id": null, "name": "Suporte Pliic" }}Caso de uso comum: notificar o autor quando algo muda no registro dele, mas sem notificá-lo quando a mudança foi causada por ele mesmo (por exemplo, o próprio usuário respondendo o próprio ticket). Compare os dois external_id:
if (data.author.external_id !== data.sender.external_id) { notificar(data.author);}Em survey.response.created, quem responde é sempre o sender (não existe fluxo de responder “em nome de” outro usuário), então author e sender sempre coincidem. O evento também mantém o campo legado app_user_id, ao lado do novo author.
Cabeçalhos da requisição
Seção intitulada “Cabeçalhos da requisição”Cada entrega inclui os seguintes cabeçalhos HTTP:
| Cabeçalho | Descrição |
|---|---|
X-Pliic-Signature | Assinatura no formato t=<timestamp unix>,v1=<HMAC-SHA256>. |
X-Pliic-Event | Nome do evento (ex: suggestion.created). |
X-Pliic-Delivery | UUID único desta entrega. |
Verificando a assinatura
Seção intitulada “Verificando a assinatura”Verificar a assinatura garante que a requisição foi enviada pelo Pliic e que o conteúdo não foi alterado no caminho. O cabeçalho tem o formato:
X-Pliic-Signature: t=1750000000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdO t é o timestamp unix do envio e o v1 é HMAC-SHA256(segredo, "{t}.{corpo_bruto}"). Verificar o timestamp junto protege contra reenvio de payloads antigos capturados (replay).
PHP (com o SDK oficial):
use Pliic\Webhook;use Pliic\Exceptions\SignatureVerificationException;
try { $event = Webhook::constructEvent( $request->getContent(), $request->header('X-Pliic-Signature'), $segredoDoEndpoint, // whsec_... );} catch (SignatureVerificationException $e) { abort(400);}
// $event->type, $event->data, $event->idO SDK (composer require pliic/pliic-php) já compara em tempo constante e rejeita assinaturas com mais de 5 minutos.
Usando Laravel? A integração Laravel do SDK já registra essa rota pronta pra você, com a verificação de assinatura incluída.
PHP (manual):
function verificarAssinatura(string $segredo, string $corpoRaw, string $cabecalho): bool{ if (preg_match('/^t=(\d+),v1=([0-9a-f]{64})$/', $cabecalho, $m) !== 1) { return false; }
[, $timestamp, $assinatura] = $m;
if (abs(time() - (int) $timestamp) > 300) { return false; }
$esperado = hash_hmac('sha256', "{$timestamp}.{$corpoRaw}", $segredo);
return hash_equals($esperado, $assinatura);}Node.js:
const crypto = require('crypto');
function verificarAssinatura(segredo, corpoRaw, cabecalho) { const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(cabecalho); if (!m) return false;
const [, timestamp, assinatura] = m; if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const esperado = crypto .createHmac('sha256', segredo) .update(`${timestamp}.${corpoRaw}`) .digest('hex');
return crypto.timingSafeEqual( Buffer.from(esperado), Buffer.from(assinatura), );}Entregas e reenvios
Seção intitulada “Entregas e reenvios”Se o seu servidor retornar um status diferente de 2xx ou a conexão expirar, o Pliic tentará reenviar automaticamente com backoff exponencial:
| Tentativa | Atraso aproximado |
|---|---|
| 1ª (reenvio) | 5 minutos |
| 2ª | 15 minutos |
| 3ª | 45 minutos |
| 4ª | 135 minutos |
| 5ª | 405 minutos (~6,75 horas) |
Após 5 tentativas sem sucesso, a entrega é abandonada.
Reenvios do mesmo evento carregam o mesmo id no corpo. Se o seu processamento não for idempotente por natureza, guarde os id já processados e ignore repetidos.
O histórico completo de cada entrega (status, código de resposta e número de tentativas) está disponível na página de detalhes do endpoint.
Recuperando eventos perdidos
Seção intitulada “Recuperando eventos perdidos”Depois das 5 tentativas, se a entrega continuar falhando, o evento é abandonado e não há uma 6ª tentativa. Para não depender só do webhook chegar, use a API como rede de segurança:
GET /api/v1/tickets?updated_since=2024-01-15T14:30:00ZGET /api/v1/suggestions?updated_since=2024-01-15T14:30:00ZO parâmetro updated_since (timestamp ISO 8601) filtra os itens atualizados a partir daquele instante e devolve o resultado ordenado por updated_at crescente — o oposto do “mais recente primeiro” padrão desses endpoints, pensado justamente para varrer o que mudou em ordem cronológica.
Padrão recomendado: guarde o horário do último webhook processado com sucesso. Ao perceber um silêncio incomum ou uma entrega marcada como abandonada no histórico do endpoint, chame os dois endpoints com updated_since=<esse horário> e reprocesse o que vier, usando o mesmo tratamento idempotente que você já aplica a reenvios. A resposta de cada sugestão nesses endpoints também traz o objeto author, no mesmo formato usado nos eventos de webhook.
Testando o endpoint
Seção intitulada “Testando o endpoint”Na página de detalhes do endpoint, clique em Enviar teste. O Pliic enviará um evento webhook.ping para que você possa verificar se o servidor está recebendo requisições corretamente antes de ativar eventos reais.
Segurança
Seção intitulada “Segurança”- Apenas URLs HTTPS públicas são aceitas. Endereços internos, de rede privada ou localhost são rejeitados.
- O certificado TLS do servidor de destino é sempre verificado.
- Nunca compartilhe o segredo de assinatura nem o inclua em código público ou logs.